juc是java.util.concurrent(java并发编程)工具包的简称,这是一个处理线程的工具包,jdk1.5开始出现
进程进程是计算机中程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础。在当代面向线程设计的计算机结构中,进程是线程的容器.
线程线程(thread) 是操作系统能够进行运算调度的最小单位。它被包含在进程之 中,是进程中的实际运作单位。一条线程指的是进程中一个单一顺序的控制流, 一个进程中可以并发多个线程,每条线程并行执行不同的任务。
枚举类6种:
Thread.State
public enum State { /** * Thread state for a thread which has not yet started. */
NEW,(新建)
/** * Thread state for a runnable thread. A thread in the runnable * state is executing in the Java virtual machine but it may * be waiting for other resources from the operating system * such as processor. */
RUNNABLE,(准备就绪)
/** * Thread state for a thread blocked waiting for a monitor lock. * A thread in the blocked state is waiting for a monitor lock * to enter a synchronized block/method or * reenter a synchronized block/method after calling * {@link Object#wait() Object.wait}. */
BLOCKED,(阻塞)
/** * Thread state for a waiting thread. * A thread is in the waiting state due to calling one of the * following methods: * * - {@link Object#wait() Object.wait} with no timeout
* - {@link #join() Thread.join} with no timeout
* - {@link LockSupport#park() LockSupport.park}
*
* * A thread in the waiting state is waiting for another thread to * perform a particular action. * * For example, a thread that has called Object.wait() * on an object is waiting for another thread to call * Object.notify() or Object.notifyAll() on * that object. A thread that has called Thread.join() * is waiting for a specified thread to terminate. */
WAITING,(不见不散)
/** * Thread state for a waiting thread with a specified waiting time. * A thread is in the timed waiting state due to calling one of * the following methods with a specified positive waiting time: * * - {@link #sleep Thread.sleep}
* - {@link Object#wait(long) Object.wait} with timeout
* - {@link #join(long) Thread.join} with timeout
* - {@link LockSupport#parkNanos LockSupport.parkNanos}
* - {@link LockSupport#parkUntil LockSupport.parkUntil}
*
*/
TIMED_WAITING,(过时不候)
/** * Thread state for a terminated thread. * The thread has completed execution. */
TERMINATED;(终结) }
(1)sleep是Thread的静态方法,wait是Object的方法,任何对象实例都 能调用。
(2)sleep不会释放锁,它也不需要占用锁。wait会释放锁,但调用它的前提 是当前线程占有锁(即代码要在synchronized中)。
(3)它们都可以被interrupted方法中断。
串行表示所有任务都一一按先后顺序进行。串行意味着必须先装完一车柴才能 运送这车柴,只有运送到了,才能卸下这车柴,并且只有完成了这整个三个步 骤,才能进行下一个步骤。
串行是一次只能取得一个任务,并执行这个任务。
并行意味着可以同时取得多个任务,并同时去执行所取得的这些任务。并行模 式相当于将长长的一条队列,划分成了多条短队列,所以并行缩短了任务队列的长度。并行的效率从代码层次上强依赖于多进程/多线程代码,从硬件角度上 则依赖于多核CPU。
并发(concurrent)指的是多个程序可以同时运行的现象,更细化的是多进程可 以同时运行或者多指令可以同时运行。但这不是重点,在描述并发的时候也不 会去扣这种字眼是否精确,并发的重点在于它是一种现象, 并发描述 的是多进程同时运行的现象。但实际上,对于单核心CPU来说,同一时刻 只能运行一个线程。所以,这里的"同时运行"表示的不是真的同一时刻有多个 线程运行的现象,这是并行的概念,而是提供一种功能让用户看来多个程序同 时运行起来了,但实际上这些程序中的进程不是一直霸占CPU的,而是执行一 会停一会。
要解决大并发问题,通常是将大任务分解成多个小任务, 由于操作系统对进程的 调度是随机的,所以切分成多个小任务后,可能会从任一小任务处执行。这可 能会出现一些现象:
• 可能出现一个小任务执行了多次,还没开始下个任务的情况。这时一般会采用 队列或类似的数据结构来存放各个小任务的成果 • 可能出现还没准备好第一步就执行第二步的可能。这时,一般采用多路复用或 异步的方式,比如只有准备好产生了事件通知才执行某个任务。 • 可以多进程/多线程的方式并行执行这些小任务。也可以单进程/单线程执行这 些小任务,这时很可能要配合多路复用才能达到较高的效率
管程(monitor)是保证了同一时刻只有一个进程在管程内活动,即管程内定义的操作在同 一时刻只被一个进程调用(由编译器实现).但是这样并不能保证进程以设计的顺序执行
JVM中同步是基于进入和退出管程(monitor)对象实现的,每个对象都会有一个管程 (monitor)对象,管程(monitor)会随着java对象一同创建和销毁 ,执行线程首先要持有管程对象,然后才能执行方法,当方法完成之后会释放管程,方 法在执行时候会持有管程,其他线程无法再获取同一个管程
==用户线程:==平时用到的普通线程,自定义线程
==守护线程:==运行在后台,是一种特殊的线程,比如垃圾回收 当主线程结束后,用户线程还在运行,JVM存活 如果没有用户线程,都是守护线程,JVM结束
synchronized 是 Java 中的关键字,是一种同步锁。它修饰的对象有以下几种:
package sync;
/**
* @author Francis
* @create 2022-03-06 7:58
*/
class Ticket { // 票 数
private int number = 30;
// 操作方法: 卖 票
public synchronized void sale() {
// 判 断 :是否有票
if(number > 0) {
System.out.println(Thread.currentThread().getName()+" : "+(30-number)+
" "+number--); }
}
}
package sync;
/**
* @author Francis
* @create 2022-03-06 7:58
*/
public class TicketSall {
public static void main(String[] args) {
Ticket ticket = new Ticket();
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0;i<40;i++){
ticket.sale();
}
}
},"AA").start();
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0;i<40;i++){
ticket.sale();
}
}
},"BB").start();
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0;i<40;i++){
ticket.sale();
}
}
},"CC").start();
}
}
如果一个代码块被synchronized修饰了,当一个线程获取了对应的锁,并执 行该代码块时,其他线程便只能一直等待,等待获取锁的线程释放锁,而这里 获取锁的线程释放锁只会有两种情况:
1)获取锁的线程执行完了该代码块,然后线程释放对锁的占有;
2)线程执行发生异常,此时JVM会让线程自动释放锁。
那么如果这个获取锁的线程由于要等待IO或者其他原因(比如调用sleep 方法)被阻塞了,但是又没有释放锁,其他线程便只能干巴巴地等待,试想一 下,这多么影响程序执行效率。
因此就需要有一种机制可以不让等待的线程一直无期限地等待下去(比如只等 待一定的时间或者能够响应中断),通过Lock就可以办到。
Lock锁实现提供了比使用同步方法和语句可以获得的更广泛的锁操作。它们允 许更灵活的结构,可以具有非常不同的属性,并且可能支持多个关联的条件对 象。Lock提供了比synchronized更多的功能。
==Lock与的Synchronized区别 ==
• Lock不是Java语言内置的,synchronized是Java语言的关键字,因此是内 置特性。Lock是一个类,通过这个类可以实现同步访问;
• Lock和synchronized有一点非常大的不同,采用synchronized不需要用户 去手动释放锁,当 synchronized 方法或者synchronized 代码块执行完之后, 系统会自动让线程释放对锁的占用;而Lock则必须要用户去手动释放锁,如 果没有主动释放锁,就有可能导致出现死锁现象。
lock()方法是平常使用得最多的一个方法,就是用来获取锁。如果锁已被其他 线程获取,则进行等待。
采用Lock,必须主动去释放锁,并且在发生异常时,不会自动释放锁。因此一 般来说,使用Lock必须在try{}catch{}块中进行,并且将释放锁的操作放在 finally块中进行,以保证锁一定被被释放,防止死锁的发生。通常使用Lock 来进行同步的话,是以下面这种形式去使用的:
Lock lock = ...; lock.lock();
try{
//处理任务
}catch(Exception ex)
{
}finally{
//释放锁
lock.unlock();
}
关键字synchronized与wait()/notify()这两个方法一起使用可以实现等待/通 知模式, Lock锁的newContition()方法返回Condition对象,Condition类 也可以实现等待/通知模式。 用notify()通知时,JVM会随机唤醒某个等待的线程, 使用Condition类可以 进行选择性通知, Condition比较常用的两个方法:
• await()会使当前线程等待,同时会释放锁,当其他线程调用signal()时,线程会重 新获得锁并继续执行。
• signal()用于唤醒一个等待的线程。
注意:在调用Condition的await()/signal()方法前,也需要线程持有相关 的Lock锁,调用await()后线程会释放这个锁,在singal()调用后会从当前 Condition对象的等待队列中,唤醒 一个线程,唤醒的线程尝试获得锁, 一旦 获得锁成功就继续执行。
ReentrantLock是唯一实现了Lock接口的类,并且ReentrantLock提供了更 多的方法。下面通过一些实例看具体看一下如何使用。
线程间通信的模型有两种:共享内存和消息传递,以下方式都是基本这两种模 型来实现的。我们来基于一道面试常见的题目来分析
场景—两个线程,一个线程对当前数值加1,另一个线程对当前数值减1,要求 用线程间通信
package communication;
/**
* @author Francis
* @create 2022-03-07 13:57
*/
public class Share {
private int number = 0;
public synchronized void incr() throws InterruptedException {
//if (number!=0){
// //释放锁
// this.wait();//在哪里睡,就在哪里醒,下一次再抢到锁,不会进行判断,而是直接走下面的代码
//}
//解决方案,将wait写到while循环中
while (number!=0){
//释放锁
this.wait();
}
number++;
System.out.println(Thread.currentThread().getName()+number);
//唤醒其他线程
this.notifyAll();
}
public synchronized void decr() throws InterruptedException {
//if (number!=0){
// //释放锁
// this.wait();//在哪里睡,就在哪里醒,下一次再抢到锁,不会进行判断,而是直接走下面的代码
//}
//解决方案,将wait写到while循环中
while (number!=1){
//释放锁
this.wait();
}
number--;
System.out.println(Thread.currentThread().getName()+number);
//唤醒其他线程
this.notifyAll();
}
}
package communication;
/**
* @author Francis
* @create 2022-03-07 14:02
*/
public class ThreadDemo01 {
public static void main(String[] args) {
Share share = new Share();
new Thread(() -> {
for (int i = 0; i < 10; i++){
try {
share.incr();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"AA").start();
new Thread(() -> {
for (int i = 0; i < 10; i++){
try {
share.decr();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"BB").start();
new Thread(() -> {
for (int i = 0; i < 10; i++){
try {
share.incr();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"CC").start();
new Thread(() -> {
for (int i = 0; i < 10; i++){
try {
share.decr();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"DD").start();
}
}
package communication;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* @author Francis
* @create 2022-03-07 14:46
*/
public class Share02 {
private int number = 0;
//声明锁
private Lock lock = new ReentrantLock();
//声明钥匙
private Condition condition = lock.newCondition();
public void incr() throws InterruptedException {
lock.lock();
try {
while (number!=0){
condition.await();
}
number++;
System.out.println(Thread.currentThread().getName()+number);
condition.signalAll();
} finally {
lock.unlock();
}
}
public void decr() throws InterruptedException {
lock.lock();
try {
while (number!=1){
condition.await();
}
number--;
System.out.println(Thread.currentThread().getName()+number);
condition.signalAll();
} finally {
lock.unlock();
}
}
}
package communication;
/**
* @author Francis
* @create 2022-03-07 14:46
*/
public class ThreadDemo02 {
public static void main(String[] args) {
Share02 share02 = new Share02();
new Thread(()->{
for (int i = 0; i < 10; i++) {
try {
share02.incr();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"AA").start();
new Thread(()->{
for (int i = 0; i < 10; i++) {
try {
share02.decr();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"BB").start();
new Thread(()->{
for (int i = 0; i < 10; i++) {
try {
share02.incr();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"CC").start();
new Thread(()->{
for (int i = 0; i < 10; i++) {
try {
share02.decr();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"DD").start();
}
}
问题: A线程打印5次A,B线程打印10次B,C线程打印15次C,按照 此顺序循环10轮
package communication;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* @author Francis
* @create 2022-03-07 15:36
*/
public class Share03 {
private int flag = 1;
private Lock lock = new ReentrantLock();
//创建三把钥匙
private Condition c1 = lock.newCondition();
private Condition c2 = lock.newCondition();
private Condition c3 = lock.newCondition();
//打印5次
public void print5(int loop) throws InterruptedException {
lock.lock();
try {
while (flag!=1){
c1.await();
}
for (int i = 0; i < 5; i++) {
System.out.println(Thread.currentThread().getName()+"第"+loop+
"轮"+i);
}
flag=2;
c2.signal();
}finally {
lock.unlock();
}
}
public void print10(int loop) throws InterruptedException {
lock.lock();
try {
while (flag!=2){
c2.await();
}
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName()+"第"+loop+
"轮"+i);
}
flag=3;
c3.signal();
}finally {
lock.unlock();
}
}
public void print15(int loop) throws InterruptedException {
lock.lock();
try {
while (flag!=3){
c3.await();
}
for (int i = 0; i < 15; i++) {
System.out.println(Thread.currentThread().getName()+"第"+loop+
"轮"+i);
}
flag=1;
c1.signal();
}finally {
lock.unlock();
}
}
}
package communication;
/**
* @author Francis
* @create 2022-03-07 15:35
*/
public class ThreadDemo03 {
public static void main(String[] args) {
Share03 share03 = new Share03();
new Thread(()->{
for (int i = 0; i < 10; i++) {
try {
share03.print5(i);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"AA").start();
new Thread(()->{
for (int i = 0; i < 10; i++) {
try {
share03.print10(i);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"BB").start();
new Thread(()->{
for (int i = 0; i < 10; i++) {
try {
share03.print15(i);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"CC").start();
}
}
ConcurrentModificationExceptio
package lock;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
/**
* @author Francis
* @create 2022-03-07 20:07
*/
public class NotSafeDemo {
public static void main(String[] args) {
List list = new ArrayList();
for (int i = 0; i < 100; i++) {
new Thread(() -> {
list.add(UUID.randomUUID().toString());
System.out.println(list);
}, "线程" + i).start();
}
}
}
Vector 是矢量队列,它是JDK1.0版本添加的类。继承于AbstractList,实现 了List, RandomAccess, Cloneable 这些接口。 Vector 继承了AbstractList, 实现了List;所以,它是一个队列,支持相关的添加、删除、修改、遍历等功 能。 Vector 实现了RandmoAccess接口,即提供了随机访问功能。 RandmoAccess是java中用来被List实现,为List提供快速访问功能的。在 Vector中,我们即可以通过元素的序号快速获取元素对象;这就是快速随机访 问。 Vector 实现了Cloneable接口,即实现clone()函数。它能被克隆。
和ArrayList不同,Vector中的操作是线程安全的。
==Collections提供了方法synchronizedList保证list是同步线程安全的 ==
package lock;
import java.util.*;
/**
* @author Francis
* @create 2022-03-07 20:07
*/
public class NotSafeDemo {
public static void main(String[] args) {
//List list = new ArrayList();
//List list = new Vector();
List list = Collections.synchronizedList(new ArrayList<>());
for (int i = 0; i < 100; i++) {
new Thread(() -> {
list.add(UUID.randomUUID().toString());
System.out.println(list);
}, "线程" + i).start();
}
}
}
首先我们对CopyOnWriteArrayList进行学习,其特点如下:
它相当于线程安全的ArrayList。和ArrayList一样,它是个可变数组;但是和 ArrayList不同的时,它具有以下特性:
package lock;
import java.util.*;
import java.util.concurrent.CopyOnWriteArrayList;
/**
* @author Francis
* @create 2022-03-07 20:07
*/
public class NotSafeDemo {
public static void main(String[] args) {
//List list = new ArrayList();
//List list = new Vector();
//List list = Collections.synchronizedList(new ArrayList<>());
List list = new CopyOnWriteArrayList();
for (int i = 0; i < 100; i++) {
new Thread(() -> {
list.add(UUID.randomUUID().toString());
System.out.println(list);
}, "线程" + i).start();
}
}
}
使用CopyOnWriteArraySet
class Phone {
public static synchronized void sendSMS() throws Exception {
// 停留 4 秒
TimeUnit.SECONDS.sleep(4);
System.out.println("------sendSMS"); }
public synchronized void sendEmail() throws Exception { System.out.println("------sendEmail"); }
public void getHello() {
System.out.println("------getHello"); }
}
/** * @Description: 8锁 * 1 标准访问,先打印短信还是邮件
------sendSMS ------sendEmail
2 停 4秒在短信方法内,先打印短信还是邮件
------sendSMS ------sendEmail
3 新增普通的 hello方法,是先打短信还是 hello
------getHello ------sendSMS
4 现在有两部手机,先打印短信还是邮件
------sendEmail ------sendSMS
5 两个静态同步方法,1部手机,先打印短信还是邮件
------sendSMS ------sendEmail
6 两个静态同步方法,2部手机,先打印短信还是邮件
------sendSMS ------sendEmail
7 1个静态同步方法,1个普通同步方法,1部手机,先打印短信还是邮件 ------sendEmail ------sendSMS
8 1个静态同步方法,1个普通同步方法,2部手机,先打印短信还是邮件 ------sendEmail ------sendSMS
结论:
一个对象里面如果有多个synchronized方法,某一个时刻内,只要一个线程去调用其中的 一个synchronized方法了, 其它的线程都只能等待,换句话说,某一个时刻内,只能有唯一一个线程去访问这些 synchronized方法 锁的是当前对象this,被锁定后,其它的线程都不能进入到当前对象的其它的 synchronized方法 加个普通方法后发现和同步锁无关 换成两个对象后,不是同一把锁了,情况立刻变化。 synchronized实现同步的基础:Java中的每一个对象都可以作为锁。 具体表现为以下3种形式。 对于普通同步方法,锁是当前实例对象。 对于静态同步方法,锁是当前类的Class对象。 对于同步方法块,锁是Synchonized括号里配置的对象 当一个线程试图访问同步代码块时,它首先必须得到锁,退出或抛出异常时必须释放锁。 也就是说如果一个实例对象的非静态同步方法获取锁后,该实例对象的其他非静态同步方 法必须等待获取锁的方法释放锁后才能获取锁, 可是别的实例对象的非静态同步方法因为跟该实例对象的非静态同步方法用的是不同的锁, 所以毋须等待该实例对象已获取锁的非静态同步方法释放锁就可以获取他们自己的锁。 所有的静态同步方法用的也是同一把锁——类对象本身,这两把锁是两个不同的对象,所 以静态同步方法与非静态同步方法之间是不会有竞态条件的。 但是一旦一个静态同步方法获取锁后,其他的静态同步方法都必须等待该方法释放锁后才 能获取锁,而不管是同一个实例对象的静态同步方法之间,还是不同的实例对象的静态同 步方法之间,只要它们同一个类的实例对象!
多个线程按照申请锁的顺序去获得锁,线程会直接进入队列去排队,永远都是队列的第一位才能得到锁。
优点:所有的线程都能得到资源,不会饿死在队列中。
缺点:吞吐量会下降很多,队列里面除了第一个线程,其他的线程都会阻塞,cpu唤醒阻塞线程的开销会很大。
多个线程去获取锁的时候,会直接去尝试获取,获取不到,再去进入等待队列,如果能获取到,就直接获取到锁。new ReentrantLock(false);就是一个非公平锁
优点:可以减少CPU唤醒线程的开销,整体的吞吐效率会高点,CPU也不必取唤醒所有线程,会减少唤起线程的数量。
缺点:你们可能也发现了,这样可能导致队列中间的线程一直获取不到锁或者长时间获取不到锁,导致饿死。
可重入锁,指的是以线程为单位,当一个线程获取对象锁之后,这个线程可以再次获取本对象上的锁,而其他的线程是不可以的。
synchronized 和 ReentrantLock 都是可重入锁。
可重入锁的意义之一在于防止死锁。
实现原理实现是通过为每个锁关联一个请求计数器和一个占有它的线程。当计数为0时,认为锁是未被占有的;线程请求一个未被占有的锁时,JVM将记录锁的占有者,并且将请求计数器置为1 。
如果同一个线程再次请求这个锁,计数器将递增;
每次占用线程退出同步块,计数器值将递减。直到计数器为0,锁被释放。
例子:
比如说A类中有个方法public synchronized methodA1(){
methodA2();
}
而且public synchronized methodA2(){
//具体操作
}
也是A类中的同步方法,当当前线程调用A类的对象methodA1同步方法,如果其他线程没有获取A类的对象锁,那么当前线程就获得当前A类对象的锁,然后执行methodA1同步方法,方法体中调用methodA2同步方法,当前线程能够再次获取A类对象的锁,而其他线程是不可以的,这就是可重入锁。
synchronized是隐式的可重入锁,Lock是显式的可重入锁
/**
* @author Francis
* @create 2022-03-07 22:11
*/
public class SyncLockDemo {
public static void main(String[] args) {
Object o = new Object();
new Thread(()->{
synchronized (o){
System.out.println(Thread.currentThread().getName()+"外层");
synchronized (o){
System.out.println(Thread.currentThread().getName()+"中层");
synchronized (o){
System.out.println(Thread.currentThread().getName()+
"内层");
}
}
}
},"t1").start();
}
}
import java.util.concurrent.locks.ReentrantLock;
/**
* @author Francis
* @create 2022-03-07 22:16
*/
public class SyncLockDemo02 {
public static void main(String[] args) {
ReentrantLock lock = new ReentrantLock();
new Thread(()->{
try {
lock.lock();
System.out.println(Thread.currentThread().getName()+"外层");
try {
lock.lock();
System.out.println(Thread.currentThread().getName()+"内层");
}finally {
lock.unlock();
}
}finally {
lock.unlock();
}
}).start();
}
}
死锁是指两个或两个以上的进程在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象,若无外力作用,它们都将无法推进下去。
产生条件
虽然进程在运行过程中,可能发生死锁,但死锁的发生也必须具备一定的条件,死锁的发生必须具备以下四个必要条件。
1)互斥条件:指进程对所分配到的资源进行排它性使用,即在一段时间内某资源只由一个进程占用。如果此时还有其它进程请求资源,则请求者只能等待,直至占有资源的进程用毕释放。
2)请求和保持条件:指进程已经保持至少一个资源,但又提出了新的资源请求,而该资源已被其它进程占有,此时请求进程阻塞,但又对自己已获得的其它资源保持不放。
3)不剥夺条件:指进程已获得的资源,在未使用完之前,不能被剥夺,只能在使用完时由自己释放。
4)环路等待条件:指在发生死锁时,必然存在一个进程——资源的环形链,即进程集合{P0,P1,P2,···,Pn}中的P0正在等待一个P1占用的资源;P1正在等待P2占用的资源,……,Pn正在等待已被P0占用的资源。
这是一个可能产生死锁的例子
/**
* @author Francis
* @create 2022-03-07 23:24
*/
public class DeadLockDemo {
static Object a = new Object();
static Object b = new Object();
public static void main(String[] args) {
new Thread(() -> {
synchronized (a) {
System.out.println(Thread.currentThread().getName()+"持有锁a" +
",试图获取锁b");
synchronized (b){
System.out.println(Thread.currentThread().getName()+"获取b");
}
}
},"A").start();
new Thread(() -> {
synchronized (b) {
System.out.println(Thread.currentThread().getName()+"持有锁b" +
",试图获取锁a");
synchronized (a){
System.out.println(Thread.currentThread().getName()+"获取a");
}
}
},"B").start();
}
}