三、共享模型之管程
1、共享带来的问题
(1)临界区 Critical Section
- 一个程序运行多个线程本身是没有问题的
- 问题出在多个线程访问共享资源
- 多个线程读共享资源其实也没有问题
- 在多个线程对共享资源读写操作时发生指令交错,就会出现问题
- 一段代码块内如果存在对共享资源的多线程读写操作,称这段代码块为临界区
例如,下面代码中的临界区
static int counter = 0;
static void increment()
// 临界区
{
counter++;
}
static void decrement()
// 临界区
{
counter--;
}
(2)竞态条件 Race Condition
多个线程在临界区内执行,由于代码的执行序列不同而导致结果无法预测,称之为发生了竞态条件
2、synchronized 解决方案
(1)解决手段
为了避免临界区的竞态条件发生,有多种手段可以达到目的。
- 阻塞式的解决方案:synchronized,Lock
- 非阻塞式的解决方案:原子变量
本次课使用阻塞式的解决方案:synchronized,来解决上述问题,即俗称的**【对象锁】**,它采用互斥的方式让同一 时刻至多只有一个线程能持有【对象锁】,其它线程再想获取这个【对象锁】时就会阻塞住(blocked)。这样就能保证拥有锁 的线程可以安全的执行临界区内的代码,不用担心线程上下文切换
(2)synchronized语法
synchronized(对象) {
//临界区
}
例:
static int counter = 0;
//创建一个公共对象,作为对象锁的对象
static final Object room = new Object();
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(() -> {
for (int i = 0; i < 5000; i++) {
synchronized (room) {
counter++;
}
}
}, "t1");
Thread t2 = new Thread(() -> {
for (int i = 0; i < 5000; i++) {
synchronized (room) {
counter--;
}
}
}, "t2");
t1.start();
t2.start();
t1.join();
t2.join();
log.debug("{}",counter);
}
(3)synchronized加在方法上
-
加在成员方法上
public class Demo { //在方法上加上synchronized关键字 public synchronized void test() { } //等价于 public void test() { synchronized(this) { } } }
-
加在静态方法上
public class Demo { //在静态方法上加上synchronized关键字 public synchronized static void test() { } //等价于 public void test() { synchronized(Demo.class) { } } }
3、变量的线程安全分析
成员变量和静态变量是否线程安全?
- 如果它们没有共享,则线程安全
- 如果它们被共享了,根据它们的状态是否能够改变,又分两种情况
- 如果只有读操作,则线程安全
- 如果有读写操作,则这段代码是临界区,需要考虑线程安全
局部变量是否线程安全?
-
局部变量是线程安全的
-
但局部变量引用的对象则未必 (要看该对象
是否被共享
且被执行了读写操作)
- 如果该对象没有逃离方法的作用范围,它是线程安全的
- 如果该对象逃离方法的作用范围,需要考虑线程安全
-
局部变量是线程安全的——每个方法都在对应线程的栈中创建栈帧,不会被其他线程共享
- 如果调用的对象被共享,且执行了读写操作,则线程不安全
- 如果是局部变量,则会在堆中创建对应的对象,不会存在线程安全问题。
[
常见线程安全类
- String
- Integer
- StringBuffer
- Random
- Vector (List的线程安全实现类)
- Hashtable (Hash的线程安全实现类)
- java.util.concurrent 包下的类
这里说它们是线程安全的是指,多个线程调用它们同一个实例的某个方法时,是线程安全的
- 它们的每个方法是原子的(都被加上了synchronized)
- 但注意它们多个方法的组合不是原子的,所以可能会出现线程安全问题
不可变类线程安全性
String、Integer 等都是不可变类,因为其内部的状态不可以改变,因此它们的方法都是线程安全的
有同学或许有疑问,String 有 replace,substring 等方法【可以】改变值啊,那么这些方法又是如何保证线程安 全的呢?
这是因为这些方法的返回值都创建了一个新的对象,而不是直接改变String、Integer对象本身。
4、Monitor概念
(1)原理之Monitor
-
当线程执行到临界区代码时,如果使用了synchronized,会先查询synchronized中所指定的对象(obj)是否绑定了Monitor。
-
如果没有绑定,则会先去去与Monitor绑定,并且将Owner设为当前线程。
-
如果
已经绑定
,则会去查询该Monitor是否已经有了Owner
- 如果没有,则Owner与将当前线程绑定
- 如果有,则放入EntryList,进入阻塞状态(blocked)
-
-
当Monitor的Owner将临界区中代码执行完毕后,Owner便会被清空,此时EntryList中处于阻塞状态的线程会被叫醒并竞争,此时的竞争是非公平的
-
注意:
- 对象在使用了synchronized后与Monitor绑定时,会将对象头中的Mark Word置为Monitor指针。
- 每个对象都会绑定一个唯一的Monitor,如果synchronized中所指定的对象(obj)不同,则会绑定不同的Monitor
5、Synchronized原理进阶
对象头格式
(1)轻量级锁(用于优化Monitor这类的重量级锁)
轻量级锁使用场景:当一个对象被多个线程所访问,但访问的时间是错开的(不存在竞争),此时就可以使用轻量级锁来优化。
- 创建锁记录(Lock Record)对象,每个线程的栈帧都会包含一个锁记录对象,内部可以存储锁定对象的mark word(不再一开始就使用Monitor)
- 让锁记录中的Object reference指向锁对象(Object),并尝试用cas去替换Object中的mark word,将此mark word放入lock record中保存
- 如果cas替换成功,则将Object的对象头替换为锁记录的地址和状态 00(轻量级锁状态),并由该线程给对象加锁
(2)锁膨胀
- 如果一个线程在给一个对象加轻量级锁时,cas替换操作失败(因为此时其他线程已经给对象加了轻量级锁),此时该线程就会进入锁膨胀过程
-
此时便会给对象加上重量级锁(使用Monitor)
(3)自旋优化
重量级锁竞争时,还可以使用自选来优化,如果当前线程在自旋成功(使用锁的线程退出了同步块,释放了锁),这时就可以避免线程进入阻塞状态。
- 第一种情况
- 第二种情况
(4)偏向锁(用于优化轻量级锁重入)
轻量级锁在没有竞争时,每次重入(该线程执行的方法中再次锁住该对象)操作仍需要cas替换操作,这样是会使性能降低的。
所以引入了偏向锁对性能进行优化:在第一次cas时会将线程的ID写入对象的Mark Word中。此后发现这个线程ID就是自己的,就表示没有竞争,就不需要再次cas,以后只要不发生竞争,这个对象就归该线程所有。
偏向状态
- Normal:一般状态,没有加任何锁,前面62位保存的是对象的信息,最后2位为状态(01),倒数第三位表示是否使用偏向锁(未使用:0)
- Biased:偏向状态,使用偏向锁,前面54位保存的当前线程的ID,最后2位为状态(01),倒数第三位表示是否使用偏向锁(使用:1)
- Lightweight:使用轻量级锁,前62位保存的是锁记录的指针,最后两位为状态(00)
- Heavyweight:使用重量级锁,前62位保存的是Monitor的地址指针,后两位为状态(10)
- 如果开启了偏向锁(默认开启),在创建对象时,对象的Mark Word后三位应该是101
- 但是偏向锁默认是有延迟的,不会再程序一启动就生效,而是会在程序运行一段时间(几秒之后),才会对创建的对象设置为偏向状态
- 如果没有开启偏向锁,对象的Mark Word后三位应该是001
撤销偏向
以下几种情况会使对象的偏向锁失效
- 调用对象的hashCode方法
- 多个线程使用该对象
- 调用了wait/notify方法(调用wait方法会导致锁膨胀而使用重量级锁)
(5)批量重偏向
- 如果对象虽然被多个线程访问,但是线程间不存在竞争,这时偏向T1的对象仍有机会重新偏向T2
- 重偏向会重置Thread ID
- 当撤销超过20次后(超过阈值),JVM会觉得是不是偏向错了,这时会在给对象加锁时,重新偏向至加锁线程。
(6)批量撤销
当撤销偏向锁的阈值超过40以后,就会将整个类的对象都改为不可偏向的
6、Wait/Notify
(1)原理
-
锁对象调用wait方法(obj.wait),就会使当前线程进入WaitSet中,变为WAITING状态。
-
处于BLOCKED和WAITING状态的线程都为
阻塞
状态,CPU都不会分给他们时间片。但是有所区别:
- BLOCKED状态的线程是在竞争对象时,发现Monitor的Owner已经是别的线程了,此时就会进入EntryList中,并处于BLOCKED状态
- WAITING状态的线程是获得了对象的锁,但是自身因为某些原因需要进入阻塞状态时,锁对象调用了wait方法而进入了WaitSet中,处于WAITING状态
-
BLOCKED状态的线程会在锁被释放的时候被唤醒,但是处于WAITING状态的线程只有被锁对象调用了notify方法(obj.notify/obj.notifyAll),才会被唤醒。
注:只有当对象被锁以后,才能调用wait和notify方法
public class Test1 {
final static Object LOCK = new Object();
public static void main(String[] args) throws InterruptedException {
//只有在对象被锁住后才能调用wait方法
synchronized (LOCK) {
LOCK.wait();
}
}
}
(2)Wait与Sleep的区别
不同点
- Sleep是Thread类的静态方法,Wait是Object的方法,Object又是所有类的父类,所以所有类都有Wait方法。
- Sleep在阻塞的时候不会释放锁,而Wait在阻塞的时候会释放锁
- Sleep不需要与synchronized一起使用,而Wait需要与synchronized一起使用(对象被锁以后才能使用)
相同点
- 阻塞状态都为TIMED_WAITING
(3)优雅地使用wait/notify
什么时候适合使用wait
- 当线程不满足某些条件,需要暂停运行时,可以使用wait。这样会将对象的锁释放,让其他线程能够继续运行。如果此时使用sleep,会导致所有线程都进入阻塞,导致所有线程都没法运行,直到当前线程sleep结束后,运行完毕,才能得到执行。
使用wait/notify需要注意什么
- 当有多个线程在运行时,对象调用了wait方法,此时这些线程都会进入WaitSet中等待。如果这时使用了notify方法,可能会造成虚假唤醒(唤醒的不是满足条件的等待线程),这时就需要使用notifyAll方法
synchronized (LOCK) {
while(//不满足条件,一直等待,避免虚假唤醒) {
LOCK.wait();
}
//满足条件后再运行
}
synchronized (LOCK) {
//唤醒所有等待线程
LOCK.notifyAll();
}
7、模式之保护性暂停
(1)定义
(2)举例
public class Test2 {
public static void main(String[] args) {
String hello = "hello thread!";
Guarded guarded = new Guarded();
new Thread(()->{
System.out.println("想要得到结果");
synchronized (guarded) {
System.out.println("结果是:"+guarded.getResponse());
}
System.out.println("得到结果");
}).start();
new Thread(()->{
System.out.println("设置结果");
synchronized (guarded) {
guarded.setResponse(hello);
}
}).start();
}
}
class Guarded {
/**
* 要返回的结果
*/
private Object response;
//优雅地使用wait/notify
public Object getResponse() {
//如果返回结果为空就一直等待,避免虚假唤醒
synchronized (this) {
while(response == null) {
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
return response;
}
}
public void setResponse(Object response)
synchronized (this) {
this.response = response;
//唤醒休眠的线程
this.notifyAll();
}
}
@Override
public String toString() {
return "Guarded{" +
"response=" + response +
'}';
}
}
带超时判断的暂停
public Object getResponse(long time) {
synchronized (this) {
//获取开始时间
long currentTime = System.currentTimeMillis();
//用于保存已经等待了的时间
long passedTime = 0;
while(response == null) {
//看经过的时间-开始时间是否超过了指定时间
long waitTime = time -passedTime;
if(waitTime <= 0) {
break;
}
try {
//等待剩余时间
this.wait(waitTime);
} catch (InterruptedException e) {
e.printStackTrace();
}
//获取当前时间
passedTime = System.currentTimeMillis()-currentTime
}
}
return response;
}
(3)join源码——使用保护性暂停模式
public final synchronized void join(long millis)
throws InterruptedException {
long base = System.currentTimeMillis();
long now = 0;
if (millis < 0) {
throw new IllegalArgumentException("timeout value is negative");
}
if (millis == 0) {
while (isAlive()) {
wait(0);
}
} else {
while (isAlive()) {
long delay = millis - now;
if (delay <= 0) {
break;
}
wait(delay);
now = System.currentTimeMillis() - base;
}
}
}
8、park/unpark
(1)基本使用
park/unpark都是LockSupport类中的的方法
//暂停线程运行
LockSupport.park;
//恢复线程运行
LockSupport.unpark(thread);Copy
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(()-> {
System.out.println("park");
//暂停线程运行
LockSupport.park();
System.out.println("resume");
}, "t1");
thread.start();
Thread.sleep(1000);
System.out.println("unpark");
//恢复线程运行
LockSupport.unpark(thread);
}
(2)特点
与wait/notify的区别
- wait,notify 和 notifyAll 必须配合Object Monitor一起使用,而park,unpark不必
- park ,unpark 是以线程为单位来阻塞和唤醒线程,而 notify 只能随机唤醒一个等待线程,notifyAll 是唤醒所有等待线程,就不那么精确
- park & unpark 可以先 unpark,而 wait & notify 不能先 notify
- park不会释放锁,而wait会释放锁
(3)原理
每个线程都有一个自己的Park对象,并且该对象**_counter, _cond,__mutex**组成
-
先调用park再调用unpark时
- 先调用unpark,再调用park
- 调用unpark
- 会将counter设置为1(运行时0)
- 调用park方法
- 查看counter是否为0
- 因为unpark已经把counter设置为1,所以此时将counter设置为0,但不放入阻塞队列cond中
- 调用unpark
9、线程中的状态转换
情况一:NEW –> RUNNABLE
- 当调用了t.start()方法时,由 NEW –> RUNNABLE
情况二: RUNNABLE <–> WAITING
- 当调用了t 线程用 synchronized(obj) 获取了对象锁后
- 调用 obj.wait() 方法时,t 线程从 RUNNABLE –> WAITING
- 调用 obj.notify() , obj.notifyAll() , t.interrupt() 时
- 竞争锁成功,t 线程从 WAITING –> RUNNABLE
- 竞争锁失败,t 线程从 WAITING –> BLOCKED
情况三:RUNNABLE <–> WAITING
-
当前线程
调用 t.join() 方法时,当前线程从 RUNNABLE –> WAITING
- 注意是当前线程在t 线程对象的监视器上等待
-
t 线程运行结束,或调用了当前线程的 interrupt() 时,当前线程从 WAITING –> RUNNABLE
情况四: RUNNABLE <–> WAITING
- 当前线程调用 LockSupport.park() 方法会让当前线程从 RUNNABLE –> WAITING
- 调用 LockSupport.unpark(目标线程) 或调用了线程 的 interrupt() ,会让目标线程从 WAITING –> RUNNABLE
情况五: RUNNABLE <–> TIMED_WAITING
t 线程用 synchronized(obj) 获取了对象锁后
- 调用 obj.wait(long n) 方法时,t 线程从 RUNNABLE –> TIMED_WAITING
- t 线程等待时间超过了 n 毫秒,或调用 obj.notify() , obj.notifyAll() , t.interrupt() 时
- 竞争锁成功,t 线程从 TIMED_WAITING –> RUNNABLE
- 竞争锁失败,t 线程从 TIMED_WAITING –> BLOCKED
情况六:RUNNABLE <–> TIMED_WAITING
-
当前线程调用 t.join
(long n
) 方法时,当前线程从 RUNNABLE –> TIMED_WAITING
- 注意是当前线程在t 线程对象的监视器上等待
-
当前线程等待时间超过了 n 毫秒,或t 线程运行结束,或调用了当前线程的 interrupt() 时,当前线程从 TIMED_WAITING –> RUNNABLE
情况七:RUNNABLE <–> TIMED_WAITING
- 当前线程调用 Thread.sleep(long n) ,当前线程从 RUNNABLE –> TIMED_WAITING
- 当前线程等待时间超过了 n 毫秒,当前线程从 TIMED_WAITING –> RUNNABLE
情况八:RUNNABLE <–> TIMED_WAITING
- 当前线程调用 LockSupport.parkNanos(long nanos) 或 LockSupport.parkUntil(long millis) 时,当前线 程从 RUNNABLE –> TIMED_WAITING
- 调用 LockSupport.unpark(目标线程) 或调用了线程 的 interrupt() ,或是等待超时,会让目标线程从 TIMED_WAITING–> RUNNABLE
情况九:RUNNABLE <–> BLOCKED
- t 线程用 synchronized(obj) 获取了对象锁时如果竞争失败,从 RUNNABLE –> BLOCKED
- 持 obj 锁线程的同步代码块执行完毕,会唤醒该对象上所有 BLOCKED 的线程重新竞争,如果其中 t 线程竞争 成功,从 BLOCKED –> RUNNABLE ,其它失败的线程仍然 BLOCKED
情况十: RUNNABLE <–> TERMINATED
当前线程所有代码运行完毕,进入 TERMINATED
10、多把锁
将锁的粒度细分
class BigRoom {
//额外创建对象来作为锁
private final Object studyRoom = new Object();
private final Object bedRoom = new Object();
}
11、活跃性
(1)定义
因为某种原因,使得代码一直无法执行完毕,这样的现象叫做活跃性
(2)死锁
有这样的情况:一个线程需要同时获取多把锁,这时就容易发生死锁
如:t1线程获得A对象 锁,接下来想获取B对象的锁t2线程获得B对象锁,接下来想获取A对象的锁
public static void main(String[] args) {
final Object A = new Object();
final Object B = new Object();
new Thread(()->{
synchronized (A) {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (B) {
}
}
}).start();
new Thread(()->{
synchronized (B) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (A) {
}
}
}).start();
}
发生死锁的必要条件
- 互斥条件
- 在一段时间内,一种资源只能被一个进程所使用
- 请求和保持条件
- 进程已经拥有了至少一种资源,同时又去申请其他资源。因为其他资源被别的进程所使用,该进程进入阻塞状态,并且不释放自己已有的资源
- 不可抢占条件
- 进程对已获得的资源在未使用完成前不能被强占,只能在进程使用完后自己释放
- 循环等待条件
- 发生死锁时,必然存在一个进程——资源的循环链。
定位死锁的方法
-
jps+jstack ThreadID
F:\Thread_study>jps 20672 RemoteMavenServer36 22880 Jps 4432 Launcher 5316 Test5 20184 KotlinCompileDaemon 11132 F:\Thread_study>jstack 5316Copy
-
打印的结果
//找到一个java级别的死锁 Found one Java-level deadlock: ============================= "Thread-1": waiting to lock monitor 0x0000000017f40de8 (object 0x00000000d6188880, a java.lang.Object), which is held by "Thread-0" "Thread-0": waiting to lock monitor 0x0000000017f43678 (object 0x00000000d6188890, a java.lang.Object), which is held by "Thread-1"Copy
-
jconsole检测死锁
哲学家就餐问题
避免死锁的方法
在线程使用锁对象时**,顺序加锁**即可避免死锁
(3)活锁
活锁出现在两个线程互相改变对方的结束条件,后谁也无法结束。
避免活锁的方法
在线程执行时,中途给予不同的间隔时间即可。
死锁与活锁的区别
- 死锁是因为线程互相持有对象想要的锁,并且都不释放,最后到时线程阻塞,停止运行的现象。
- 活锁是因为线程间修改了对方的结束条件,而导致代码一直在运行,却一直运行不完的现象。
(4)饥饿
某些线程因为优先级太低,导致一直无法获得资源的现象。
在使用顺序加锁时,可能会出现饥饿现象
12、ReentrantLock
和synchronized相比具有的的特点
- 可中断
- 可以设置超时时间
- 可以设置为公平锁 (先到先得)
- 支持多个条件变量( 具有多个waitset)
基本语法
//获取ReentrantLock对象
private ReentrantLock lock = new ReentrantLock();
//加锁
lock.lock();
try {
//需要执行的代码
}finally {
//释放锁
lock.unlock();
}
可重入
- 可重入是指同一个线程如果首次获得了这把锁,那么因为它是这把锁的拥有者,因此有权利再次获取这把锁
- 如果是不可重入锁,那么第二次获得锁时,自己也会被锁挡住
可打断
如果某个线程处于阻塞状态,可以调用其interrupt方法让其停止阻塞,获得锁失败
简而言之就是:处于阻塞状态的线程,被打断了就不用阻塞了,直接停止运行
public static void main(String[] args) {
ReentrantLock lock = new ReentrantLock();
Thread t1 = new Thread(()-> {
try {
//加锁,可打断锁
lock.lockInterruptibly();
} catch (InterruptedException e) {
e.printStackTrace();
//被打断,返回,不再向下执行
return;
}finally {
//释放锁
lock.unlock();
}
});
lock.lock();
try {
t1.start();
Thread.sleep(1000);
//打断
t1.interrupt();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
锁超时
使用lock.tryLock方法会返回获取锁是否成功。如果成功则返回true,反之则返回false。
并且tryLock方法可以指定等待时间,参数为:tryLock(long timeout, TimeUnit unit), 其中timeout为最长等待时间,TimeUnit为时间单位
简而言之就是:获取失败了、获取超时了或者被打断了,不再阻塞,直接停止运行
不设置等待时间
public static void main(String[] args) {
ReentrantLock lock = new ReentrantLock();
Thread t1 = new Thread(()-> {
//未设置等待时间,一旦获取失败,直接返回false
if(!lock.tryLock()) {
System.out.println("获取失败");
//获取失败,不再向下执行,返回
return;
}
System.out.println("得到了锁");
lock.unlock();
});
lock.lock();
try{
t1.start();
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
设置等待时间
public static void main(String[] args) {
ReentrantLock lock = new ReentrantLock();
Thread t1 = new Thread(()-> {
try {
//判断获取锁是否成功,最多等待1秒
if(!lock.tryLock(1, TimeUnit.SECONDS)) {
System.out.println("获取失败");
//获取失败,不再向下执行,直接返回
return;
}
} catch (InterruptedException e) {
e.printStackTrace();
//被打断,不再向下执行,直接返回
return;
}
System.out.println("得到了锁");
//释放锁
lock.unlock();
});
lock.lock();
try{
t1.start();
//打断等待
t1.interrupt();
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
公平锁
在线程获取锁失败,进入阻塞队列时,先进入的会在锁被释放后先获得锁。这样的获取方式就是公平的。
//默认是不公平锁,需要在创建时指定为公平锁
ReentrantLock lock = new ReentrantLock(true);
条件变量
synchronized 中也有条件变量,就是我们讲原理时那个 waitSet 休息室,当条件不满足时进入waitSet 等待
ReentrantLock 的条件变量比 synchronized 强大之处在于,它是支持多个条件变量的,这就好比
- synchronized 是那些不满足条件的线程都在一间休息室等消息
- 而 ReentrantLock 支持多间休息室,有专门等烟的休息室、专门等早餐的休息室、唤醒时也是按休息室来唤 醒
使用要点:
- await 前需要获得锁
- await 执行后,会释放锁,进入 conditionObject 等待
- await 的线程被唤醒(或打断、或超时)取重新竞争 lock 锁
- 竞争 lock 锁成功后,从 await 后继续执
static Boolean judge = false;
public static void main(String[] args) throws InterruptedException {
ReentrantLock lock = new ReentrantLock();
//获得条件变量
Condition condition = lock.newCondition();
new Thread(()->{
lock.lock();
try{
while(!judge) {
System.out.println("不满足条件,等待...");
//等待
condition.await();
}
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
System.out.println("执行完毕!");
lock.unlock();
}
}).start();
new Thread(()->{
lock.lock();
try {
Thread.sleep(1);
judge = true;
//释放
condition.signal();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}).start();
}
通过Lock与AQS实现可重入锁
public class MyLock implements Lock {
private static class Sync extends AbstractQueuedSynchronizer {
@Override
protected boolean tryAcquire(int arg) {
if (getExclusiveOwnerThread() == null) {
if (compareAndSetState(0, 1)) {
setExclusiveOwnerThread(Thread.currentThread());
return true;
}
return false;
}
if (getExclusiveOwnerThread() == Thread.currentThread()) {
int state = getState();
compareAndSetState(state, state + 1);
return true;
}
return false;
}
@Override
protected boolean tryRelease(int arg) {
if (getState() <= 0) {
throw new IllegalMonitorStateException();
}
if (getExclusiveOwnerThread() != Thread.currentThread()) {
throw new IllegalMonitorStateException();
}
int state = getState();
if (state == 1) {
setExclusiveOwnerThread(null);
compareAndSetState(state, 0);
} else {
compareAndSetState(state, state - 1);
}
return true;
}
@Override
protected boolean isHeldExclusively() {
return getState() >= 1;
}
public Condition newCondition() {
return new ConditionObject();
}
}
Sync sync = new Sync();
@Override
public void lock() {
sync.acquire(1);
}
@Override
public void lockInterruptibly() throws InterruptedException {
sync.acquireInterruptibly(1);
}
@Override
public boolean tryLock() {
return sync.tryAcquire(1);
}
@Override
public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
return sync.tryAcquireNanos(1, time);
}
@Override
public void unlock() {
sync.release(1);
}
@Override
public Condition newCondition() {
return sync.newCondition();
}
}
class Main {
static int num = 0;
public static void main(String[] args) throws InterruptedException, IOException {
MyLock lock = new MyLock();
Object syncLock = new Object();
Thread t1 = new Thread(() -> {
for (int i = 0; i < 10000; i++) {
lock.lock();
try {
lock.lock();
try {
lock.lock();
try {
num++;
} finally {
lock.unlock();
}
} finally {
lock.unlock();
}
} finally {
lock.unlock();
}
}
});
Thread t2 = new Thread(() -> {
for (int i = 0; i < 10000; i++) {
lock.lock();
try {
lock.lock();
try {
lock.lock();
try {
num--;
} finally {
lock.unlock();
}
} finally {
lock.unlock();
}
} finally {
lock.unlock();
}
}
});
t1.start();
t2.start();
t1.join();
t2.join();
int x = 0;
}
}
13、同步模式之顺序控制
Wait/Notify版本
static final Object LOCK = new Object();
//判断先执行的内容是否执行完毕
static Boolean judge = false;
public static void main(String[] args) {
new Thread(()->{
synchronized (LOCK) {
while (!judge) {
try {
LOCK.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("2");
}
}).start();
new Thread(()->{
synchronized (LOCK) {
System.out.println("1");
judge = true;
//执行完毕,唤醒所有等待线程
LOCK.notifyAll();
}
}).start();
}
交替输出
wait/notify版本
public class Test4 {
static Symbol symbol = new Symbol();
public static void main(String[] args) {
new Thread(()->{
symbol.run("a", 1, 2);
}).start();
new Thread(()->{
symbol.run("b", 2, 3);
}).start();
symbol.run("c", 3, 1);
new Thread(()->{
}).start();
}
}
class Symbol {
public synchronized void run(String str, int flag, int nextFlag) {
for(int i=0; i<loopNumber; i++) {
while(flag != this.flag) {
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(str);
//设置下一个运行的线程标记
this.flag = nextFlag;
//唤醒所有线程
this.notifyAll();
}
}
/**
* 线程的执行标记, 1->a 2->b 3->c
*/
private int flag = 1;
private int loopNumber = 5;
public int getFlag() {
return flag;
}
public void setFlag(int flag) {
this.flag = flag;
}
public int getLoopNumber() {
return loopNumber;
}
public void setLoopNumber(int loopNumber) {
this.loopNumber = loopNumber;
}
}
await/signal版本
public class Test5 {
static AwaitSignal awaitSignal = new AwaitSignal();
static Condition conditionA = awaitSignal.newCondition();
static Condition conditionB = awaitSignal.newCondition();
static Condition conditionC = awaitSignal.newCondition();
public static void main(String[] args) {
new Thread(()->{
awaitSignal.run("a", conditionA, conditionB);
}).start();
new Thread(()->{
awaitSignal.run("b", conditionB, conditionC);
}).start();
new Thread(()->{
awaitSignal.run("c", conditionC, conditionA);
}).start();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
awaitSignal.lock();
try {
//唤醒一个等待的线程
conditionA.signal();
}finally {
awaitSignal.unlock();
}
}
}
class AwaitSignal extends ReentrantLock{
public void run(String str, Condition thisCondition, Condition nextCondition) {
for(int i=0; i<loopNumber; i++) {
lock();
try {
//全部进入等待状态
thisCondition.await();
System.out.print(str);
nextCondition.signal();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
unlock();
}
}
}
private int loopNumber=5;
public int getLoopNumber() {
return loopNumber;
}
public void setLoopNumber(int loopNumber) {
this.loopNumber = loopNumber;
}
}
异步模式
传统版
异步模式之生产者/消费者:
class ShareData {
private int number = 0;
private Lock lock = new ReentrantLock();
private Condition condition = lock.newCondition();
public void increment() throws Exception{
// 同步代码块,加锁
lock.lock();
try {
// 判断 防止虚假唤醒
while(number != 0) {
// 等待不能生产
condition.await();
}
// 干活
number++;
System.out.println(Thread.currentThread().getName() + "\t " + number);
// 通知 唤醒
condition.signalAll();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void decrement() throws Exception{
// 同步代码块,加锁
lock.lock();
try {
// 判断 防止虚假唤醒
while(number == 0) {
// 等待不能消费
condition.await();
}
// 干活
number--;
System.out.println(Thread.currentThread().getName() + "\t " + number);
// 通知 唤醒
condition.signalAll();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
public class TraditionalProducerConsumer {
public static void main(String[] args) {
ShareData shareData = new ShareData();
// t1线程,生产
new Thread(() -> {
for (int i = 0; i < 5; i++) {
shareData.increment();
}
}, "t1").start();
// t2线程,消费
new Thread(() -> {
for (int i = 0; i < 5; i++) {
shareData.decrement();
}
}, "t2").start();
}
}
改进版
异步模式之生产者/消费者:
- 消费队列可以用来平衡生产和消费的线程资源,不需要产生结果和消费结果的线程一一对应
- 生产者仅负责产生结果数据,不关心数据该如何处理,而消费者专心处理结果数据
- 消息队列是有容量限制的,满时不会再加入数据,空时不会再消耗数据
- JDK 中各种阻塞队列,采用的就是这种模式
public class demo {
public static void main(String[] args) {
MessageQueue queue = new MessageQueue(2);
for (int i = 0; i < 3; i++) {
int id = i;
new Thread(() -> {
queue.put(new Message(id,"值"+id));
}, "生产者" + i).start();
}
new Thread(() -> {
while (true) {
try {
Thread.sleep(1000);
Message message = queue.take();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"消费者").start();
}
}
//消息队列类,Java间线程之间通信
class MessageQueue {
private LinkedList<Message> list = new LinkedList<>();//消息的队列集合
private int capacity;//队列容量
public MessageQueue(int capacity) {
this.capacity = capacity;
}
//获取消息
public Message take() {
//检查队列是否为空
synchronized (list) {
while (list.isEmpty()) {
try {
sout(Thread.currentThread().getName() + ":队列为空,消费者线程等待");
list.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//从队列的头部获取消息返回
Message message = list.removeFirst();
sout(Thread.currentThread().getName() + ":已消费消息--" + message);
list.notifyAll();
return message;
}
}
//存入消息
public void put(Message message) {
synchronized (list) {
//检查队列是否满
while (list.size() == capacity) {
try {
sout(Thread.currentThread().getName()+":队列为已满,生产者线程等待");
list.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//将消息加入队列尾部
list.addLast(message);
sout(Thread.currentThread().getName() + ":已生产消息--" + message);
list.notifyAll();
}
}
}
final class Message {
private int id;
private Object value;
//get set
}
阻塞队列
public static void main(String[] args) {
ExecutorService consumer = Executors.newFixedThreadPool(1);
ExecutorService producer = Executors.newFixedThreadPool(1);
BlockingQueue<Integer> queue = new SynchronousQueue<>();
producer.submit(() -> {
try {
System.out.println("生产...");
Thread.sleep(1000);
queue.put(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
consumer.submit(() -> {
try {
System.out.println("等待消费...");
Integer result = queue.take();
System.out.println("结果为:" + result);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
}
14、ThreadLocal
简介
ThreadLocal是JDK包提供的,它提供了线程本地变量,也就是如果你创建了一个ThreadLocal变量,那么访问这个变量的每个线程都会有这个变量的一个本地副本。当多个线程操作这个变量时,实际操作的是自己本地内存里面的变量,从而避免了线程安全问题
使用
public class ThreadLocalStudy {
public static void main(String[] args) {
// 创建ThreadLocal变量
ThreadLocal<String> stringThreadLocal = new ThreadLocal<>();
ThreadLocal<User> userThreadLocal = new ThreadLocal<>();
// 创建两个线程,分别使用上面的两个ThreadLocal变量
Thread thread1 = new Thread(()->{
// stringThreadLocal第一次赋值
stringThreadLocal.set("thread1 stringThreadLocal first");
// stringThreadLocal第二次赋值
stringThreadLocal.set("thread1 stringThreadLocal second");
// userThreadLocal赋值
userThreadLocal.set(new User("Nyima", 20));
// 取值
System.out.println(stringThreadLocal.get());
System.out.println(userThreadLocal.get());
// 移除
userThreadLocal.remove();
System.out.println(userThreadLocal.get());
});
Thread thread2 = new Thread(()->{
// stringThreadLocal第一次赋值
stringThreadLocal.set("thread2 stringThreadLocal first");
// stringThreadLocal第二次赋值
stringThreadLocal.set("thread2 stringThreadLocal second");
// userThreadLocal赋值
userThreadLocal.set(new User("Hulu", 20));
// 取值
System.out.println(stringThreadLocal.get());
System.out.println(userThreadLocal.get());
});
// 启动线程
thread1.start();
thread2.start();
}
}
class User {
String name;
int age;
public User(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
运行结果
thread1 stringThreadLocal second
thread2 stringThreadLocal second
User{name='Nyima', age=20}
User{name='Hulu', age=20}
null
从运行结果可以看出
- 每个线程中的ThreadLocal变量是每个线程私有的,而不是共享的
- 从线程1和线程2的打印结果可以看出
- ThreadLocal其实就相当于其泛型类型的一个变量,只不过是每个线程私有的
- stringThreadLocal被赋值了两次,保存的是最后一次赋值的结果
- ThreadLocal可以进行以下几个操作
- set 设置值
- get 取出值
- remove 移除值
原理
Thread中的threadLocals
public class Thread implements Runnable {
...
ThreadLocal.ThreadLocalMap threadLocals = null;
// 放在后面说
ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;
...
}Copy
static class ThreadLocalMap {
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
可以看出Thread类中有一个threadLocals和一个inheritableThreadLocals,它们都是ThreadLocalMap类型的变量,而ThreadLocalMap是一个定制化的Hashmap。在默认情况下,每个线程中的这两个变量都为null。此处先讨论threadLocals,inheritableThreadLocals放在后面讨论
ThreadLocal中的方法
set方法
public void set(T value) {
// 获取当前线程
Thread t = Thread.currentThread();
// 获得ThreadLocalMap对象
// 这里的get会返回Thread类中的threadLocals
ThreadLocalMap map = getMap(t);
// 判断map是否已经创建,没创建就创建并放入值,创建了就直接放入
if (map != null)
// ThreadLocal自生的引用作为key,传入的值作为value
map.set(this, value);
else
createMap(t, value);
}
如果未创建
void createMap(Thread t, T firstValue) {
// 创建的同时设置想放入的值
// hreadLocal自生的引用作为key,传入的值作为value
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
get方法
public T get() {
// 获取当前线程
Thread t = Thread.currentThread();
// 获取当前线程的threadLocals变量
ThreadLocalMap map = getMap(t);
// 判断threadLocals是否被初始化了
if (map != null) {
// 已经初始化则直接返回
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
// 否则就创建threadLocals
return setInitialValue();
}Copy
private T setInitialValue() {
// 这个方法返回是null
T value = initialValue();
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
// 无论map创建与否,最终value的值都为null
if (map != null)
map.set(this, value);
else
createMap(t, value);
return value;
}Copy
protected T initialValue() {
return null;
}
remove方法
public void remove() {
ThreadLocalMap m = getMap(Thread.currentThread());
if (m != null)
// 如果threadLocals已经被初始化,则移除
m.remove(this);
}
总结
在每个线程内部都有一个名为threadLocals的成员变量,该变量的类型为HashMap,其中key为我们定义的ThreadLocal变量的this引用,value则为我们使用set方法设置的值。每个线程的本地变量存放在线程自己的内存变量threadLocals中
只有当前线程第一次调用ThreadLocal的set或者get方法时才会创建threadLocals(inheritableThreadLocals也是一样)。其实每个线程的本地变量不是存放在ThreadLocal实例里面,而是存放在调用线程的threadLocals变量里面
15、InheritableThreadLocal
简介
从ThreadLocal的源码可以看出,无论是set、get、还是remove,都是相对于当前线程操作的
Thread.currentThread()
所以ThreadLocal无法从父线程传向子线程,所以InheritableThreadLocal出现了,它能够让父线程中ThreadLocal的值传给子线程。
也就是从main所在的线程,传给thread1或thread2
使用
public class Demo1 {
public static void main(String[] args) {
ThreadLocal<String> stringThreadLocal = new ThreadLocal<>();
InheritableThreadLocal<String> stringInheritable = new InheritableThreadLocal<>();
// 主线程赋对上面两个变量进行赋值
stringThreadLocal.set("this is threadLocal");
stringInheritable.set("this is inheritableThreadLocal");
// 创建线程
Thread thread1 = new Thread(()->{
// 获得ThreadLocal中存放的值
System.out.println(stringThreadLocal.get());
// 获得InheritableThreadLocal存放的值
System.out.println(stringInheritable.get());
});
thread1.start();
}
}
运行结果
null
this is inheritableThreadLocal
可以看出InheritableThreadLocal的值成功从主线程传入了子线程,而ThreadLocal则没有
原理
InheritableThreadLocal
public class InheritableThreadLocal<T> extends ThreadLocal<T> {
// 传入父线程中的一个值,然后直接返回
protected T childValue(T parentValue) {
return parentValue;
}
// 返回传入线程的inheritableThreadLocals
// Thread中有一个inheritableThreadLocals变量
// ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;
ThreadLocalMap getMap(Thread t) {
return t.inheritableThreadLocals;
}
// 创建一个inheritableThreadLocals
void createMap(Thread t, T firstValue) {
t.inheritableThreadLocals = new ThreadLocalMap(this, firstValue);
}
}
由如上代码可知,InheritableThreadLocal继承了ThreadLocal,并重写了三个方法。InheritableThreadLocal重写了createMap方法,那么现在当第一次调用set方法时,创建的是当前线程的inheritableThreadLocals变量的实例而不再是threadLocals。当调用getMap方法获取当前线程内部的map变量时,获取的是inheritableThreadLocals而不再是threadLocals
childValue(T parentValue)方法的调用
在主函数运行时,会调用Thread的默认构造函数(创建主线程,也就是父线程),所以我们先看看Thread的默认构造函数
public Thread() {
init(null, null, "Thread-" + nextThreadNum(), 0);
}Copy
private void init(ThreadGroup g, Runnable target, String name,
long stackSize, AccessControlContext acc,
boolean inheritThreadLocals) {
...
// 获得当前线程的,在这里是主线程
Thread parent = currentThread();
...
// 如果父线程的inheritableThreadLocals存在
// 我们在主线程中调用set和get时,会创建inheritableThreadLocals
if (inheritThreadLocals && parent.inheritableThreadLocals != null)
// 设置子线程的inheritableThreadLocals
this.inheritableThreadLocals =
ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
/* Stash the specified stack size in case the VM cares */
this.stackSize = stackSize;
/* Set thread ID */
tid = nextThreadID();
}Copy
static ThreadLocalMap createInheritedMap(ThreadLocalMap parentMap) {
return new ThreadLocalMap(parentMap);
}
在createInheritedMap内部使用父线程的inheritableThreadLocals变量作为构造函数创建了一个新的ThreadLocalMap变量,然后赋值给了子线程的inheritableThreadLocals变量
private ThreadLocalMap(ThreadLocalMap parentMap) {
Entry[] parentTable = parentMap.table;
int len = parentTable.length;
setThreshold(len);
table = new Entry[len];
for (int j = 0; j < len; j++) {
Entry e = parentTable[j];
if (e != null) {
@SuppressWarnings("unchecked")
ThreadLocal<Object> key = (ThreadLocal<Object>) e.get();
if (key != null) {
// 这里调用了 childValue 方法
// 该方法会返回parent的值
Object value = key.childValue(e.value);
Entry c = new Entry(key, value);
int h = key.threadLocalHashCode & (len - 1);
while (table[h] != null)
h = nextIndex(h, len);
table[h] = c;
size++;
}
}
}
}
在该构造函数内部把父线程的inheritableThreadLocals成员变量的值复制到新的ThreadLocalMap对象中
总结
InheritableThreadLocal类通过重写getMap和createMap,让本地变量保存到了具体线程的inheritableThreadLocals变量里面,那么线程在通过InheritableThreadLocal类实例的set或者get方法设置变量时,就会创建当前线程的inheritableThreadLocals变量。
当父线程创建子线程时,构造函数会把父线程中inheritableThreadLocals变量里面的本地变量复制一份保存到子线程的inheritableThreadLocals变量里面。