callable和runnable区别
callable专为线程池设计
runnnable.run()没有返回值且不能抛出异常(抛出则视为异常就自动终止线程)
callable.call()有返回值且可以抛出异常,以供executeService.submit()方法
基础概念
线程状态
初始状态
刚创建了一个线程类,刚new了一个线程类
就绪状态
当进程目前没有运行中的线程的时候,所有就绪状态的线程都会抢夺cpu资源
让线程从执行中的状态滚回就绪中的状态
Thread.yield();
运行中
线程执行的状态
阻塞
阻塞分三种,一种是等待阻塞——其它线程都等着,一种是同步阻塞——没有获取到锁在不停死循环获取独占锁资源,一种是其它阻塞——调用sleep()睡着的线程
线程阻塞中,并且同进程内其它线程也不能获得cpu资源,必须等待此线程重新执行后,进程方可继续运行
进入synchronized同步块但还没获得锁的时候是阻塞状态,或者执行Thread.wait();方法进入阻塞状态
全线程阻塞
Thread.wait();
休眠
线程让出资源,等待手工唤醒。其他线程可抢夺cpu资源。手工唤醒后,与其他就绪状态的线程一起抢夺资源
超时休眠
线程让出资源,一定时间内休眠,其他线程可抢夺cpu资源。一定时间后,线程转换为就绪状态,与其他就绪状态的线程一起抢夺资源
实现方式
Thread.sleep(123); //123=123毫秒
唤醒方式
单线程唤醒
thread.notify();
注:如果线程不是休眠状态,调用此方法会报错java.lang.IllegalMonitorStateException
群体唤醒
thread.notifyAll();
注意
sleep不释放锁
thread.run()与thread.start()区别
start()调用thread.run方法,不需要等待线程执行完可以直接运行主线程下一行代码,是真正的多线程
run()只有在主线程执行完当前线程才会执行下一行代码,实际上还是单线程
同步队列和等待队列
同步队列
线程实现中,线程安全的实现的队列
等待队列
所有状态为等待运行的线程的队列
区别:一个是实现线程安全(线程同步)的队列,一个只是等待运行线程的队列
VarHandle
jdk1.9之后才有的特性
VarHandle是什么
一般来说,Object o=new Object(); o指向new Object();所在的内存空间,而VarHandle则是引用本身
作用
1、普通对象修改值属于原子性操作
2、比反射快——直接操作二进制码
创建以及运行线程
创建线程方式
实现runnable接口
public class SingletonTest {
private static class TestThread implements Runnable{
@Override
public void run() {
String name = Thread.currentThread().getName();
System.out.println(name);
}
}
public static void main(String[] args) {
for(int i=0;i<100;i++){
new Thread(new TestThread()).start();
}
}
}
继承thread
public class SingletonTest {
private static class TestThread extends Thread{
@Override
public void run() {
String name = Thread.currentThread().getName();
System.out.println(name);
}
}
public static void main(String[] args) {
for(int i=0;i<100;i++){
new Thread(new TestThread()).start();
}
}
}
开启线程
Thread().start()
volatile
实现方式
1、缓存一致性协议
2、禁止指令重排序(添加内存屏障)
作用
1.所有线程都可见
所有对象都放在堆的共享内存,堆有个区域是线程共享的内容。但是每个线程都有自己的独立区域(线程不可见性)。volatitle通过使用 cpu的MESI——缓存一致性协议
2.禁止指令重排序
旧版cpu
把指令放入队列并发执行,jvm编译器会把指令打散重新排序,例如:
原始代码:
b=1;
a=2;
b=3;
编译器可能把代码变为:
b=1;
b=2;
a=3;
volatile可以把上述的三个指令划分到一个内存屏障里面,让指令的执行顺序始终是
b=1;
a=2;
b=3;
Double Check Lock
用单例模式举例
public class SingletonTest {
private static volatile SingletonTest INSTANTIATE = null;
private SingletonTest() {
}
public static SingletonTest getInstance() {
if (INSTANTIATE == null) {
synchronized (SingletonTest.class) {
try {
Thread.sleep(1);
if (INSTANTIATE == null) {
//JVM中,new对象分三步:1、申请对象内存空间;2、初始化成员变量;3、把对象的内存空间指向变量。
//即:1、创建对象内存空间,并且按照jvm设置的变量初始值赋值;2、对成员变量赋值真正的初始值;3、把该空间指向外部的变量
INSTANTIATE = new SingletonTest();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
return INSTANTIATE;
}
public static void main(String[] args) {
for (int i = 0; i < 100; i++) {
new Thread(() -> {
System.out.println(SingletonTest.getInstance().hashCode());
}).start();
}
}
}
指令重排序可能引起的问题
由于new对象分为三个指令:1、创建对象内存空间;2、按照jvm设置的变量初始值赋值;3、对成员变量赋值真正的初始值;4、把该空间指向外部的变量。指令重排序后,顺序可能变成:1——>3——>2——4,如果第一步走完后,第三步也走完了(指针指向完成),这时候,线程A的资源让给了B,此时,第三步和第四步没有走,对象的成员变量初始值还是jvm规定的初始值,B拿到后直接处理,数据就出问题了。
加入volatitle的好处
禁止了上述指令的重排序,创建对象必须按照顺序走完1、2、3步后才会让出资源
**
线程池
在面向对象编程中,创建和销毁对象是很费时间的,因为创建一个对象要获取内存资源或者其它更多资源。在Java中更是如此,虚拟机将试图跟踪每一个对象,以便能够在对象销毁后进行垃圾回收。所以提高服务程序效率的一个手段就是尽可能减少创建和销毁对象的次数,特别是一些很耗资源的对象创建和销毁,这就是”池化资源”技术产生的原因。线程池顾名思义就是事先创建若干个可执行的线程放入一个池(容器)中,需要的时候从池中获取线程不用自行创建,使用完毕不需要销毁线程而是放回池中,从而减少创建和销毁线程对象的开销。
Java 5+中的Executor接口定义一个执行线程的工具。它的子类型即线程池接口是ExecutorService。要配置一个线程池是比较复杂的,尤其是对于线程池的原理不是很清楚的情况下,因此在工具类Executors面提供了一些静态工厂方法,生成一些常用的线程池,如下所示:
- newSingleThreadExecutor:创建一个单线程的线程池。这个线程池只有一个线程在工作,也就是相当于单线程串行执行所有任务。如果这个唯一的线程因为异常结束,那么会有一个新的线程来替代它。此线程池保证所有任务的执行顺序按照任务的提交顺序执行。
- newFixedThreadPool:创建固定大小的线程池。每次提交一个任务就创建一个线程,直到线程达到线程池的最大大小。线程池的大小一旦达到最大值就会保持不变,如果某个线程因为执行异常而结束,那么线程池会补充一个新线程。
- newCachedThreadPool:创建一个可缓存的线程池。如果线程池的大小超过了处理任务所需要的线程,那么就会回收部分空闲(60秒不执行任务)的线程,当任务数增加时,此线程池又可以智能的添加新线程来处理任务。此线程池不会对线程池大小做限制,线程池大小完全依赖于操作系统(或者说JVM)能够创建的最大线程大小。
- newScheduledThreadPool:创建一个大小无限的线程池。此线程池支持定时以及周期性执行任务的需求。
- newSingleThreadExecutor:创建一个单线程的线程池。此线程池支持定时以及周期性执行任务的需求。
锁释放
1、线程执行完,自动释放锁
2、lock手动释放锁
3、检测到运行时异常自动释放锁
乐观锁与悲观锁
悲观锁
在关系数据库管理系统里,悲观并发控制(又名“悲观锁”,Pessimistic Concurrency Control,缩写“PCC”)是一种并发控制的方法。它可以阻止一个事务以影响其他用户的方式来修改数据。如果一个事务执行的操作都某行数据应用了锁,那只有当这个事务把锁释放,其他事务才能够执行与该锁冲突的操作。
悲观并发控制主要用于数据争用激烈的环境,以及发生并发冲突时使用锁保护数据的成本要低于回滚事务的成本的环境中。
悲观锁,正如其名,它指的是对数据被外界(包括本系统当前的其他事务,以及来自外部系统的事务处理)修改持保守态度(悲观),因此,在整个数据处理过程中,将数据处于锁定状态。 悲观锁的实现,往往依靠数据库提供的锁机制 (也只有数据库层提供的锁机制才能真正保证数据访问的排他性,否则,即使在本系统中实现了加锁机制,也无法保证外部系统不会修改数据)在数据库中,悲观锁的流程如下: 在对任意记录进行修改前,先尝试为该记录加上排他锁(exclusive locking)。 如果加锁失败,说明该记录正在被修改,那么当前查询可能要等待或者抛出异常。 具体响应方式由开发者根据实际需要决定。 如果成功加锁,那么就可以对记录做修改,事务完成后就会解锁了。 其间如果有其他对该记录做修改或加排他锁的操作,都会等待我们解锁或直接抛出异常。MySQL InnoDB中使用悲观锁 要使用悲观锁,我们必须关闭mysql数据库的自动提交属性,因为MySQL默认使用autocommit模式,也就是说,当你执行一个更新操作后,MySQL会立刻将结果进行提交。set autocommit=0;
上面的查询语句中,我们使用了select…for update的方式,这样就通过开启排他锁的方式实现了悲观锁。此时在t_goods表中,id为1的 那条数据就被我们锁定了,其它的事务必须等本次事务提交之后才能执行。这样我们可以保证当前的数据不会被其它事务修改。 上面我们提到,使用select…for update会把数据给锁住,不过我们需要注意一些锁的级别,MySQL InnoDB默认行级锁。行级锁都是基于索引的,如果一条SQL语句用不到索引是不会使用行级锁的,会使用表级锁把整张表锁住,这点需要注意。优点与不足 悲观并发控制实际上是“先取锁再访问”的保守策略,为数据处理的安全提供了保证。但是在效率方面,处理加锁的机制会让数据库产生额外的开销,还有增加产生死锁的机会;另外,在只读型事务处理中由于不会产生冲突,也没必要使用锁,这样做只能增加系统负载;还有会降低了并行性,一个事务如果锁定了某行数据,其他事务就必须等待该事务处理完才可以处理那行数
synchronized详解
重点常识
object.notify()不释放锁
锁状态以及升级
无锁——>偏向锁——>自旋锁——>重量级锁
属性
1、可重入锁
就是已经锁上的对象可以再锁一次(或者多次)
public class TestThreadDomain {
private String name;
private Integer balance;
public synchronized String getName() {
System.out.println("读取名字——"+name);
return name;
}
public synchronized void setName(String name) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
getName()
this.name = name;
}
public Integer getBalance() {
return balance;
}
public void setBalance(Integer balance) {
this.balance = balance;
}
}
setName方法内调用了getName(),当调用setName()方法的时候,getName和setName使用的是同一把锁
sync锁的是对象而不是方法
synchronized void m(){} 等同于
void m(){
synchronized (this){
}
}
synchronized 锁静态对象,等同于锁XXX.CLASS
synchronized (this.singletonObjects) {}
一旦有一个线程进入同步块,还有其它线程访问该同步块的lock(锁)对象则会进行阻塞,但不同对象访问到同一个方法则不会进行阻塞
锁定方法和非锁定方法同时执行
对象加锁原理
sync主要是通过obj对象的头部元数据的前两位来判断是否获得锁
当一个线程进入一个对象的一个synchronized方法后,其它线程是否可进入此对象的其它方法
其他方法前是否加了synchronized关键字,如果没加,则能。
如果这个方法内部调用了wait,则可以进入其他synchronized方法。
如果其他个方法都加了synchronized关键字,并且内部没有调用wait,则不能。
如果其他方法是static,它用的同步锁是当前类的字节码,与非静态的方法不能同步,因为非静态的方法用的是this。
synchronized和java.util.concurrent.locks.Lock的异同
主要相同点:Lock能完成synchronized所实现的所有功能
主要不同点:Lock有比synchronized更精确的线程语义和更好的性能。synchronized会自动释放锁,而Lock一定要求程序员手工释放,并且必须在finally从句中释放。Lock还有更强大的功能,例如,它的tryLock方法可以非阻塞方式去拿锁。
四种用法
1、synchronized包含对象
ssynchronized (this.singletonObjects) {}
2、synchronized修饰方法
synchronized void aa(){}
一旦一个线程访问到synchronized修饰的方法,则会对其它线程进行阻塞。
3、synchronized修饰类
synchronized(SyncThread.class){}
一旦一个线程访问到synchronized修饰的类,则会对其它线程进行阻塞
4、synchronized修饰静态方法
synchronized static void aa(){}
一旦一个线程访问到该方法,则对所有线程进行阻塞
总结
如果synchronized 锁定的是非静态方法和类,则是一个对象使用一把锁,多个对象使用多把锁。
如果synchronized 锁定的是静态方盒或者类,则对类中的所有成员加锁,整个class使用同一把锁
synchronized 底层实现
jdk早期
synchronized实现重量级——每次都找系统os申请锁
后期(jdk1.5之后)改进成锁升级概念——偏向锁,有其它线程征用cpu资源——>升级为自旋锁,不停尝试获取锁,默认尝试十次,次数满了后——>升级为重量级锁直接向os申请调用cpu资源获取锁
偏向锁:第一访问某把锁的线程,在obj的头上记录本线程号
自旋锁:如果有线程征用,升级为自旋锁,尝试获取锁调用资源,默认尝试十次
重量级锁:如果尝试十次后还得不到锁,升级为重量级锁,直接向os申请调用cpu资源
自旋锁和重量级锁的使用场景
自旋锁:执行时间短、执行线程少
重量级锁:执行时间长,执行线程多
sync锁优化
锁细化
一个业务逻辑内只有极少部分的资源需要加锁
public class OptimizeLock {
private Integer count = 0;
/**
* 不建议的加锁方式
*/
public synchronized void noQptimized() {
//不需要加锁的操作
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//需要加锁的操作
count++;
//不需要加锁的操作
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
则只在需要加锁的资源上加锁即可
/**
* 优化方式1:只在需要加锁的处理逻辑上加锁,其它不加锁
*/
public void qptimized1(){
//不需要加锁的业务逻辑
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//需要加锁的操作
synchronized (this){
count++;
}
//不需要加锁的业务逻辑
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
锁的粗化
一个业务逻辑内有多个资源需要加锁
/**
* 一个业务逻辑内多个需要加锁的
*/
public void everyLockSync(){
synchronized (this){
count++;
}
synchronized (this){
count++;
}
synchronized (this){
count++;
}
synchronized (this){
count++;
}
}
则在整个方法内加上锁
/**
* 锁粗化
*/
public synchronized void coarseningSync(){
count++;
count++;
count++;
count++;
count++;
}
缺点
锁越粗越容易造成死锁
可能出现的问题
失去锁的对象
由于jvm的锁是锁定对象的头两位数字,一旦锁所锁定的对象换成了另一个对象,锁会立即失效
解决方式
private final Object obj=new Object();
锁的对象前面加上final并在创建的时候初始化,即可避免后面的代码修改了对象造成锁失效
用String对象当锁
由于string对象的值都是放在常量值当中,多个String对象的值一致的时候,他们所指向的内存地址一致,也就是说,他们都能获得锁
乐观锁(CAS(Compare And Set)|自旋)
在关系数据库管理系统里,乐观并发控制(又名“乐观锁”,Optimistic Concurrency Control,缩写“OCC”)是一种并发控制的方法。它假设多用户并发的事务在处理时不会彼此互相影响,各事务能够在不产生锁的情况下处理各自影响的那部分数据。在提交数据更新之前,每个事务会先检查在该事务读取数据后,有没有其他事务又修改了该数据。如果其他事务有更新的话,正在提交的事务会进行回滚。乐观事务控制最早是由孔祥重(H.T.Kung)教授提出。
乐观锁( Optimistic Locking ) 相对悲观锁而言,乐观锁假设认为数据一般情况下不会造成冲突,所以在数据进行提交更新的时候,才会正式对数据的冲突与否进行检测,如果发现冲突了,则让返回用户错误的信息,让用户决定如何去做。
相对于悲观锁,在对数据库进行处理的时候,乐观锁并不会使用数据库提供的锁机制。一般的实现乐观锁的方式就是记录数据版本。
数据版本,为数据增加的一个版本标识。当读取数据时,将版本标识的值一同读出,数据每更新一次,同时对版本标识进行更新。当我们提交更新的时候,判断数据库表对应记录的当前版本信息与第一次取出来的版本标识进行比对,如果数据库表当前版本号与第一次取出来的版本标识值相等,则予以更新,否则认为是过期数据。
简单而言:1、获取现有的值(a)——>2、然后输入期望的值(b)——>3、最后获得最终的值©。整个过程对现有的值(a)保持监控,确保现有的值(a)还是原来的值。如果检测到现有的值(a)被改变,有两种处理方式:1、重新读取值并从第一步重新运行处理逻辑;2、整个过程失败报错
cas由cpu原语支持,由cpu指令集实现,中间不能被打断.
实现数据版本有两种方式,第一种是使用版本号,第二种是使用时间戳。
可能遇到的问题
ABA问题
A变成B,B又变回A
解决办法
使用cas时使用版本号
使用版本号实现乐观锁
使用版本号时,可以在数据初始化时指定一个版本号,每次对数据的更新操作都对版本号执行+1操作。并判断当前版本号是不是该数据的最新的版本号。
![image.png]([object Object]&name=image.png&originHeight=147&originWidth=778&size=10343&status=done&style=none&width=778)
优点与不足
乐观并发控制相信事务之间的数据竞争(data race)的概率是比较小的,因此尽可能直接做下去,直到提交的时候才去锁定,所以不会产生任何锁和死锁。但如果直接简单这么做,还是有可能会遇到不可预期的结果,例如两个事务都读取了数据库的某一行,经过修改以后写回数据库,这时就遇到了问题。
使用
juc包下面的类使用样例
public class TestThreadAtomic {
private AtomicInteger atomicInteger=new AtomicInteger(0);
private static Integer count=0;
public void testAtomicInteger(){
for(int i=0;i<20;i++){
atomicInteger.incrementAndGet();//等价atomicInteger++
count++;
}
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
TestThreadAtomic testThreadAtomic = new TestThreadAtomic();
ArrayList<Thread> threads = new ArrayList<>();
for(int i=0;i<10;i++){
threads.add(new Thread(testThreadAtomic::testAtomicInteger,"testAtomic"+i));
}
threads.stream().forEach(thread -> thread.start());
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("计数器:"+count);
System.out.println(testThreadAtomic.atomicInteger);
}
}
运行结果
//第一次
计数器:189
200
//第二次
计数器:196
200
//第三次
计数器:200
200
//第四次
计数器:196
200
由此可以看出,AtomicInteger类是integer的线程安全类
AtomicInteger
原子性的Integer 类api
atomicInteger.incrementAndGet() | atomicInteger的值+1,等价于atomicInteger++。结果与addAndGet(1)相同 |
atomicInteger.addAndGet(int delta) | atomicInteger添加任意的值,保证原子性 |
atomicInteger.decrementAndGet() | atomicInteger的值-1,等价于atomicInteger–。结果与addAndGet(-1)相同 |
atomicInteger.lazySet() | 为atomicInteger设置新的值 |
atomicInteger.getAndSet() | watomicInteger设置新的值并且返回旧的值 |
实现cas的基类 Unsafe
**
cas类对比
longAdder比atomic更快
实验过程
public class AtomicTest{
private AtomicLong count1=new AtomicLong(0);
private LongAdder count2=new LongAdder();
/**
* count1递增
*/
public void addCount1(){
for(int i=0;i<100000;i++){
count1.incrementAndGet();
}
}
/**
* count2递增
*/
public void addCount2(){
for(int i=0;i<100000;i++){
count2.increment();
}
}
public static void main(String[] args) throws Exception{
AtomicTest atomicTest = new AtomicTest();
ArrayList<Thread> threads1 = new ArrayList<>();
ArrayList<Thread> threads2 = new ArrayList<>();
for(int i=0;i<10000;i++){
threads1.add(new Thread(()->{
atomicTest.addCount1();
}));
threads2.add(new Thread(()->{
atomicTest.addCount2();
}));
}
long count1NowTime = System.currentTimeMillis();
for(Thread thread:threads1){
thread.start();
}
for(Thread thread:threads1){
thread.join();
}
long count1EndTime = System.currentTimeMillis();
System.out.println("atomic开始时间:"+count1NowTime+"\t结束时间:"+count1EndTime);
System.out.println("atomic他们相差:"+((count1EndTime-count1NowTime)/100)+"秒");
long count2NowTime = System.currentTimeMillis();
for(Thread thread:threads2){
thread.start();
}
for(Thread thread:threads2){
thread.join();
}
long count2EndTime = System.currentTimeMillis();
System.out.println("longAddr开始时间:"+count2NowTime+"\t结束时间:"+count2EndTime);
System.out.println("longAddr他们相差:"+((count2EndTime-count2NowTime)/100)+"秒");
}
}
实验结果
atomic开始时间:1615609095729 结束时间:1615609113348
atomic他们相差:176秒
longAddr开始时间:1615609113348 结束时间:1615609115605
longAddr他们相差:22秒
longAddr为什么比atomic更快
longAddr.add的源码
public void add(long x) {
Cell[] as; long b, v; int m; Cell a;
if ((as = cells) != null || !casBase(b = base, b + x)) {
boolean uncontended = true;
if (as == null || (m = as.length - 1) < 0 ||
(a = as[getProbe() & m]) == null ||
!(uncontended = a.cas(v = a.value, v + x)))
longAccumulate(x, null, uncontended);
}
}
由此可见,longAddr在内部有一个=数组,对期望值和当前值进行累加,每一个线程进去都在后面记录+1,出来的时候再全部计算累加值,所以longAddr在线程多的情况下较快
LOCK
常用线程类
reentrantlock | 相当于sync,但需要手动加锁(lock.lock()或者lock.tryLock())和手动解锁(lock.unlock()) |
countDownLatch | 线程计数器,预先设定一个数量T,每执行一次await(),则在已运行数量上+1,当运行数量>T,则运行countDownLatch.await()下面的代码 |
CyclicBarrier | 预先设定一个数字T,当每运行一次CyclicBarrier.await()时,在已运行数量上+1,当已运行数量=T时,则运行构造方法内的runnable对象 |
phaser | 预先设置一个数字T,然后设置n个阶段,每个阶段都对应一个方法;方法内写入增加执行数量+1的方法(phaser.arriveAndAwaitAdvance() |
)或者写入放弃执行(T-1)的方法(phaser.arriveAndDeregister()) | |
exchanger | 两个线程之间交换数据(exchanger.exchange(object)),当前线程数不是偶数时,则当前线程池阻塞住等待执行 |
Semaphore | 每次放入n个线程执行代码,n为预设值 |
lockSupport | 唤醒、阻塞线程 |
ReentrantReadWriteLock | 读写锁 |
公平锁和非公平锁
ReentrantLock默认非公平锁
公平锁
除了正在执行的线程外,所有等待执行的线程都会放在一个队列里面等待锁,锁释放后按顺序执行,也可能是后进的先执行。但,执行的一定是队列里面的线程
非公平锁
除了正在执行的线程外,锁释放后全都一起竞争获取锁,不分先后
互斥锁(排他锁)
只要一个线程拿到后,另外的线程拿不到这把锁
lock.API
lock() | 直接加锁,如果无法加锁,线程会不停循环直到成功加锁为止。注:加锁后必须在finally{}中解锁 |
tryLock(int i,TimeUnit unit) | 在有限时间内尝试加锁,成功加锁返回true,否则为false。时间过去后无论是否成功加锁都会继续往下执行代码。 |
unlock() | 解锁 |
interrupt() | 打断线程锁 |
lockInterruptibly() | 创建一个可以被打断的锁 |
使用方式
直接加锁
public class ReentrantLockTest {
Lock lock = new ReentrantLock(true);//true为公平锁,false为非公平锁。默认是非公平锁
public void reentrantLockTest1() {
lock.lock();//此处,阿里代码格式规范建议把lock.lock()写在try块的前一行
try {
for (int i = 0; i < 1000; i++) {
System.out.println(i);
}
} catch (Exception e) {
} finally {
//使用lock手动加锁的话,当前方法内必须写finally{}并且finally{}第一行必须手动解锁——lock.unlock();。否则该锁的资源永远得不到释放,会造成死锁。
lock.unlock();
}
}
public static void main(String[] args) {
ReentrantLockTest reentrantLockTest = new ReentrantLockTest();
ArrayList<Thread> threads = new ArrayList<>();
for (int i = 0; i < 100; i++) {
threads.add(new Thread(reentrantLockTest::
reentrantLockTest1
));
}
threads.forEach(Thread::start);
}
}
尝试加锁
public void reeentrantTryLock(){
boolean b=false;
try {
//尝试在10毫秒内加锁,如果10毫秒内没有获得锁,则返回false。而无论是否能加锁,都会执行下面的代码
b = lock.tryLock(1, TimeUnit.MILLISECONDS);
System.out.println(Thread.currentThread()+"线程已加锁");
for(int i=0;i<10;i++){
Thread.sleep(100);
System.out.println(i);
}
}catch (Exception e){
}finally {
if(b){
lock.unlock();
}
System.out.println(Thread.currentThread()+"锁已解除");
}
}
public static void main(String[] args) {
ReentrantLockTest reentrantLockTest = new ReentrantLockTest();
ArrayList<Thread> threads = new ArrayList<>();
for(int i=0;i<10;i++){
threads.add(new Thread(reentrantLockTest::reeentrantTryLock));
}
threads.forEach(Thread::start);
}
执行结果
Thread[Thread-1,5,main]线程已尝试加锁
Thread[Thread-8,5,main]线程已尝试加锁
Thread[Thread-9,5,main]线程已尝试加锁
Thread[Thread-4,5,main]线程已尝试加锁
Thread[Thread-6,5,main]线程已尝试加锁
Thread[Thread-2,5,main]线程已尝试加锁
Thread[Thread-3,5,main]线程已尝试加锁
Thread[Thread-5,5,main]线程已尝试加锁
Thread[Thread-0,5,main]线程已尝试加锁
Thread[Thread-7,5,main]线程已尝试加锁
0
0
8
8
9
Thread[Thread-1,5,main]锁已解除
9
9
Thread[Thread-9,5,main]没有加锁
Thread[Thread-0,5,main]没有加锁
Thread[Thread-8,5,main]没有加锁
Thread[Thread-2,5,main]没有加锁
Thread[Thread-3,5,main]没有加锁
Thread[Thread-7,5,main]没有加锁
Thread[Thread-6,5,main]没有加锁
Thread[Thread-5,5,main]没有加锁
Thread[Thread-4,5,main]没有加锁
由此可得
lock.trylock();加锁不一定会成功,但无论成功与否,都会执行lock.tryLock()下方的代码
锁打断
/**
* 插入别的锁直接运行(不会停止其它线程的运行)
*/
public void lockInterruptibly() {
try {
//加锁——此锁可以进行打断,也就是说,可以被其它线程中途插入进来
lock.lockInterruptibly();
//休眠5秒
System.out.println(Thread.currentThread() + "start");
TimeUnit.SECONDS.sleep(10);
System.out.println(Thread.currentThread() + "end");
} catch (Exception e) {
System.out.println(Thread.currentThread()+"被中断锁");
e.printStackTrace();
} finally {
lock.unlock();
}
}
Thread thread1 = new Thread(reentrantLockTest::lockInterruptibly);
Thread thread2 = new Thread(reentrantLockTest::lockInterruptibly);
thread1.start();
System.out.println("先睡5秒再启动第二个线程");
Thread.sleep(5000);
thread2.start();
System.out.println("再睡五秒");
Thread.sleep(5000);
//打断
thread1.interrupt();
System.out.println("睡5秒再打断");
Thread.sleep(5000);
thread2.interrupt();
执行结果
先睡5秒再启动第二个线程
Thread[Thread-0,5,main]start
再睡五秒
Thread[Thread-0,5,main]end
Thread[Thread-1,5,main]start
睡5秒再打断
Thread[Thread-1,5,main]被中断锁
java.lang.InterruptedException: sleep interrupted
at java.lang.Thread.sleep(Native Method)
at java.lang.Thread.sleep(Thread.java:340)
at java.util.concurrent.TimeUnit.sleep(TimeUnit.java:386)
at com.me.thread.ReentrantLockTest.lockInterruptibly(ReentrantLockTest.java:62)
at com.me.thread.ReentrantLockTest$$Lambda$2/258952499.run(Unknown Source)
at java.lang.Thread.run(Thread.java:745)
创建可以被打断的锁
/**
* 插入别的锁直接运行(不会停止其它线程的运行)
*/
public void lockInterruptibly() {
try {
//加锁——此锁可以进行打断,也就是说,可以被其它线程中途插入进来
lock.lockInterruptibly();
//休眠5秒
System.out.println(Thread.currentThread() + "start");
for(Integer i=0;i<100;i++){
System.out.println(i);
}
System.out.println(Thread.currentThread() + "end");
} catch (Exception e) {
System.out.println(Thread.currentThread()+"被中断锁");
e.printStackTrace();
} finally {
lock.unlock();
}
}
ReentrantLockTest reentrantLockTest = new ReentrantLockTest();
ArrayList<Thread> threads = new ArrayList<>();
Thread thread1 = new Thread(reentrantLockTest::lockInterruptibly);
Thread thread2 = new Thread(reentrantLockTest::lockInterruptibly);
thread1.start();
System.out.println("先睡5秒再启动第二个线程");
Thread.sleep(10);
System.out.println("启动thread2");
thread2.start();
System.out.println("再睡五秒");
Thread.sleep(5000);
打断锁
thread1.interrupt();
System.out.println("睡5秒再打断");
Thread.sleep(10);
thread2.interrupt();
运行结果
启动thread1
先睡5秒再启动第二个线程
Thread[Thread-0,5,main]start
0
1
2
3
4
5
997
998
999
Thread[Thread-0,5,main]end
启动thread2
再睡五秒
Thread[Thread-1,5,main]start
999
Thread[Thread-1,5,main]end
睡5秒再打断
当lockInterruptibly()内的100换成100000的话,由于此时thread1线程还未执行完就被打断,所以thread1不会再执行
注意事项
1、使用lock手动加锁的话,当前方法内必须写finally{}并且finally{}第一行必须手动解锁——lock.unlock();。否则该锁的资源永远得不到释放,会造成死锁。
countDownLatch
线程计数器,countDownLatch.await()线程执行完后再往下执行,countDownLatch.countDown()则是计数-1,
使用方式
Thread[] threads = new Thread[100];
//线程计数器,CountDownLatch只有一个构造器,就是CountDownLatch(int length)。length则是计数器的长度,每执行一次countDown(),计数器-1)
CountDownLatch countDownLatch = new CountDownLatch(threads.length);
for(int i=0;i<threads.length;i++){
threads[i]=new Thread(()->{
int result=0;
for(int k=0;k<100;k++){
result+=k;
System.out.println(result);
}
//计数-1
countDownLatch.countDown();
});
}
System.out.println(Thread.currentThread()+"线程开启,时间:"+ LocalTime.now());
Arrays.stream(threads).forEach(Thread::start);
try {
//阻塞状态,等待所有线程执行完后再往下走
countDownLatch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread()+"线程结束,时间:"+LocalTime.now());
注意:countDownLatch.await()过程中,上面的代码都是阻塞状态,只有等到countDownLatch()计数器为0后,程序才会继续往下执行
CyclicBarrier(第n次执行await()后,执行一次)
使用方式
//每20个线程执行一次
CyclicBarrier cyclicBarrier = new CyclicBarrier(20, new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "start");
System.out.println(Thread.currentThread().getName() + "end");
}
});
for (int i = 0; i < 100; i++) {
new Thread(() -> {
try {
cyclicBarrier.await();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
}).start();
}
System.out.println("cyc循环结束");
输出结果
Thread-19start
Thread-19end
Thread-44start
Thread-44end
Thread-59start
Thread-59end
Thread-80start
Thread-80end
cyc循环结束
Thread-99start
Thread-99end
应用场景
限流,多少次请求才会有处理一次
Phaser(当等待下一阶段(或者放弃下一阶段)的数量等于设置的相位器的数量,则放弃等待执行下一阶段)
api
arriveAndAwaitAdvance() | phaser注册器+1。当注册器数量=bulkRegister。设置的注册器时,进所有线程进入下一个阶段 |
arriveAndDeregister() | bulkRegister进入下一阶段所需的注册器数量-1 |
bulkRegister(int length) | 设置所有线程进入下一个阶段所需注册器的数量 |
register() | 进入下一阶段所需数量+1 |
多线程分阶段运行业务逻辑。
public class TestPhaser {
private static Phaser phase = new MarriagePhaser();
static class Person implements Runnable {
String name;
public Person() {
}
public Person(String name) {
this.name = name;
}
public void threadSleep(Integer time) {
try {
Thread.sleep(time);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
/**
* 到达现场
*/
public void arrive() {
this.threadSleep(5000);
System.out.println(name + "到达现场");
//等待进入下一个阶段(phaser注册器+1)
phase.arriveAndAwaitAdvance();
}
/**
* 吃饭
*/
public void eat() {
this.threadSleep(1000);
System.out.println(name + "吃完了");
//等待进入下一个阶段(phaser注册器+1)
phase.arriveAndAwaitAdvance();
}
/**
* 散席
*/
public void leave() {
this.threadSleep(1000);
System.out.println(name + "离开了");
//等待进入下一个阶段(phaser注册器+1)
phase.arriveAndAwaitAdvance();
}
/**
* 入洞房
*/
private void hug() {
if (name.indexOf("新郎") > -1 || name.indexOf("新娘") > -1) {
this.threadSleep(10000);
System.out.println("新人入洞房");
//等待进入下一个阶段(phaser注册器+1)
phase.arriveAndAwaitAdvance();
} else {
//不进入下一阶段(phaser所需注册器-1)
phase.arriveAndDeregister();
}
}
@Override
public void run() {
this.arrive();
this.eat();
this.leave();
this.hug();
}
}
static class MarriagePhaser extends Phaser{
/**
* 前进,当被满足某些条件后此方法会被调用
* @param phase 阶段id
* @param registeredParties 目前有多少人参与
* @return
*/
@Override
protected boolean onAdvance(int phase, int registeredParties) {
switch (phase){
case 0:
System.out.println("所有人都到齐了!"+registeredParties);
return false;
case 1:
System.out.println("所有人都吃完了!"+registeredParties);
return false;
case 2:
System.out.println("所有人都离开了!"+registeredParties);
return false;
case 3:
System.out.println("新娘新郎送入洞房!"+registeredParties);
return false;
default:
return true;
}
}
}
public static void main(String[] args) {
ArrayList<Thread> threads = new ArrayList<>();
//进入下一阶段所需的其他参与方数量。也就是说,注册phase的数量必须等于此设置的数量
phase.bulkRegister(12);
for (int i = 0; i < 10; i++) {
threads.add(new Thread(new Person("宾客" + i)));
}
threads.add(new Thread(new Person("新郎A")));
threads.add(new Thread(new Person("新娘B")));
threads.forEach(Thread::start);
}
}
输出结果
宾客4到达现场
宾客2到达现场
宾客9到达现场
宾客7到达现场
新娘B到达现场
新郎A到达现场
宾客0到达现场
宾客3到达现场
宾客8到达现场
宾客1到达现场
宾客6到达现场
宾客5到达现场
所有人都到齐了!12
宾客8吃完了
宾客6吃完了
宾客3吃完了
新郎A吃完了
宾客7吃完了
新娘B吃完了
宾客2吃完了
宾客9吃完了
宾客0吃完了
宾客4吃完了
宾客1吃完了
宾客5吃完了
所有人都吃完了!12
宾客2离开了
宾客7离开了
宾客8离开了
新娘B离开了
宾客4离开了
宾客5离开了
宾客6离开了
宾客0离开了
新郎A离开了
宾客9离开了
宾客3离开了
宾客1离开了
所有人都离开了!12
新人入洞房
新人入洞房
新娘新郎送入洞房!2
ReentrantReadWriteLock(读写锁)
为了提高读写的性能,读锁和写锁分别进行,要么先全部读完再写要么先全部写完再读。读和写不重叠。读锁间是共享锁状态运行,写锁间是不共享的互斥锁
实现方式
/**
* 读写锁练习
*
* @author admin
*/
public class TestReadWriteLock {
private static Lock lock = new ReentrantLock();
private static ReentrantReadWriteLock readWriteLock=new ReentrantReadWriteLock(false); //false为非公平锁,true为公平锁,默认false
private static Lock readLock = readWriteLock.readLock();
private static Lock writeLock = readWriteLock.writeLock();
private void read(Lock lock) {
lock.lock();
try {
System.out.println("正在读取");
this.threadSleep(1000);
System.out.println("读取完成");
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void write(Lock lock) {
lock.lock();
try {
System.out.println("正在写入");
this.threadSleep(1000);
System.out.println("写入完成");
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
/**
* 线程休眠
*
* @param time 休眠时间(单位:毫秒)
*/
private void threadSleep(Integer time) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
TestReadWriteLock testReadWriteLock = new TestReadWriteLock();
//读取线程
ArrayList<Thread> readThreads = new ArrayList<>();
//写的线程
ArrayList<Thread> writeThreads = new ArrayList<>();
Runnable readRunner = () -> testReadWriteLock.read(readLock);
Runnable writeRunner = () -> testReadWriteLock.write(writeLock);
for(int i=0;i<20;i++){
readThreads.add(new Thread(readRunner));
}
for(int i=0;i<4;i++){
writeThreads.add(new Thread(writeRunner));
}
readThreads.forEach(Thread::start);
writeThreads.forEach(Thread::start);
}
}
Semaphore
每次放入N个线程执行,n的数量为预先设置的数量
实现方式
Integer time=2000;
//信号量的最大值为1
//true为公平锁,false为非公平锁。默认非公平
Semaphore semaphore = new Semaphore(1,true);
new Thread(()->{
try {
//信号量的可用值-1,当信号量的可用值<1的时候,线程处于阻塞状态
semaphore.acquire();
System.out.println(Thread.currentThread().getName()+"正在运行");
Thread.sleep(time);
System.out.println(Thread.currentThread().getName()+"结束运行");
//信号量可用值+1
semaphore.release();
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
new Thread(()->{
try {
//信号量的可用值-1,当信号量的可用值<1的时候,线程处于阻塞状态
semaphore.acquire();
System.out.println(Thread.currentThread().getName()+"正在运行");
Thread.sleep(time);
System.out.println(Thread.currentThread().getName()+"结束运行");
//信号量可用值+1
semaphore.release();
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
输出结果
Thread-0正在运行
Thread-0结束运行
Thread-1正在运行
Thread-1结束运行
Exchanger(线程交换机一对线程之间交换数据【一对=两个】),成阻塞交换
API
exchange(object obj) | 交换数据 |
exchange(object ob,long time,TimeUnit unit) | ob=需要交换的对象,time=等待时间,unit=等待时间的单位。超时会报错InterruptedException |
运行结果
public class TestExchanger {
private static Exchanger<String> exchanger=new Exchanger<>();
public static void main(String[] args) {
//交换s对象
new Thread(()->{
String s="Test01";
try {
exchanger.exchange(s);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"\t"+s);
}).start();
new Thread(()->{
String s="Test02";
try {
exchanger.exchange(s);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"\t"+s);
}).start();
new Thread(()->{
String s="Test03";
try {
exchanger.exchange(s);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"\t"+s);
}).start();
new Thread(()->{
String s="Test04";
try {
exchanger.exchange(s);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"\t"+s);
}).start();
}
}
运行结果
Thread-1 Test02
Thread-0 Test01
Thread-3 Test04
Thread-2 Test03
注:当不满足一对的exchanger.exchanger()出现时,整个应用会卡住,整个进程都会卡着
LockSupport(修改线程状态——阻塞、等待、唤醒)
api
park() | 中断当前线程 |
unpark(Thread thread) | 唤醒线程thread |
实现方式
用unsafe实现park()和unPark()
public class TestLockSupport {
private Boolean flag = false;
private Thread thread = null;
public static void main(String[] args) {
TestLockSupport testLockSupport = new TestLockSupport();
Thread thread = new Thread(() -> {
for (int i = 0; i < 100; i++) {
try {
Thread.sleep(1000);
if (i == 10) {
testLockSupport.thread = Thread.currentThread();
testLockSupport.flag = true;
LockSupport.park();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(i);
}
});
thread.start();
new Thread(()->{
while (true) {
if (testLockSupport.flag) {
try {
System.out.println("休眠2秒");
Thread.sleep(2000);
System.out.println("唤醒线程");
LockSupport.unpark(testLockSupport.thread);
testLockSupport.flag=false;
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}).start();
}
}
输出结果
0
1
2
3
4
5
6
7
8
9
休眠2秒
唤醒线程
10
11
12
13
22
23
24
25
26
AQS源码
AQS核心
用cas操作——把线程加入线程等待队列的尾巴上
lock
Node.waitStatus状态值解释
CANCELLED,值为 1,表示当前的线程被取消;
SIGNAL,值为-1,表示当前节点的后继节点包含的线程需要运行,也就是 unpark ;
CONDITION,值为-2,表示当前节点在等待 condition,也就是在 condition 队列中;
PROPAGATE,值为-3,表示当前场景下后续的 acquireShared 能够得以执行;
值为 0,表示当前节点在 sync 队列中,等待着获取锁。
reentLock.lock()
public void lock() {
sync.acquire(1);
}
//进入
public final void acquire(int arg) {
//tryAcquire()方法尝试获取锁,获取成功则不会进入下一步。如果无法获取锁,则进入acquireQueued()
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
执行流程
尝试获取独占锁——>不断循环尝试把此线程加到等待执行队列的尾巴直到成功为止——>线程中断
tryAcquire(尝试获取锁,获取成功返回true,获取失败返回false)
protected final boolean tryAcquire(int acquires) {
//获取当前线程
final Thread current = Thread.currentThread();
//获取当前线程状态(0=初始态,1=待执行,2=运行中,3=阻塞中,4=休眠中,5=超时休眠中(一定时间后休眠自动结束),6=待销毁)
int c = getState();
if (c == 0) {
//如果线程队列中没有排队中的线程或者线程队列为null&&cas操作更新线程状态&&把该线程设置为独有访问权限(设置成功返回true,否则为false)
if (!hasQueuedPredecessors() &&
compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
//如果当前线程有访问权限
else if (current == getExclusiveOwnerThread()) {
//当前线程状态+acquires=nextc
int nextc = c + acquires;
if (nextc < 0)
throw new Error("Maximum lock count exceeded");
//线程状态+acquires
setState(nextc);
return true;
}
return false;
}
总结:首先获取当前线程的状态,然后判断线程状态是否为初始化状态,如果线程是初始化状态则把该线程设置为独有访问权限且当前状态是否与期望状态相等,如果条件成立则把此进程的独有访问权限赋予此线程。如果其中一个条件不成立则检查是否有访问权限,有访问权限的话线程状态值+1并且返回true,如果状态值+1后小于0则抛出异常)。
addWaiter(把当前线程加入线程队列的末尾,如果线程队列不存在则先创建一个队列)
private Node addWaiter(Node mode) {
Node node = new Node(Thread.currentThread(), mode);
// Try the fast path of enq; backup to full enq on failure
Node pred = tail;
if (pred != null) {
node.prev = pred;
if (compareAndSetTail(pred, node)) {
pred.next = node;
return node;
}
}
enq(node);
return node;
}
简单来说——尝试把线程加入线程等待执行队列末尾为止,由于是cas操作,此过程不可被打断
acquireQueued(执行队列【自旋锁】)
final boolean acquireQueued(final Node node, int arg) {
//此标志默认是失败的
boolean failed = true;
try {
//中断的标示值,false为非中断
boolean interrupted = false;
for (;;) {
//获取上一个节点
final Node p = node.predecessor();
//如果上一个节点是头部节点则尝试获取锁
if (p == head && tryAcquire(arg)) {
//把此线程设置为头部节点
setHead(node);
//p的下一个节点设置为null,并期待gc回收
p.next = null; // help GC
//失败标志改为成功的状态
failed = false;
//返回中断的标志
return interrupted;
}
//如果当前线程应该被阻塞,则会阻塞当前线程
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
//如果失败标志是失败的,则取消正在进行的尝试获取锁(自旋锁)
if (failed)
cancelAcquire(node);
}
}
设置两个标志——一个是初始值为false的失败标志,一个是初始值为false的是否中断标志。设置一个死循环,不断获取上一个节点,如果上一个节点是头部节点并且获取到锁,则把当前线程设置为头部节点并把本节点设置为null且失败标志改为成功状态并返回。如果上一个节点p不是头部节点或者没有获取到锁的话,则检测p.waitStatus是否等于小于等于0,如果p.waitStatus小于等于零则把当前节点(不是p)的waitStatus设置为-1并中断当前线程。最后,如果失败标志是成功的话,则把node设置为不再关联线程并且把该节点的状态设置为等待清除。
![image.png]([object Object]&name=image.png&originHeight=113&originWidth=570&size=9973&status=done&style=none&width=570)
简单来说——线程A肯定不会进入此方法,能进入此方法的只有线程B和线程C,线程B进入后,跟前置节点线程A竞争。如果直接拿到锁,则把B设置为头部节点,否则B线程进入等待唤醒状态,等A走完后主动唤醒B集训转入下一个循环。C同理
![image.png]([object Object]&name=image.png&originHeight=672&originWidth=981&size=105878&status=done&style=none&width=981)上图只是lock加锁的过程,每个新线程进入都会加入线程队列的tail(末尾)位置,每次都只观测队列中上一个节点(线程)的状态,不需要对整个队列进行加锁,减少锁的开销。上锁过程中用的cas操作,由操作系统保证线程安全,所以线程加入队列的过程中,线程必定是安全的。
selfInterrupt(中断此线程)
lock.unLock()源码
public void unlock() {
sync.release(1);
}
sync.release(1)
//arg=1
public final boolean release(int arg) {
//解锁当前节点(线程)
if (tryRelease(arg)) {
//获取sync队列的头部接地那
Node h = head;
//头部节点不为null且waitStatus值不等于=(头部节点不处于等待获取锁)
if (h != null && h.waitStatus != 0)
//唤醒头部节点
unparkSuccessor(h);
return true;
}
return false;
}
TryRelease()源码
//releases=1
protected final boolean tryRelease(int releases) {
//本线程状态值-1
int c = getState() - releases;
//如果本线程不是享有独占锁的线程直接报错
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
boolean free = false;
//把独有访问权限的线程制空
if (c == 0) {
free = true;
setExclusiveOwnerThread(null);
}
//设置线程的状态值
setState(c);
return free;
}
unparkSuccessor源码
//node=头部节点
private void unparkSuccessor(Node node) {
/*
* If status is negative (i.e., possibly needing signal) try
* to clear in anticipation of signalling. It is OK if this
* fails or if status is changed by waiting thread.
*/
//如果头部节点的waitStatus<0,则把它设为0(cas操作)
int ws = node.waitStatus;
if (ws < 0)
compareAndSetWaitStatus(node, ws, 0);
/*
* Thread to unpark is held in successor, which is normally
* just the next node. But if cancelled or apparently null,
* traverse backwards from tail to find the actual
* non-cancelled successor.
*/
//下一节点s
Node s = node.next;
if (s == null || s.waitStatus > 0) {
s = null;
//创建循环——创建sync队列末尾节点(线程)t,每次循环t都在sync向前移动一次,t不是头部节点且t不为null
for (Node t = tail; t != null && t != node; t = t.prev)
//如果t不是被取消状态
if (t.waitStatus <= 0)
//等待唤醒的节点变为t
s = t;
}
if (s != null)
//如果s不是null,则唤醒s节点(线程)
LockSupport.unpark(s.thread);
}
![image.png]([object Object]&name=image.png&originHeight=707&originWidth=1010&size=91405&status=done&style=none&width=1010)
简单来说——先把本节点(线程)解锁(解锁期间检测正在运行的线程是否是期望解锁的线程,不是就报错),然后获取头部节点,如果头部节点不为null,则唤醒头部节点,期间从后往前遍历整个sync等待队列,如果队列中有非头部节点且waitStatus非被取消状态,则把需要唤醒的节点换成该节点,不断循环。
ThreadLocal
ThreadLocal是用ThreadLocalMap实现,ThreadLocalMap的key-value的key值是threadLocal(即当前线程),ThreadLocalMap又是由弱引用的Entry实现
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
用法
public class TestThreadLocal {
private ThreadLocal<Person> person = new ThreadLocal<>();
public TestThreadLocal() {
}
public static void main(String[] args) {
TestThreadLocal testThreadLocal = new TestThreadLocal();
new Thread(() -> {
testThreadLocal.person.set(new Person());
testThreadLocal.person.get().setName("234234");
Person person = new Person();
person.setName("234234");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(person);
}).start();
new Thread(() -> {
testThreadLocal.person.set(new Person());
testThreadLocal.person.get().setName("234234");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(testThreadLocal.person.get());
}).start();
}
}
用途
声明式事物,确保同一个connection
注意
ThreadLocal不使用时,必须调用ThreadLocal.remove()方法清除ThreadLocal对象,否则会有内存泄漏的风险
**
condition(阻塞、唤醒,相当于notify和await)
相当于一个等待队列,两个线程交替运行
ReentrantLock lock = new ReentrantLock();
Condition condition1 = lock.newCondition();
Condition condition2 = lock.newCondition();
Thread thread1 = new Thread(() -> {
lock.lock();
try {
for(int i=0;i<10;i++){
condition2.signal();
condition1.await();
System.out.println(Thread.currentThread() + "处理业务");
}
condition2.signal();
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
lock.unlock();
}
});
Thread thread2 = new Thread(() -> {
lock.lock();
try {
for(int i=0;i<10;i++){
condition1.signal();
condition2.await();
System.out.println(Thread.currentThread() + "处理业务");
}
condition1.signal();
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
lock.unlock();
}
});
thread1.start();
thread2.start();
输出
Thread[Thread-0,5,main]处理业务
Thread[Thread-1,5,main]处理业务
Thread[Thread-0,5,main]处理业务
Thread[Thread-1,5,main]处理业务
Thread[Thread-0,5,main]处理业务
Thread[Thread-1,5,main]处理业务
Thread[Thread-0,5,main]处理业务
Thread[Thread-1,5,main]处理业务
Thread[Thread-0,5,main]处理业务
Thread[Thread-1,5,main]处理业务
Thread[Thread-0,5,main]处理业务
Thread[Thread-1,5,main]处理业务
Thread[Thread-0,5,main]处理业务
Thread[Thread-1,5,main]处理业务
Thread[Thread-0,5,main]处理业务
Thread[Thread-1,5,main]处理业务
Thread[Thread-0,5,main]处理业务
Thread[Thread-1,5,main]处理业务
Thread[Thread-0,5,main]处理业务
Thread[Thread-1,5,main]处理业务
练习题
1、创建一个类,里面有两个方法——add()和size(),开启两个线程,一个线程添加10次,另一个线程监控第一个线程的添加个数,当第一个线程添加满5个时警告并退出
第一种写法
public class TestShopCar {
private boolean flag = true;
private List<Integer> list = new LinkedList<>();
private Thread newThread = null;
public void addTen() {
newThread = Thread.currentThread();
System.out.println("开始添加");
while (flag) {
for (int i = 0; i < 10; i++) {
list.add(i);
LockSupport.park();
}
}
}
public void check() {
while (flag) {
if(newThread!=null){
synchronized (newThread) {
if(flag){
if (list.size() > 3) {
System.out.println("0");
break;
} else {
if (newThread != null) {
LockSupport.unpark(newThread);
}
}
}
}
}
}
}
public static void main(String[] args) {
for(int i=0;i<100;i++){
TestShopCar shopCar = new TestShopCar();
new Thread(() -> {
shopCar.addTen();
}).start();
new Thread(() -> {
shopCar.check();
}).start();
}
}
}
两个线程交替进行,第一个线程执行完立马让给第二个线程执行,双方交替运行
第二种方法
public class TestShopCar3 {
private List<Integer> list = new ArrayList<>();
public synchronized void add(Integer i) {
list.add(i);
}
public synchronized Integer size() {
return list.size();
}
public static void main(String[] args) {
TestShopCar3 testShopCar3 = new TestShopCar3();
final Object lock = new Object();
new Thread(() -> {
while (true) {
synchronized (lock) {
if (testShopCar3.size() == 5) {
System.out.println(Thread.currentThread().getName() + "结束");
lock.notify();
break;
}
}
}
}, "checkThread").start();
new Thread(() -> {
synchronized (lock) {
for (int i = 0; i < 10; i++) {
try {
lock.notify();
if (testShopCar3.size() == 5) {
lock.wait();
}
testShopCar3.add(i);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("add " + i);
}
}
}, "addThread").start();
}
}
第三种写法
public class TestShopCar4 {
private List<Integer> list = new ArrayList<>();
public synchronized void add(Integer i) {
list.add(i);
}
public synchronized Integer size() {
return list.size();
}
public static void main(String[] args) {
CountDownLatch countDownLatch = new CountDownLatch(1);
CountDownLatch countDownLatch1 = new CountDownLatch(1);
TestShopCar4 testShopCar4 = new TestShopCar4();
Object lock = new Object();
new Thread(()->{
synchronized (lock){
try {
countDownLatch.await();
if(testShopCar4.size()==4){
countDownLatch1.countDown();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread()+"线程结束");
}
},"checkThread").start();
new Thread(()->{
for(int i=0;i<10;i++){
if(testShopCar4.size()==4){
try {
countDownLatch.countDown();
countDownLatch1.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
testShopCar4.add(i);
System.out.println(testShopCar4.list);
}
},"addThread").start();
}
}
第四种写法
public class TestShopCar {
private boolean flag = true;
private List<Integer> list = new LinkedList<>();
private Thread newThread = null;
public void addTen() {
newThread = Thread.currentThread();
System.out.println("开始添加");
for (int i = 0; i < 10; i++) {
list.add(i);
LockSupport.park();
LockSupport.unpark(newThread);
newThread=Thread.currentThread();
System.out.println(list);
}
}
public void check() {
while (flag) {
if (newThread != null) {
if (flag && list.size() == 4) {
System.out.println("结束");
LockSupport.unpark(newThread);
newThread = Thread.currentThread();
LockSupport.park();
} else if (list.size() > 8) {
break;
} else {
if (newThread != null) {
LockSupport.unpark(newThread);
}
}
}
}
}
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
TestShopCar shopCar = new TestShopCar();
new Thread(() -> {
shopCar.addTen();
}).start();
new Thread(() -> {
shopCar.check();
}).start();
}
}
}
2、创建一个类,有方法——get()、add()、getCount()。然后创建两个生产者线程,再创建十个消费者线程
第一种写法
public class TestMq {
private List<String> list = Collections.synchronizedList(new LinkedList<String>());
private ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
private Lock readLock = readWriteLock.readLock();
private Lock writeLock = readWriteLock.writeLock();
public synchronized String get() {
return list.get(0);
}
public synchronized void add(String str) {
list.add(str);
}
public synchronized Integer getCount() {
return list.size();
}
public static void main(String[] args) {
TestMq testMq = new TestMq();
CyclicBarrier cyclicBarrier = new CyclicBarrier(10, () -> {
Iterator<String> iterator = testMq.list.iterator();
while (iterator.hasNext()) {
iterator.next();
iterator.remove();
break;
}
});
Thread writeThread1 = new Thread(() -> {
testMq.writeLock.lock();
try {
testMq.add(Thread.currentThread().getName());
} catch (Exception e) {
e.printStackTrace();
} finally {
testMq.writeLock.unlock();
}
}, "writer1");
Thread writeThread2 = new Thread(() -> {
testMq.writeLock.lock();
try {
testMq.add(Thread.currentThread().getId() + "");
} catch (Exception e) {
e.printStackTrace();
} finally {
testMq.writeLock.unlock();
}
}, "writer2");
writeThread1.start();
writeThread2.start();
for (int i = 0; i < 10; i++) {
new Thread(() -> {
testMq.readLock.lock();
try {
while (testMq.getCount() > 0) {
System.out.println(testMq.get());
cyclicBarrier.await();
}
} catch (Exception e) {
e.printStackTrace();
} finally {
testMq.readLock.unlock();
}
}, "read" + i).start();
}
}
}