Java并发-线程基础
线程状态转移
线程状态:
- 新建(New)
- 可运行(Runnable)
- 阻塞(Blocking)
- 无期限等待(Waiting)
- 期限等待(Timed Waiting)
- 死亡(Terminated)
新建
创建后未启动.
可运行
- 可能正在运行,可能在等CPU时间片.
- 包含Running 和 Ready.
阻塞
等待获取排它锁.
无期限等待
- 等待其他线程唤醒.
- 不分配时间片.
进入 | 退出 |
---|---|
无参Object.wait() |
Object.notify() /Object.notifyAll() |
无参Thread.join() |
被调用程序结束 |
LockSupport.park() |
期限等待
- 无需其他线程显式唤醒,一定时间被系统唤醒.
- 阻塞:被动的,等待获取排它锁;等待:主动的,通过
sleep()
或wait()
进入.
进入 | 退出 |
---|---|
Thread.sleep() 方法 |
时间结束 |
Object.wait(int time) |
时间结束/Object.notify() /Object.notifyAll() |
Thread.join(int time) |
时间结束/被调用线程完成 |
LockSupport.parkNanos() |
|
LockSupport.parkUntil() |
死亡
线程结束任务或产生异常终止.
线程的创建
方式:
- 实现
Runnable
接口. - 实现
Callable
接口. - 继承
Thread
类.
注:
Runnable
和Callable
是创建一个可以在线程中运行的任务,最终线程的创建还是通过Thread
来实现.
实现Runnable接口
- 需实现
run
方法. - 通过
Thread
的start()
方法启动线程.
实现Callable接口
- 可以有返回值.
- 重写
call()
方法. - 返回值通过
FutureTask
对象进行封装.(通过Callable
对象创建FutureTask
对象,再通过Thread
创建线程,通过FutureTask
对象获得返回值)
继承Thread类
- 需实现
run()
方法. - 调用
start()
方法启动线程,JVM将线程放在就绪队列中等待调度.被调度的线程执行run()
方法.
实现接口与继承Thread
- Java非多继承,继承Thread类就无法继承其他类,但可实现多个接口.
- 类可能只要可执行就行,继承Thread开销大.
基础线程机制
Executor
管理多个异步任务的执行,无需显式管理线程的生命周期。
分类:
CachedThreadPool
:一个任务创建一个线程。FixedThreadPool
:所有任务只能使用固定大小的线程。SingleThreadExecutor
:大小为1的FixedThreadPool。
CachedThreadPool
ExecutorService es = Executors.newCachedThreadPool();
for (int i = 0; i < 10; i++) {
es.execute(()->{
System.out. println("This is "+Thread.currentThread().getName());
});
}
Thread.sleep(2000);
es.shutdown();
Daemon
- 守护线程是程序运行时在后台提供服务的线程。
- 当所有非守护线程结束时,程序结束,同时结束其设置的所有守护线程。
main()
属于非守护线程。- 使用
setDaemon()
将一个线程设为守护线程。(thread.setDaemon(true)
)
sleep()
- 会休眠当前运行的线程指定的时间。
- 可能抛出异常,需要捕获。
yield()
- 声明当前线程可以切换给其他线程执行。
- 不一定让出CPU,具体由CPU决定。
线程中断
- 一个线程在运行期间发生异常后会提前结束。
InterruptedException
- 对于一个处于阻塞、期限等待或无期限等待状态的线程,调用其
interrupt()
中断线程,会抛出InterruptedException
异常,从而使线程提前结束。 interrupt
不能中断I/O阻塞或synchronized
锁阻塞的线程。
interrupted()
- 使用
interrupted()
方法,在当前线程被其他线程interrupt()
后,返回true,从而可以用于被中断后正常结束进程。
Executor的中断操作
shutdown()
方法会等待线程都执行完毕后再关闭。shutdownNow()
立即关闭所有线程,相当于调用所有线程的interrupt()
方法。- 若中断指定的线程,则创建线程时使用
submit()
创建一个Future
对象,再调用其cancel(true)
进行中断。
线程互斥同步
两种锁机制控制多线程对共享资源的互斥访问:
synchronized
ReentrantLock
synchronized
同步一个代码块
// 多线程访问同一对象时控制其互斥访问
// 若为不同对象时,则多线程互不影响
public void func() {
synchronized (this) {
// 共享变量访问代码
}
}
// 同一对象,互斥访问
Thread t1 = new Thread(()->{func()});
Thread t2 = new Thread(()->{func()});
// 不同对象,互不影响
Object o1 = new Object();
Object o2 = new Object();
Thread t1 = new Thread(()->{o1.func()});
Thread t2 = new Thread(()->{o2.func()});
同步一个方法
// 同一对象,多线程互斥访问
// 不同对象,多线程互不影响
public synchronized void func () {
// 共享变量访问代码
}
同步一个类
// 作用于整个类
// 无论是否是同一个对象,同一个类的对象都互斥访问
public void func() {
synchronized (Test.class) {
// 共享变量访问代码
}
}
// 同样是作用于整个类
public synchronized static void fun() {
// 共享变量访问代码
}
ReentrantLock
示例:
private Lock lock = new ReentrantLock();
public void func() {
lock.lock(); // 获得锁
try {
// 共享变量访问代码
} finally {
lock.unlock(); // 释放锁,避免发生死锁。
}
}
对比
实现
synchronized
是JVM实现的。ReentrantLock
是JDK实现的。
性能
大致相当。
等待可中断
- 持有锁的线程长期不释放锁,等待线程可以放弃等待。
- ReentrantLock可中断,synchronized不可中断。
公平锁
公平锁:多个线程等待同一个锁时,按照申请的先后顺序获得锁。
- synchronized是非公平的。
- ReentrantLock可以是公平的,也可是非公平的。
绑定条件
一个ReentrantLock可以绑定多个Condition对象。
线程间的协作
join()
- 当一个线程调用另外一个线程的
join()
方法,则当前线程挂起,直到另外线程完成后,该线程继续运行。
wait(),notify(),notifyAll()
- 调用
wait()
,使得当前线程被挂起,直到其他线程调用同一个对象实例的notify()
或notifyAll()
来唤醒挂起的线程。 wait()
,notify()
,notifyAll()
都必须在synchronized
修饰的方法内使用,否则抛出异常。wait()
挂起线程后,当前线程获得的锁会释放。sleep()
是Thread类的静态方法,并且不会释放锁。
await(),signal(),signalAll()
- 创建
Lock
对象,利用Lock
对象生成Condition
。 - 调用
Condition
上的await()
方法使得线程等待,其他线程调用signal()
或signalAll()
方法唤醒等待的线程。
参考: