目录 一.上节内容复习 1.线程池的实现 2.自定义一个线程池,构造方法的参数及含义 3.线程池的工作原理 4.拒绝策略 5.为什么不推荐系统提供的线程池 二.常见的锁策略 1.乐观锁和悲观锁 2.轻量级锁和重量级锁 3.读写锁和普通互斥
目录
7.ReentrantLock和synchronized的区别
内容指路:Java之线程池
1.阻塞队列保存要执行的任务
2.构造方法初始化线程的数量,不断扫描阻塞队列中的任务并执行
不推荐使用Executors工厂方法构建ExecutorService线程池对象,可能会存在浪费系统的资源的现象.可以自行new出来一个ExecutorService线程池对象
int corePoolSize 核心线程数,创建线程是包含的最小线程数
int maximumPoolSize 最大线程数,也叫临时线程数(核心线程数不够时,允许创建最大的线程数)
long keepAliveTime 临时空闲时长,超过这个时间自动释放
TimeUnit unit 空闲的时间单位,和keepAliveTime一起使用
BlockingQueue
ThreadFactory threadFactory 线程工厂,如何去创建线程
RejectedExecutionHandler handler 拒绝策略,触发的时机,当线程池处理不了过多的任务
AbortPolicy:直接拒绝任务的加入,并且抛出RejectedExecutionException异常
CallerRunsPolicy:返回给提交任务的线程执行
DiscardOldestPolicy:舍弃最老的任务
DiscardPolicy:舍弃最新的任务
1.无界队列
2.最大线程数使用了Integer.MAX_VALUE
乐观锁:对运行环境处乐观态度,刚开始不加锁,当有竞争的时候才加锁
悲观锁:对运行环境处悲观态度,刚开始就直接加锁
判断依据:消耗资源的多少.描述的实现锁的过程
轻量级锁:可以是纯用户态的锁,消耗的资源比较少
重量级锁:可能会调用到系统的内核态,消耗的资源比较多
现实中并不是所有的锁都是互斥锁,互斥会消耗很多的系统资源,所以优化出读写锁
读锁:共享锁,读与读操作都能同时拿到锁资源
写锁:排它锁,读写,写读,写写不能同时拿到锁资源
普通互斥锁:synchronized,只要其中一个线程拿到锁资源,其他的线程就要堵塞等待.
自旋锁:不停的询问资源是否被释放,如果释放了可以第一时间获得锁资源
挂起等待锁:等待通知之后再去竞争锁,并不会第一时间获得锁资源
可重入锁:对同一个锁资源可以加多次锁
不可重入锁:不可以对同一个锁资源加多次锁
公平锁:先堵塞等待锁资源的线程先拿到锁资源
非公平锁:先争抢到锁资源的线程先拿到锁,没有先后顺序之说
所有关于争抢的事情,大多是都是非公平的,这样可以提高系统效率.
CAS:compare and swap,比较并交换
boolean CAS(address, expectValue, swapValue) { if (&address == expectedValue) { &address = swapValue; return true; } return false;}
address:指的是内存地址
expectValue:期望值
swapValue:要交换的值
具体实现:用期望值(expectValue)和内存中的值(&address)进行比较,如果内存中的值和期望值相等,用要交换的值(swapValue)覆盖内存中的值(&address).如果不等,什么都不做
CAS用户态实现原子性
public class Demo01_CAS { public static void main(String[] args) throws InterruptedException { //原子整型 AtomicInteger atomicInteger = new AtomicInteger(); Thread thread = new Thread(() -> { //五万次自增操作 for (int i = 0; i < 50000; i++) { atomicInteger.getAndIncrement(); } }); Thread thread2 = new Thread(() -> { //五万次自增操作 for (int i = 0; i < 50000; i++) { atomicInteger.getAndIncrement(); } }); //启动线程 thread.start(); thread2.start(); //等待两个线程执行完成 thread.join(); thread2.join(); System.out.println(atomicInteger); }}
线程1将主内存的value值加载到工作内存1中,工作内存1中var5=0(expectValue),然后线程1调离CPU,线程2调入CPU,此时主内存的value值加载到工作内存1中,工作内存2中var5=0(expectValue),然后线程2调离CPU,线程1调入CPU,此时进入到while循环判断,var5==&(var1+var2=1)(主内存中value的值),将主内存中的值赋值为swapValue(var5+1)返回true,线程1操作结束.
此时线程1调离CPU,线程2调入CPU,此时线程2工作内存中var5=0(expectValue),进入到CAS操作中,此时将&(var1+var2)=1与var5=0(expectValue)进行对比,发现不相同,返回false,之后重新将主内存中的value值加载到工作内存中,此时var5=1,进入到CAS操作中,此时将&(var1+var2)=1与var5=0(expectValue)进行对比,发现相同,将主内存中的值赋值为swapValue(var5+1=2)返回true,线程2操作结束.
两个线程的操作结束,没有发生线程不安全的现象,因此我们可以总结出:CAS操作通过不停的自旋检查预期值来保证了线程安全,while循环是在用户态(应用层)的层面上支持了原子性,所以比内核态的锁效率要高很多.
轻量级锁,自旋锁的实现
public class SpinLock { private Thread owner = null; public void lock(){ // 通过 CAS 看当前锁是否被某个线程持有. // 如果这个锁已经被别的线程持有, 那么就自旋等待. // 如果这个锁没有被别的线程持有, 那么就把 owner 设为当前尝试加锁的线程. while(!CAS(this.owner, null, Thread.currentThread())){ } } public void unlock (){ this.owner = null; }}
owner:标记了哪一个线程竞争到了锁.
还是拿线程1和线程2举例,当线程1加锁,线程1执行lock()方法,while循环的CAS操作,此时owner=null,符合预期值,将owner赋值为线程1的信息,CAS方法返回true,while循环结束,加锁方法结束,此时线程2也执行lock()方法,进入到while循环CAS操作,owner此时保存线程1的信息,返回false,然后线程2一直执行while循环,直到线程1的内容执行完毕之后,执行unlock()方法,此时owner置为null,此时线程2的CAS操作owner符合预期值null,将owner赋值为线程2的信息,返回ture,结束while循环,线程2执行相应的操作,直到完毕.
ABA分别代表预期值的三种状态.
CAS的ABA状态可能会带来的问题:接下来我们看一个具体的场景
我的账户里面有2000块钱(状态A),我委托张三说:如果我忘给李四转1000块钱,下午帮我转一下,我在中午给李四转了1000块钱(状态B),但是随后公司发奖金1000到我的账户,此时我账户有1000块钱(状态A),张三下午检查我账户,发现我有2000块钱,于是又给李四转了1000块钱,此时就出现问题了,李四收到了两次1000元,不符合我们的需求了.
解决ABA问题:
给预期值加一个版本号.
在做CAS操作时,同时要更新预期值的版本号,版本号只增不减
在进行CAS比较的时候,不仅预期值要相同,版本号也要相同,这个时候才会返回true.
通过以上锁策略学习可以知道,synchronized在不同的时期可能会用到不同的锁策略
随着线程间对锁竞争的激烈程度不断增加,锁的状态不断升级.
查看锁对象的对象头信息
在pom.xml中导入依赖
org.openjdk.jol jol-core 0.16
public class Demo02_Synchronized { // 定义一些变量 private int count; private long count1 = 200; private String hello = ""; // 定义一个对象变量 private TestLayout test001 = new TestLayout(); public static void main(String[] args) throws InterruptedException { // 创建一个对象的实例 Object obj = new Object(); // 打印实例布局 System.out.println("=== 任意Object对象布局,起初为无锁状态"); System.out.println(ClassLayout.parseInstance(obj).toPrintable()); System.out.println("=== 延时4S开启偏向锁"); // 延时4S开启偏向锁 Thread.sleep(5000); // 创建本类的实例 Demo02_Synchronized monitor = new Demo02_Synchronized(); // 打印实例布局,注意查看锁状态为偏向锁 System.out.println("=== 打印实例布局,注意查看锁状态为偏向锁"); System.out.println(ClassLayout.parseInstance(monitor).toPrintable()); System.out.println("==== synchronized加锁"); // 加锁后观察加锁信息 synchronized (monitor) { System.out.println("==== 第一层synchronized加锁后"); System.out.println(ClassLayout.parseInstance(monitor).toPrintable()); // 锁重入,查看锁信息 synchronized (monitor) { System.out.println("==== 第二层synchronized加锁后,锁重入"); System.out.println(ClassLayout.parseInstance(monitor).toPrintable()); } // 释放里层的锁 System.out.println("==== 释放内层锁后"); System.out.println(ClassLayout.parseInstance(monitor).toPrintable()); } // 释放所有锁之后 System.out.println("==== 释放 所有锁"); System.out.println(ClassLayout.parseInstance(monitor).toPrintable()); System.out.println("==== 多个线程参与锁竞争,观察锁状态"); Thread thread1 = new Thread(() -> { synchronized (monitor) { System.out.println("=== 在线程A 中获取锁,参与锁竞争,当前只有线程A 竞争锁,轻度锁竞争"); System.out.println(ClassLayout.parseInstance(monitor).toPrintable()); } }); thread1.start(); // 休眠一会,不与线程A 激烈竞争 Thread.sleep(100); Thread thread2 = new Thread(() -> { synchronized (monitor) { System.out.println("=== 在线程B 中获取锁,与其他线程进行锁竞争"); System.out.println(ClassLayout.parseInstance(monitor).toPrintable()); } }); thread2.start(); // 不休眠直接竞争锁,产生激烈竞争 System.out.println("==== 不休眠直接竞争锁,产生激烈竞争"); synchronized (monitor) { // 加锁后的类对象 System.out.println("==== 与线程B 产生激烈的锁竞争,观察锁状态为fat lock"); System.out.println(ClassLayout.parseInstance(monitor).toPrintable()); } // 休眠一会释放锁后 Thread.sleep(100); System.out.println("==== 释放锁后"); System.out.println(ClassLayout.parseInstance(monitor).toPrintable()); System.out.println("==========================================================================================="); System.out.println("==========================================================================================="); System.out.println("==========================================================================================="); System.out.println("==========================================================================================="); System.out.println("==========================================================================================="); System.out.println("==========================================================================================="); // 调用hashCode后才保存hashCode的值 monitor.hashCode(); // 调用hashCode后观察现象 System.out.println("==== 调用hashCode后查看hashCode的值"); System.out.println(ClassLayout.parseInstance(monitor).toPrintable()); // 强制执行垃圾回收 System.GC(); // 观察GC计数 System.out.println("==== 调用GC后查看age的值"); System.out.println(ClassLayout.parseInstance(monitor).toPrintable()); // 打印类布局,注意调用的方法不同 System.out.println("==== 查看类布局"); System.out.println(ClassLayout.parseClass(Demo02_Synchronized.class).toPrintable()); // 打印类对象布局 System.out.println("==== 查看类对象布局"); System.out.println(ClassLayout.parseInstance(Demo02_Synchronized.class).toPrintable()); }}class TestLayout {}
打印的信息:
无锁的状态(non-biasable)
可偏向锁状态(biasable)
已偏向锁状态(biased)
当有一个线程参与竞争之后,就会升级成为轻量级锁(thin lock)
继续创建线程参与锁竞争,那么就会升级为重量级锁
在写代码的时候,程序员自己加synchronized来保证线程安全
如果加了synchronized的代码块只有读操作没有写操作,JVM就认为这个代码块没必要加锁,JVM运行的时候就会被优化掉,这个现象就叫做锁消除
简单来说就是过滤掉了无效的synchronized,从而提高了效率.JVM只有100%的把握才会优化
java.util.concurrent 包的简称,JDK1.5之后对多线程的一种实现,这个包下的类都与多线程有关,提供了许多工具类.
也是描述线程任务的接口
Callable接口接口使用说明
public class Demo03_Callable { public static void main(String[] args) throws ExecutionException, InterruptedException { Callable callable = new Callable() { @Override public Integer call() throws Exception { int sum = 0; for (int i = 0; i < 5; i++) { sum += i; TimeUnit.SECONDS.sleep(1); } return sum; } }; //通过FutureTask来创建一个对象,这个对象持有Callable FutureTask futureTask = new FutureTask<>(callable); //让线程执行好定义的任务 Thread thread = new Thread(futureTask); thread.start(); Integer result = futureTask.get(); System.out.println("最终的结果为:" + result); }}
打印结果:
Thread类没有关于Callable的构造方法,因此我们要借助FutureTask让Thread执行Callable接口定义的任务,FutureTask是Runnable的一个实现类,所以可以传入Thread的构造方法中.
Callable接口抛出异常演示:
public class Demo04_CallableException { public static void main(String[] args){ // 先定义一个线程的任务 Callable callable = new Callable() { @Override public Integer call() throws Exception { int sum = 0; for (int i = 0; i < 5; i++) { sum += i; TimeUnit.SECONDS.sleep(1); throw new Exception("业务出现异常"); } // 返回结果 return sum; } }; // 通过FutureTask类来创建一个对象,这个对象持有callable FutureTask futureTask = new FutureTask<>(callable); // 创建线程并指定任务 Thread thread = new Thread(futureTask); // 让线程执行定义好的任务 thread.start(); // 获取线程执行的结果 System.out.println("等待结果..."); Integer result = null; // 捕获异常 try { result = futureTask.get(); } catch (InterruptedException e) { e.printStackTrace(); } catch (ExecutionException e) { System.out.println("处理异常" + e.getMessage()); e.printStackTrace(); } // 打印结果 System.out.println(result); }}
打印结果:
Runnable和Callable接口的区别
1.Callable实现的是call()方法,Runnable实现的是run()方法
2.Callable可以返回一个结果,Runnable没有返回值
3.Callable要配合FutureTask一起使用.
4.Callable可以抛出异常,Runnabe不可以
本身就是一个锁,是基于CAS实现的纯用户态的锁
lock() tryLock() unlock()
public class Demo05_ReentrantLock { public static void main(String[] args) throws InterruptedException { ReentrantLock lock = new ReentrantLock(); //加锁 lock.lock(); //尝试加锁,死等 lock.tryLock(); //尝试加锁,有超时时间 lock.tryLock(1, TimeUnit.SECONDS); //释放锁 lock.unlock(); }}
//模拟出现异常的处理 public static void exception() { ReentrantLock lock = new ReentrantLock(); try { //加锁 lock.lock(); //需要实现的业务 throw new Exception("出现异常"); } catch (Exception e) { throw new RuntimeException(e); } finally { lock.unlock(); } }
//公平锁ReentrantLock lock = new ReentrantLock(true);
//非公平锁
ReentrantLock lock = new ReentrantLock(false);
public class Demo06_ReadWriteLock { public static void main(String[] args) { ReentrantReadWriteLock lock = new ReentrantReadWriteLock(); //读锁,共享锁,读与读可以共享 lock.writeLock(); //写锁,排它锁,读写,读读,写读不能共存 lock.readLock(); }}
private static ReentrantLock reentrantLock = new ReentrantLock(); // 定义不同的条件 private static Condition boyCondition = reentrantLock.newCondition(); private static Condition girlCondition = reentrantLock.newCondition(); public static void demo05_Condition () throws InterruptedException { Thread threadBoy = new Thread(() -> { // 让处理男生任务的线程去休眠 try { boyCondition.await(); } catch (InterruptedException e) { e.printStackTrace(); } // 唤醒处理女生任务的线程 girlCondition.signalAll(); }); Thread threadGirl = new Thread(() -> { // 让处理女生任务的线程去休眠 try { girlCondition.await(); } catch (InterruptedException e) { e.printStackTrace(); } // 唤醒处理男生任务的线程 boyCondition.signalAll(); }); }
1. synchronized 使用时不需要手动释放锁.ReentrantLock使用时需要手动释放.使用起来更灵活,但是也容易遗漏unlock.
2. synchronized在申请锁失败时,会一直等待锁资源.ReentrantLock可以通过trylock的方式等待一段时间就放弃.
3. synchronized是非公平锁, ReentrantLock 默认是非公平锁.可以通过构造方法传入一个true开启公平锁模式.
4. synchronized是一个关键字,是JVM内部实现的(可能涉及到内核态).ReentrantLock是标准库的一个类,基于Java JUC实现(用户态实现)
JUC包下常见的原子类
原子类是基于CAS操作实现的,因此比加锁的方式保证线程安全要高效的很多.
下面以AtomicInteger为例看一些方法
public class Demo07_Atomic { public static void main(String[] args) { AtomicInteger atomicInteger = new AtomicInteger(); //相等于i++ atomicInteger.getAndIncrement(); System.out.println(atomicInteger); //相当于++i atomicInteger.incrementAndGet(); System.out.println(atomicInteger); //相当于i-- atomicInteger.getAndDecrement(); System.out.println(atomicInteger); //相当于--i atomicInteger.decrementAndGet(); System.out.println(atomicInteger); }}
信号量:表示可用资源的数量,本质上就是一个计数器
时间案例:停车场展示牌,一共有100个车位,表示一共最多有100个车位可以使用
当有车开进去的时候,表示P操作(申请资源),可用车位就-1;当有车开出去的时候,表示V操作(释放资源),可用车位就+1,如果计数器已经为0了,这个时候还有车想进来,仅需要阻塞等待.
操作系统有PV操作
P操作表示申请资源,可用资源-1;
V操作表示释放资源,可用资源+1;
当没有可用资源的时候,其他线程就阻塞等待
Semaphore信号量的使用案例
public class Demo08_Semaphore { private static Semaphore semaphore = new Semaphore(3); public static void main(String[] args) { //定义一个任务 Runnable runnable = new Runnable() { @Override public void run() { System.out.println(Thread.currentThread().getName() + ":申请资源"); try { semaphore.acquire(); System.out.println(Thread.currentThread().getName() + ":获取到了资源"); //等待一秒,模仿处理业务 Thread.sleep(1000); semaphore.release(); System.out.println(Thread.currentThread().getName() + ":释放了资源"); } catch (InterruptedException e) { throw new RuntimeException(e); } } }; //创建10个线程 for (int i = 0; i < 10; ++i) { Thread thread = new Thread(runnable); thread.start(); } }}
打印结果:
由结果可以看出,几乎所有的线程都在申请资源,但是信号量控制了最多三个可以申请到资源,因此最多有三个线程同时工作,其他的线程都处在阻塞等待的状态,等到申请到的资源的线程释放资源之后,其他阻塞等待的线程才可能申请到资源.
应用场景:需要指定有效资源个数(比如同时最多支持多少个并发执行),可以考虑使用Semaphore
同时等待 N 个任务执行结束.
现实案例:相当于10个人赛跑,需要等待10个人都跑完之后,才能公布所有人的成绩.
public class Demo09_CountDownLatch { private static CountDownLatch countDownLatch = new CountDownLatch(10); public static void main(String[] args) throws InterruptedException { for (int i = 0; i < 10; ++i) { Thread thread = new Thread(() -> { System.out.println(Thread.currentThread().getName() + ":出发"); try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { throw new RuntimeException(e); } //执行完毕,计数减一 countDownLatch.countDown(); //走到这里说明这个线程已经结束 }, "线程"+i); thread.start(); } //等待所有的线程执行完成之后再继续执行下面的内容 countDownLatch.await(); System.out.println("所有的线程执行完毕"); }}
打印结果:
应用场景:把一个大任务分为几个小任务,或是等待一些前置资源,可以考虑使用CountDownLatch
通过它可以实现让一组线程等待至某个状态之后再全部同时执行。叫做回环是因为当所有等待线程都被释放以后,CyclicBarrier可以被重用。我们暂且把这个状态就叫做barrier,当调用await()方法之后,线程就处于barrier了。
Exchanger一般用于两个工作线程之间交换数据。
public class Demo10_List { public static void main(String[] args) throws InterruptedException { ArrayList list = new ArrayList<>(); for (int i = 0; i < 10; ++i) { int num = i; Thread thread = new Thread(() -> { list.add(num); System.out.println(list); }); thread.start(); } Thread.sleep(1000); System.out.println("============="); System.out.println(list); }}
打印结果:
我们可以看出报了并发修改异常的错误,而且每一次执行,报异常的位置不一样,也可能没有报异常.
如果我们在开发中遇到这个问题,我们要先考虑是不是集合类使用不恰当.也就是说我们使用了线程不安全的集合类,如何使用线程安全的集合类
1.前面我们也学习过了Vector,HashTable,但是不推荐使用.不推荐使用,效率太低
只是方法加了synchronized
2.自己加synchronized和ReentrantLock进行加锁,也不推荐,效率还是很低.
3.通过工具类,创建一个线程安全的集合类 ---不推荐
List
源码分析:本质上和Vector一样,不过是将所有代码加上synchronized
4.CopyOnWriteArrayList
它是JUC包下的一个类,使用的是一种叫写时复制技术来实现的
1.当要修改一个集合时,先复制这个集合的复本
2.修改复本的数据,修改完成后,用复本覆盖原始集合
优点:
在读多写少的场景下,性能很高,不需要加锁竞争.
缺点:
1.占用内存较多.是因为复制了一份新的数据进行修改
新写的数据不能被第一时间读取到.
在多线程环境下,如果使用集合类,优先推荐使用CopyOnWriteArrayList
线程安全的哈希表,但只是将方法简单的加上了synchronized修饰,效率很低,不推荐使用
线程不安全的哈希表,单线程下使用不会报错,但是多线程环境下使用会产生线程不安全的问题
多线程环境下推荐使用这个哈希表,并不是通过synchronized简单的加锁,与HashTable不同,而是通过JUC包下的ReentrantLock实现的加锁(基于CAS,纯用户态实现).
1.更小的锁粒度
HashTable的加锁方式:一个HashTable对象只有一把锁,当一个线程修改一个哈希桶的时候,其他线程无法修改任何一个桶的数据
ConcurrentHashMap的加锁方式:加锁的方式是synchronized,但是不是锁整个对象, 而是 "锁桶" (用每个链表的头结点作为锁对象)
2.只给写加锁,不给读加锁
3.共享变量用volatile修饰
4.充分利用CAS机制
比如size属性通过CAS来更新.避免出现重量级锁的情况.
5.对扩容进行了特殊优化
对于需要扩容的操作,新建一个新的Hash桶,随后的每次操作都搬运一些元素去新的Hash桶
在扩容没有完成时,两个Hash桶同时存在每次写入时只写入新的Hash桶.每次读取需要新旧的Hash桶同时读取.所有的数据搬运完成后,把老的Hash桶删除
死锁就是一个线程加上锁之后不运行也不释放僵住了,死锁会导致程序无法继续运行,是一个最严重的BUG之一.
死锁出现的场景:
1.一个线程一把锁
一个线程对一把锁加锁两次,如果是不可重入锁,就会产生死锁的现象,如果是可重入锁,就不会产生死锁的现象.
2.两个线程两把锁
public class Demo11_DeadLock { public static void main(String[] args) { //定义两个锁对象 Object locker1 = new Object(); Object locker2 = new Object(); //线程1先获取locker1,再获取locker2 Thread thread1 = new Thread(() -> { System.out.println(Thread.currentThread().getName() + ":申请locker1"); synchronized (locker1) { System.out.println(Thread.currentThread().getName() + ":获取到了locker1"); //模拟业务处理 try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { throw new RuntimeException(e); } //获取locker2 System.out.println(Thread.currentThread().getName() + ":申请locker2"); synchronized (locker2) { System.out.println(Thread.currentThread().getName() + ":获取到了locker2"); } } }); thread1.start(); //线程1先获取locker2,再获取locker1 Thread thread2 = new Thread(() -> { System.out.println(Thread.currentThread().getName() + ":申请locker2"); synchronized (locker2) { System.out.println(Thread.currentThread().getName() + ":获取到了locker2"); //模拟业务处理 try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { throw new RuntimeException(e); } //获取locker1 System.out.println(Thread.currentThread().getName() + ":申请locker1"); synchronized (locker1) { System.out.println(Thread.currentThread().getName() + ":获取到了locker1"); } } }); thread2.start(); }}
打印结果:
线程1获取到了locker1,线程2获取到了locker2,线程1又想获取到了locker2,而线程2又想获取locker1,但是这两个锁已经被占用,而且不会被释放,所以两个线程互相拿到了对象想要获取的锁,就产生了死锁的现象.
2.发生死锁的原因
1.互斥使用:锁A被线程1占用了,线程2就不能用了
2.不可抢占:锁A被线程1占用了,线程2不能主动把锁A抢过来,除非线程1主动释放
3.请求保持:有多把锁,线程1拿到了锁A之后,不释放还要继续再拿锁B
4.循环等待:线程1等待线程2释放锁,线程2要释放锁得等待线程3先释放锁,线程3释放锁得等待线程1释放锁...形成了循环关系
3.避免死锁解决方案
以上四条是形成死锁的必要条件,打破上面四条中的任何一条就可以,逐条分析一下
1.互斥使用:这个不能打破,这个是锁的基本特性
2.不可抢占:这个也不能打破,这个也是锁的基本特性
3.请求保持:这个有可能打破,取决于代码怎么写
4.循环等待:约定好加锁顺序就可以把破循环等待,t1.locker1 ->locker2,t2.locker2 -> locker1这个顺序造成了循环等待,如调整加锁顺序,就可以避免循环等待
现在举一个哲学家吃面的案例:一共有五个哲学家,一共5个筷子.
哲学家只做两件事情:吃面或者思考人生(阻塞等待).
如果所有的哲学家都先拿右手的筷子,然后再尝试拿左手的筷子的时候,发现左手的筷子都被占用了,全部都会阻塞得不到筷子,所以就会产生死锁.
现在我们重新安排一下,就可以避免死锁的问题.
让每个哲学家先拿身边编号小的筷子,然后再拿身边编号相对大的筷子.
这样哲学家1和哲学家5就会先拿筷子1,比如哲学家1争抢到了筷子1(此时哲学家5处在阻塞等待状态),哲学家2拿到筷子2,哲学家3拿到筷子3,哲学家4可以拿到筷子4和筷子5,哲学家4吃完之后释放筷子,哲学家3,2,1依次吃面,最后哲学家5拿到筷子吃饭,就可以避免死锁的问题.
场景:多个班级根据各班的人数订制校服
public class Demo12_ThreadLocal { // 初始化一个ThreadLocal private static ThreadLocal threadLocal = new ThreadLocal<>(); public static void main(String[] args) { // 多个线程分别去统计人数 Thread thread1 = new Thread(() -> { // 统计人数 int count = 35; threadLocal.set(count);// Integer value = threadLocal.get();// System.out.println(value); // 订制校服 print(); }, "threadNameClass1"); Thread thread2 = new Thread(() -> { // 统计人数 int count = 40; threadLocal.set(count);// Integer value = threadLocal.get();// System.out.println(value); // 订制校服 print(); }, "threadNameClass2"); thread1.start(); thread2.start(); } // 订制校服 public static void print() { // 从threadLocal中获取值 Integer value = threadLocal.get(); System.out.println(Thread.currentThread().getName() + " : 需要订制 " + value + "套校服."); }}
相当于一个map,以当前线程作为key,值为value保存
来源地址:https://blog.csdn.net/qq_64580912/article/details/130668183
--结束END--
本文标题: Java之多线程进阶
本文链接: https://lsjlt.com/news/385585.html(转载时请注明来源链接)
有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341
2024-04-01
2024-04-03
2024-04-03
2024-01-21
2024-01-21
2024-01-21
2024-01-21
2023-12-23
回答
回答
回答
回答
回答
回答
回答
回答
回答
回答
0