1. wait - notify 使用方法
synchronized(lock) {
while(条件不成立) {
lock.wait();
}
// 执行任务
}
//其他线程
synchronized(lock) {
lock.notifyAll();
}
2. join 和 GuardedObject 保护性暂停模式的区别
- 保护性暂停模式:一个线程等另一个线程的结果
- join :一个线程等另一个线程的结束
3. 常见线程安全类
- String
- Integer
- StringBuffer
- Random
- Vector
- Hashtable
- java.util.concurrent 包下的类
这里说它们是线程安全的是指,多个线程调用它们同一个实例的某个方法时,是线程安全的。
4. sleep(long n) 和 wait(long n) 的区别
- sleep 是 Thread 方法,而 wait 是 Object 的方法
- sleep 不需要强制和 synchronized 配合使用,但 wait 需要
和 synchronized 一起用 - sleep 在睡眠的同时,不会释放对象锁的,但 wait 在等待的时候会释放对象锁
- wait() 方法被调用后,线程不会自动苏醒,需要别的线程调用同一个对象上的 notify()或者 notifyAll() 方法。sleep()方法执行完成后,线程会自动苏醒。
5. 线程之间的转态转换
(1) NEW ===> RUNNABLE
- 线程调用
Thread.start()
方法
(2) RUNNABLE <===> WAITING
- t 线程用 synchronized(obj) 获取了对象锁后,
调用obj.wait()
方法时,t 线程从 RUNNABLE --> WAITING。
调用obj.notify() , obj.notifyAll() , t.interrupt()
时, 若:
a. 竞争锁成功,t 线程从 WAITING --> RUNNABLE
b. 竞争锁失败,t 线程从 WAITING --> BLOCKED
- 当前线程调用另一线程
t.join()
方法时,当前线程从 RUNNABLE --> WAITING,
t 线程运行结束,或调用了当前线程的interrupt()
时,当前线程从 WAITING --> RUNNABLE
- 线程调用
LockSupport.park()
方法,从 RUNNABLE --> WAITING ;
调用LockSupport.unpark
(目标线程) 或调用了线程 的interrupt()
,会让目标线程从 WAITING --> RUNNABLE
(3) RUNNABLE <===>TIMED_WAITING
- 调用
obj.wait(long n)
方法时,t 线程从 RUNNABLE --> TIMED_WAITING ;
t 线程等待时间超过了 n 毫秒,或调用obj.notify() , obj.notifyAll() , t.interrupt()
时:
竞争锁成功,t 线程从 TIMED_WAITING --> RUNNABLE
竞争锁失败,t 线程从 TIMED_WAITING --> BLOCKED
- 当前线程调用
t.join(long n)
方法时,当前线程从 RUNNABLE --> TIMED_WAITING(注意是当前线程在t 线程对象的监视器上等待);
当前线程等待时间超过了 n 毫秒,或 t 线程运行结束,或调用了当前线程的interrupt()
时,当前线程从
TIMED_WAITING --> RUNNABLE
- 当前线程调用
Thread.sleep(long n)
,当前线程从 RUNNABLE --> TIMED_WAITING ;
当前线程等待时间超过了 n 毫秒,当前线程从 TIMED_WAITING --> RUNNABLE
- 当前线程调用
LockSupport.parkNanos(long nanos)
或LockSupport.parkUntil(long millis)
时,当前线
程从 RUNNABLE --> TIMED_WAITING ;
调用LockSupport.unpark
(目标线程) 或调用了线程 的interrupt()
,或是等待超时,会让目标线程从
TIMED_WAITING–> RUNNABLE
(4) RUNNABLE <===> BLOCKED
- t 线程用
synchronized(obj)
获取了对象锁时如果竞争失败,从 RUNNABLE --> BLOCKED ;
持有 obj 锁线程的同步代码块执行完毕,会唤醒该对象上所有 BLOCKED 的线程重新竞争,如果其中 t 线程竞争
成功,从 BLOCKED --> RUNNABLE ,其它失败的线程仍然 BLOCKED
(5) RUNNABLE <===> TERMINATED
当前线程所有代码运行完毕,进入 TERMINATED
6. 定位死锁
检测死锁可以用jps 定位进程 id,再用 jstack 定位死锁,使用 jconsole工具 :
jps 定位进程 id
7. 活锁
活锁是指线程间资源冲突激烈,引起线程不断的尝试获取资源,不断的失败。活锁有点类似于线程饥饿,虽然资源并没有被别人持有,但由于各种原因而无法得到。最常见的原因是进程组的执行顺序不合理,导致某些先需要的资源被后置。活锁和死锁的不同在于,活锁的状态是变化的,只是无法达到目的。活锁有可能在一定时间后自动解开,但死锁不能。
解决:合理分配线程组的执行顺序,交错执行。
8. ReentrantLock(对象级别)
相对于 synchronized 它具备如下特点
- 可中断
- 可以设置超时时间
- 可以设置为公平锁
- 支持多个条件变量
- 与 synchronized 一样,都支持可重入
基本用法:lock和unlock成对出现
// 获取锁,也乐意放在try块中
reentrantLock.lock();
try {
// 临界区
} finally {
// 释放锁
reentrantLock.unlock();
}
可重入
可重入是指同一个线程
如果首次获得了这把锁,那么因为它是这把锁的拥有者,因此有权利再次获取这把锁
如果是不可重入锁,那么第二次获得锁时,自己也会被锁挡住
可打断
等待锁的过程中,可以用interrupt打断等待
ReentrantLock lock = new ReentrantLock();
Thread t1 = new Thread(() -> {
log.debug("启动...");
try {
lock.lockInterruptibly();
} catch (InterruptedException e) {
e.printStackTrace();
log.debug("等锁的过程中被打断");
return;
}
try {
log.debug("获得了锁");
} finally {
lock.unlock();
}
}, "t1");
lock.lock();
log.debug("获得了锁");
t1.start();
try {
sleep(1);
t1.interrupt();
log.debug("执行打断");
} finally {
lock.unlock();
}
锁超时
线程在获取锁的过程中,如果其他线程持有的锁一直没有释放,线程不会一直死等,线程会等待一段时间,判断是否获取锁成功。
lock.trylock() //返回布尔值
lock.tryLock(long timeout, TimeUnit unit)
公平锁
ReentrantLock默认不公平
条件变量
synchronized 中也有条件变量,当条件不满足时进入 waitSet 等待
ReentrantLock 的条件变量比 synchronized 强大之处在于,它是支持多个条件变量的,这就好比synchronized 是那些不满足条件的线程都在一间休息室等消息
而 ReentrantLock 支持多间休息室
使用要点:
await 前需要获得锁
await 执行后,会释放锁,进入 conditionObject 等待
await 的线程被唤醒(或打断、或超时)取重新竞争 lock 锁
竞争 lock 锁成功后,从 await 后继续执行