多线程基本概念
基本概念
- 进程 (Process):一个程序运行起来时在内存中开辟一段空间用来运行程序,这段空间包括 heap、stack、data segment 和 code segment。例如,开一个 QQ 就表明开了一个 QQ 进程。
- 线程 (Thread):每一个进程中都至少有一个线程。线程是指程序中代码运行时的运行路径,一个线程表示一条路径。例如 QQ 进程中,发送消息、接收消息、接收文件、发送文件等各种独立的功能都需要一个线程来执行。
进程和线程的区别:
- 从资源的角度来考虑,进程主要考虑的是 CPU 和内存,而线程主要考虑的是 CPU 的调度,某进程中的各线程之间可以共享这个进程的很多资源。
- 从粒度粗细来考虑,进程的粒度较粗,进程上下文切换时消耗的 CPU 资源较多。线程的粒度要小的多,虽然线程也会切换,但因为共享进程的上下文,相比进程上下文切换而言,同进程内的线程切换时消耗的资源要小的多的多。
- 在 JAVA 中,除了 java 运行时启动的 JVM 是一个进程,其他所有任务都以线程的方式执行,也就是说 java 应用程序是单进程的,甚至可以说没有进程的概念。
- 线程组 (ThreadGroup):线程组提供了一些批量管理线程的方法,因此通过将线程加入到线程组中,可以更方便地管理这些线程。
- 线程的状态:就绪态、运行态、睡眠态。还可以分为存活和死亡,死亡表示线程结束,非死亡则存活,因此存活包含就绪、运行、睡眠。
- 中断睡眠 (interrupt):将线程从睡眠态强制唤醒,唤醒后线程将进入就绪队列等待 cpu 调度。
- 并发操作:多个线程同时操作一个资源。这会带来多线程安全问题,解决方法是使用线程同步。
- 线程同步:让线程中的某些任务原子化,即要么全部执行完毕,要么不开始执行。通过互斥锁来实现同步,通过监视这个互斥锁是否被谁持有来决定是否从睡眠态转为就绪态 (即从线程池中出去),也就是是否有资格去获取 cpu 的执行权。线程同步解决了线程安全的问题,但降低了程序的效率。
- 死锁:线程全睡眠了无法被唤醒,导致程序卡死在某一处无法再执行下去。典型的是两个同步线程,线程 1 持有 A 锁,且等待 B 锁,但线程 2 持有 B 锁且等待 A 锁,这样的僵局会造成死锁。但需要注意的是,死锁并非都是因为僵局,只要两边的线程都无法继续向下执行代码 (或者两边的线程池都无法被唤醒,这是等价的概念,因为锁等待也会让进程进入睡眠态),则都是死锁。
- 竞争条件
在 Java 多线程中,当两个或以上的线程对同一个数据进行操作的时候,可能会产生 “竞争条件” 的现象。这种现象产生的根本原因是因为多个线程在对同一个数据进行操作,此时对该数据的操作是非 “原子化” 的,可能前一个线程对数据的操作还没有结束,后一个线程又开始对同样的数据开始进行操作,这就可能会造成数据结果的变化未知。
- java中使用的线程调度算法
抢占式。一个线程用完 CPU 之后,操作系统会根据线程优先级、线程饥饿情况等数据算出一个总的优先级并分配下一个时间片给某个线程执行。
什么是 ThreadLocal 类,怎么使用它?
ThreadLocal 是一个线程级别的局部变量,并非 “本地线程”。ThreadLocal 为每个使用该变量的线程提供了一个独立的变量副本,每个线程修改副本时不影响其它线程对象的副本 (译者注)。- 一个线程局部变量 (ThreadLocal variables) 为每个线程方便地提供了一个单独的变量。
- ThreadLocal 实例通常作为静态的私有的 (private static) 字段出现在一个类中,这个类用来关联一个线程。
- 当多个线程访问 ThreadLocal 实例时,每个线程维护 ThreadLocal 提供的独立的变量副本。
- 常用的使用可在 DAO 模式中见到,当 DAO 类作为一个单例类时,数据库链接 (connection) 被每一个线程独立的维护,互不影响。(基于线程的单例)
- 在一个对象上两个线程可以调用两个不同的同步实例方法吗?
不能,因为一个对象已经同步了实例方法,线程获取了对象的对象锁。所以只有执行完该方法释放对象锁后才能执行其它同步方法。
- 当一个同步方法已经执行,线程能够调用对象上的非同步实例方法吗?
可以,一个非同步方法总是可以被调用而不会有任何问题。实际上,Java 没有为非同步方法做任何检查,锁对象仅仅在同步方法或者同步代码块中检查。如果一个方法没有声明为同步,即使你在使用共享数据 Java 照样会调用,而不会做检查是否安全,所以在这种情况下要特别小心。一个方法是否声明为同步取决于临界区访问 (critial section access), 如果方法不访问临界区 (共享资源或者数据结构) 就没必要声明为同步的。
线程相关的常用方法
Thread 类中的方法:
- isAlive(): 判断线程是否还活着。活着的概念是指是否消亡了,对于运行态、就绪态、睡眠态的线程都是活着的状态。
- currentThread(): 返回值为 Thread,返回当前线程对象。
- getName(): 获取当前线程的线程名称。
- setName(): 设置线程名称。给线程命名还可以使用构造方法 Thread(String thread_name) 或 Thread(Runnable r,String thread_name)。
- getPriority():获取线程优先级。优先级范围值为 1-10 (默认值为 5),相邻值之间的差距对 cpu 调度的影响很小。一般使用 3 个字段 MIN_PRIORITY、NORM_PRIORITY、MAX_PRIORITY 分别表示 1、5、10 三个优先级,这三个优先级可较大地区分 cpu 的调度。
- setPriority(): 设置线程优先级。
- run():封装的是线程开启后要执行的任务代码。如果 run () 中没有任何代码,则线程不做任何事情。
- start():开启线程并让线程开始执行 run () 中的任务。
- toString(): 返回线程的名称、优先级和线程组。
- sleep(long millis):让线程睡眠多少毫秒。
- join(t1): 将线程 t1 合并到当前线程,并等待线程 t1 执行完毕后才继续执行当前线程。即让 t1 线程强制插队到当前线程的前面并等待 t1 完成。
- yield(): 将当前正在执行的线程退让出去,以让就绪队列中的其他线程有更大的几率被 cpu 调度。即强制自己放弃 cpu,并将自己放入就绪队列。由于自己也在就绪队列中,所以即使此刻自己放弃了 cpu,下一次还是可能会立即被 cpu 选中调度。但毕竟给了机会给其它就绪态线程,所以其他就绪态线程被选中的几率要更大一些。
Object 类中的方法:
- wait(): 线程进入某个线程池中并进入睡眠态。等待 notify () 或 notifyAll () 的唤醒。
- notify(): 从某个线程池中随机唤醒一个睡眠态的线程。
- notifyAll(): 唤醒某个线程池中所有的睡眠态线程。
java.util.concurrent.locks 包中的类和它们的方法
Lock 类中:
lock(): 获取锁 (互斥锁)。
unlock(): 释放锁。
newCondition(): 创建关联此 lock 对象的 Condition 对象。
Condition 类中:
await(): 和 wait () 一样。
signal(): 和 notify () 一样。
signalAll(): 和 notifyAll () 一样。
还没有评论,来说两句吧...