一、线程和进程
1.进程:一个程序,如qq.exe、music.exe程序的集合
- 一个进程往往可以包含多个线程,至少含有一个进程
- java默认有两个线程,main 和 GC
- 线程:程序里面的一个执行方式 如 发送、保存等
Java三种方式: Thread、Runnable、Callable
public synchronized void start() {
/**
* This method is not invoked for the main method thread or "system"
* group threads created/set up by the VM. Any new functionality added
* to this method in the future may have to also be added to the VM.
*
* A zero status value corresponds to state "NEW".
*/
if (threadStatus != 0)
throw new IllegalThreadStateException();
/* Notify the group that this thread is about to be started
* so that it can be added to the group's list of threads
* and the group's unstarted count can be decremented. */
group.add(this);
boolean started = false;
try {
start0();
started = true;
} finally {
try {
if (!started) {
group.threadStartFailed(this);
}
} catch (Throwable ignore) {
/* do nothing. If start0 threw a Throwable then
it will be passed up the call stack */
}
}
}
//java没办法自己开启,调用底层C++操作操作系统
private native void start0();
二、并发、并行
1.并发(多线程操作同一个资源)
- cpu一核,模拟出来多条线程,快速交替 2.并行(多个人一起行走)
- cpu多核,多个线程同时执行:线程池
public static void main(String[] args) {
//获取cpu的核数
//cpu密集型 IO密集型
System.out.println(Runtime.getRuntime().availableProcessors());
}
并发编程的本质:充分利用cpu的资源
public enum State {
//新生
NEW,
//运行
RUNNABLE,
//阻塞
BLOCKED,
//等待
WAITING,
//超时等待
TIMED_WAITING,
//终止
TERMINATED;
}
三、wait/sleep区别 1.来自不同的类
wait => Object
sleep=> Thread
2.关锁的释放 wait会释放锁,sleep 睡觉了,不会是释放
3.使用的范围是不同的
wait必须在同步代码块中,不用捕获异常
sleep 可以在任何地方,必须捕获异常
四、LOCK锁
1.传统synchronized 和 Lock区别
- synchronized 内置java关键字 , Lock是一个java类
- synchronized 无法判断获取锁的状态 lock 可以判断是否获取到了锁
- synchronized 自动释放锁 lock手动释放锁
- synchronized 线程1阻塞 线程2一直等 lock不一定会等
- synchronized 可重入锁,不可中断 非公平 lock 可重入锁可以判断锁 非公平(可以自己设置)
- synchronized 适合锁少量的代码同步问题 lock适合锁大量的同步代码
锁 将资源私有化
五、8锁现象
package com.cxx.juc.day003;
import java.util.concurrent.TimeUnit;
/**
* 8锁,就是关于锁的8个问题
* 1.标准情况下,两个线程先打印是先发短信还是打电话
* 2.发短信延迟4s,两个线程先打印是先发短信还是打电话
* 3.添加一个普通方法,先发短信还是hello
* 4.两个对象,两个同步方法,发短信还是打电话
* 5.增加两个静态的同步方法 一个对象 先打印还是发短信
* 6.两个对象 增加两个静态的同步方法,先打印还是打电话
* 7.一个静态方法。1个普通方法 ,一个对象 先打印 发短信还是打电话
* 8.1个静态的同步方法,1个普通的同步方法,先打印发短信还是打电话
* @author cxx
* @version 1.0
* @date 2021/12/20 9:23
*/
public class lock4 {
public static void main(String[] args) {
//两个对象两把锁,按延迟时间来
phone4 phone = new phone4();
new Thread(()->{
phone.sendSms();
},"A").start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(()->{
phone.hello();
},"B").start();
}
}
class phone4{
//synchronized 锁的对象是方法的调用者
// 两个方法用的是同一个问题 ,谁先拿到谁执行
// static 静态方法 类一加载就有了 锁的是class
public static synchronized void sendSms(){
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("发短信");
}
public static synchronized void call(){
System.out.println("打电话");
}
//这里没有锁 不是同步方法,不受锁的影响
public synchronized void hello(){
System.out.println("hello");
}
}
小结:
- new this 具体的一个手机
- static Class 唯一的一个模板
六、集合类不安全
package com.cxx.juc.day004;
import java.util.*;
import java.util.concurrent.CopyOnWriteArrayList;
/**
* java.util.ConcurrentModificationException 并发修改异常
* 并发下ArrayList 不安全
* 解决方案
* 1.List<String> list = new Vector<>(); 效率低
* 2. Collection<String> list = Collections.synchronizedCollection(
* new ArrayList<>()
* );
* 3,List<String> list = new CopyOnWriteArrayList<>();
* @author cxx
* @version 1.0
* @date 2021/12/20 10:25
*/
public class ListTest {
public static void main(String[] args) {
// Collection<String> list = Collections.synchronizedCollection(
// new ArrayList<>()
// );
// List<String> list = new Vector<>();
//CopyOnWrite 写入时复制 cow 计算机程序设计领域的一种优化策略
// 多个线程调用的时候,list,读取的时候,固定的 ,写入(覆盖)
// 在写入的时候避免覆盖(复制),造成数据问题!
List<String> list = new CopyOnWriteArrayList<>();
for (int i = 0; i < 10 ; i++) {
new Thread(()->{
list.add(UUID.randomUUID().toString().substring(0,5));
System.out.println(list);
},String.valueOf(i)).start();
}
}
}
hashset的底层是什么
public HashSet() {
map = new HashMap<>();
}
//add set 本质就是map key是无法重复的
public boolean add(E e) {
return map.put(e, PRESENT)==null;
}
//不变的值
private static final Object PRESENT = new Object(); 、
七、callable
package com.cxx.juc.day004;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
/**
* TODO
*
* @author cxx
* @version 1.0
* @date 2021/12/20 13:48
*/
public class CallableTest {
public static void main(String[] args) throws ExecutionException, InterruptedException {
MyThread thread = new MyThread();
FutureTask futureTask = new FutureTask<>(thread);
new Thread(futureTask,"A").start();//适配类
new Thread(futureTask,"B").start();
Integer o = (Integer) futureTask.get(); //这个get可能引起阻塞,把他放到最后,或者用异步
System.out.println(o);
}
}
class MyThread implements Callable<Integer>{
@Override
public Integer call() throws Exception {
System.out.println("call");
return 1024;
}
}
细节: 1.有缓存 2.结构可能需要等,会阻塞
package com.cxx.juc.day004;
import java.util.concurrent.CountDownLatch;
/**
* 计数器
*
* @author cxx
* @version 1.0
* @date 2021/12/20 15:38
*/
public class CountDownLatchDemo {
public static void main(String[] args) throws InterruptedException {
CountDownLatch countDownLatch = new CountDownLatch(6);
for (int i = 0; i <=6 ; i++) {
new Thread(()->{
System.out.println(Thread.currentThread().getName()+" get out");
long count = countDownLatch.getCount();
countDownLatch.countDown(); //数量 -1
},String.valueOf(i)).start();
}
countDownLatch.await(); //等待计数器归0 ,然后向下执行
System.out.println("close door");
}
}
原理:
countDownLatch.countDown(); //数量 -1
countDownLatch.await(); //等待计数器归0 ,然后向下执行
每次有线程用countDown()数量-1,假设计数器变为0,countDownLatch.await()就会被唤醒,继续执行
package com.cxx.juc.day004;
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;
/**
* 加法计数器
*
* @author cxx
* @version 1.0
* @date 2021/12/20 15:57
*/
public class CyclicBarrierDemo {
public static void main(String[] args) {
CyclicBarrier cyclicBarrier = new CyclicBarrier(7,()->{
System.out.println("召唤神龙成功");
});
for (int i = 1; i <= 7 ; i++) {
int finalI = i;
new Thread(()->{
System.out.println(Thread.currentThread().getName()+"收集" + finalI +"个龙珠");
try {
cyclicBarrier.await(); //等待
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
}).start();
}
}
}
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
/**
* TODO
*
* @author cxx
* @version 1.0
* @date 2021/12/20 16:08
*/
public class SemaphoreDemo {
public static void main(String[] args) {
Semaphore semaphore = new Semaphore(3);
for (int i = 0; i < 6 ; i++) {
new Thread(()->{
try {
semaphore.acquire();//得到
System.out.println(Thread.currentThread().getName() + "抢到车位");
TimeUnit.SECONDS.sleep(2);
System.out.println(Thread.currentThread().getName() + "离开车位");
} catch (InterruptedException e) {
e.printStackTrace();
}
semaphore.release();//释放
},String.valueOf(i)).start();
}
}
}
semaphore.acquire(); 获得,假设如果已经蛮了,等待,等待被释放为止
semaphore.release();释放,会将当前的信号量+1,然后唤醒等待的线程
九、读写锁
package com.cxx.juc.day004;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
/**
* 写锁 (独占锁)一次只能被一些线程占有
* 读锁 (共享锁)多个线程可以同时占有
* 读-读 可以共存
* 读-写 不能共存
* 写-写 不能共存
* @author cxx
* @version 1.0
* @date 2021/12/20 16:24
*/
public class ReadWriterLockDemo {
public static void main(String[] args) {
MyCache myCache = new MyCache();
for (int i = 0; i < 6; i++) {
int finalI = i;
new Thread(()->{
myCache.put(finalI+"",finalI);
},String.valueOf(i)).start();
}
for (int i = 0; i < 6; i++) {
int finalI = i;
new Thread(()->{
myCache.get(finalI+"");
},String.valueOf(i)).start();
}
}
}
class MyCache{
private volatile Map<String,Object> map = new HashMap<>();
//读写锁,更加细粒度的控制
private ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
//存 、写入的时候,只希望同时只有一个线程写
public void put(String key,Object value){
readWriteLock.writeLock().lock();
System.out.println(Thread.currentThread().getName() + "写入" +key);
map.put(key,value);
System.out.println(Thread.currentThread().getName() + "写入OK");
readWriteLock.writeLock().unlock();
}
//所有人都可以读
public void get(String key){
readWriteLock.readLock().lock();
System.out.println(Thread.currentThread().getName() + "读取" +key);
map.get(key);
System.out.println(Thread.currentThread().getName() + "读取OK");
readWriteLock.readLock().unlock();
}
}
10、阻塞队列
11、线程池
三大方法、7大参数、4种拒绝策略
池化技术
程序的运行,本质:占用系统的资源!优化资源的使用!
线程池、连接池、内存池、对象池
池化技术:事先准备好一些资源,有人要用,来我这里拿,用完之后还给我
线程池的好处:
1.降低资源的消耗
2.提高响应的速度
3.方便管理
线程复用、可以控制最大并发数、管理线程
三大方法:
package com.cxx.juc.day005;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* TODO
*
* @author cxx
* @version 1.0
* @date 2021/12/21 9:54
*/
public class pooldemo1 {
public static void main(String[] args) {
// ExecutorService pool = Executors.newSingleThreadExecutor();//单个线程
// ExecutorService pool =Executors.newFixedThreadPool(5);//创建一个固定的线程池的大小
ExecutorService pool =Executors.newCachedThreadPool();//可伸缩的,
try {
for (int i = 0; i < 30 ; i++) {
//使用线程池创建线程
pool.execute(()->{
System.out.println(Thread.currentThread().getName() + " okk");
});
}
} finally {
//线程池用完关闭
pool.shutdown();
}
}
}
newSingleThreadExecutor
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
newFixedThreadPool
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
newCachedThreadPool
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
七大参数
public ThreadPoolExecutor(int corePoolSize, //核心线程
int maximumPoolSize, //最大核心线程大小
long keepAliveTime, //最大存活时间
TimeUnit unit, // 超时单位
BlockingQueue<Runnable> workQueue, //阻塞队列
ThreadFactory threadFactory, // 线程工厂,创建线程的,一般不动
RejectedExecutionHandler handler // 拒绝策略) {
if (corePoolSize < 0 ||
maximumPoolSize <= 0 ||
maximumPoolSize < corePoolSize ||
keepAliveTime < 0)
throw new IllegalArgumentException();
if (workQueue == null || threadFactory == null || handler == null)
throw new NullPointerException();
this.acc = System.getSecurityManager() == null ?
null :
AccessController.getContext();
this.corePoolSize = corePoolSize;
this.maximumPoolSize = maximumPoolSize;
this.workQueue = workQueue;
this.keepAliveTime = unit.toNanos(keepAliveTime);
this.threadFactory = threadFactory;
this.handler = handler;
}
四种拒绝策略
new ThreadPoolExecutor.AbortPolicy(); // 满了抛出一次
new ThreadPoolExecutor.CallerRunsPolicy(); //哪里来的去哪里
new ThreadPoolExecutor.DiscardOldestPolicy(); //队列满了,丢掉任务,不会抛出异常
new ThreadPoolExecutor.DiscardPolicy(); //队列满了,尝试去和最早的竞争,也不会抛出异常
小结和拓展 池最大小如何去设置 //1.cpu 密集型 几核 就是几,可以保持cpu的效率最高 Runtime.getRuntime().availableProcessors(), //2.IO 密集型 > 判断你的程序中十分耗IO的线程
package com.cxx.juc.day005;
import java.util.concurrent.*;
/**
* TODO
*
* @author cxx
* @version 1.0
* @date 2021/12/21 9:54
*/
public class pooldemo1 {
public static void main(String[] args) {
// ExecutorService pool = Executors.newSingleThreadExecutor();//单个线程
// ExecutorService pool =Executors.newFixedThreadPool(5);//创建一个固定的线程池的大小
// ExecutorService pool =Executors.newCachedThreadPool();//可伸缩的,.
// new ThreadPoolExecutor.AbortPolicy();
// new ThreadPoolExecutor.CallerRunsPolicy();
// new ThreadPoolExecutor.DiscardOldestPolicy();
// new ThreadPoolExecutor.DiscardPolicy();
//最大线程数该如何定义
//1.cpu 密集型 几核 就是几,可以保持cpu的效率最高 Runtime.getRuntime().availableProcessors(),
//2.IO 密集型 > 判断你的程序中十分耗IO的线程
ThreadPoolExecutor pool = new ThreadPoolExecutor(
2,
Runtime.getRuntime().availableProcessors(),
2, TimeUnit.SECONDS,
new LinkedBlockingDeque<>(3),
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.AbortPolicy());
try {
for (int i = 0; i < 8 ; i++) {
//使用线程池创建线程
pool.execute(()->{
System.out.println(Thread.currentThread().getName() + " okk");
});
}
} finally {
//线程池用完关闭
pool.shutdown();
}
}
}
16、JMM
请谈谈你对Volatile的理解
1.保证可见性
2.不保证原子性
3.禁止指令重拍
什么是JMM
MM: java内存模型,不存在的东西,概念。约定
关于JMM的一些同步的约定
1.线程解锁前,必须把共享变量立刻刷回主存
2.线程加锁前,必须读取主存中的最新值到工作内存中
3.加锁和解锁是同一把锁
17、Volatile
1.保证可见性
package com.cxx.juc.day006;
import java.util.concurrent.TimeUnit;
/**
* TODO
*
* @author cxx
* @version 1.0
* @date 2021/12/22 9:41
*/
public class jmmdemo {
private static volatile int num = 0;
public static void main(String[] args) throws InterruptedException {
new Thread(() -> {
while(num == 0) {
}
}).start();
TimeUnit.SECONDS.sleep(2);
num = 1;
System.out.println(num);
}
}
2.不保证原子性
package com.cxx.juc.day006;
import java.util.concurrent.atomic.AtomicInteger;
/**
* TODO
*
* @author cxx
* @version 1.0
* @date 2021/12/22 10:14
*/
public class jmmdemo2 {
//volatile不保证原子性
//integer的原子类
private volatile static AtomicInteger num = new AtomicInteger();
public static void add(){
num.getAndIncrement();
}
public static void main(String[] args) {
for (int i = 0; i < 20 ; i++) {
new Thread(()->{
for (int j = 0; j < 10000; j++) {
add();
}
}).start();
}
while (Thread.activeCount()>2){ //mian gc
Thread.yield();
}
System.out.println(Thread.currentThread().getName() + " " +num);
}
}
这些类的底层都直接和操作系统挂钩,在内存中修改值。Unsafe类是一个很特殊的存在
指令重排
1.什么是指令重排:
你写的程序,计算机并不是按照你写的那样去执行的
源代码--》编译器优化的重排--》 指令并行也可能重排 -- 》 内存系统也会重排 --》 执行
Volatile 是可以保持可见性,不能保证原子性,由于内存屏障,可以保证避免指令重排的现象产生.
饿汉式:
package com.cxx.juc.day006;
/**
* 饿汉式单利
*
* @author cxx
* @version 1.0
* @date 2021/12/22 11:03
*/
public class hurry {
private hurry (){
}
private final static hurry hurry = new hurry();
public static hurry getInstance(){
return hurry;
}
}
懒汉式:
package com.cxx.juc.day006;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
/**
* 懒汉式单例
*
* @author cxx
* @version 1.0
* @date 2021/12/22 11:06
*/
/*
* 1.分配内存
* 2.执行构造方法、初始化对象
* 3.吧这个对象指向这个空间
* 指令可能重排
* 123
* 132
**/
public class Lazyman {
private Lazyman(){
synchronized (Lazyman.class){
if(lazyman!=null){
throw new RuntimeException("不要瞎搞");
}
System.out.println(Thread.currentThread().getName());
}
}
private volatile static Lazyman lazyman;
// 双重检查锁模式
public static Lazyman getInstance(){
if(lazyman == null){
synchronized (Lazyman.class){
if(lazyman == null)
lazyman = new Lazyman(); //不是原子性操作
}
}
return lazyman; //lazyman未完成构造 所以用到volatile进行指令重排
}
public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
Lazyman instance = Lazyman.getInstance();
Constructor<Lazyman> declaredConstructor = Lazyman.class.getDeclaredConstructor(null);
declaredConstructor.setAccessible(true);
Lazyman lazyman = declaredConstructor.newInstance();
System.out.println(instance);
System.out.println(lazyman);
}
}
静态内部类:
package com.cxx.juc.day006;
/**
* 静态内部类
*
* @author cxx
* @version 1.0
* @date 2021/12/22 15:34
*/
public class holder {
private holder(){
}
public static holder getInstance(){
return InstanClass.holder;
}
public static class InstanClass{
private static final holder holder = new holder();
}
}
19、深入理解CAS
比较当前工作内存中的值和主内存中的值,如果这个值是期望的,那么执行操作,否则就一直循环
缺点: 1.循环会耗时 2.一次性只能保证一个共享变量的原子性
ABA问题: 引入原子引用,乐观锁原理
package com.cxx.juc.day006;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicMarkableReference;
import java.util.concurrent.atomic.AtomicStampedReference;
/**
* TODO
*
* @author cxx
* @version 1.0
* @date 2021/12/22 17:24
*/
public class cas {
//AtomicStampedReference 如果是一个包装类会有对象 引用问题
static AtomicStampedReference<Integer> atomicStampedReference = new AtomicStampedReference<Integer>(2,1);
//cas 比较并交换
public static void main(String[] args) {
new Thread(()->{
int stamp = atomicStampedReference.getStamp();
System.out.println("A1: " + stamp);
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("A2: "+atomicStampedReference.compareAndSet(2, 3, stamp, stamp + 1));
System.out.println("A2: "+atomicStampedReference.getStamp());
System.out.println("A3: "+atomicStampedReference.compareAndSet(3, 2, stamp, stamp + 1));
System.out.println("A3: "+atomicStampedReference.getStamp());
}).start();
new Thread(()->{
int stamp = atomicStampedReference.getStamp();
System.out.println("B1: " + stamp);
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("B2: " +atomicStampedReference.compareAndSet(2, 9, stamp, stamp + 1));
System.out.println("B2: " + atomicStampedReference.getStamp());
}).start();
}
}
21、各种锁的理解
公平锁:非常公平,不能插队
非公平锁:非公平锁,可以插队(默认非公平)
可重入锁: “可重入”,可重入就是说某个线程已经获得某个锁,可以再次获取锁而不会出现死锁
synchronized:
package com.cxx.juc.day007;
/**
* 可重入锁
*
* @author cxx
* @version 1.0
* @date 2021/12/23 9:59
*/
public class synca {
public static void main(String[] args) {
phone phone = new phone();
new Thread(()->{
phone.sms();
},"A").start();
new Thread(()->{
phone.sms();
},"B").start();
}
}
class phone {
public synchronized void sms() {
System.out.println(Thread.currentThread().getName()+"发短信");
call();//这里也有锁
}
public synchronized void call() {
System.out.println(Thread.currentThread().getName()+"打电话");
}
}
Lock:
package com.cxx.juc.day007;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* TODO
*
* @author cxx
* @version 1.0
* @date 2021/12/23 10:05
*/
public class lockb {
public static void main(String[] args) {
phone1 phone = new phone1();
new Thread(()->{
phone.sms();
},"A").start();
new Thread(()->{
phone.sms();
},"B").start();
}
}
class phone1 {
Lock lock = new ReentrantLock();
public void sms() {
lock.lock(); //细节问题,锁必须配对,加了两把锁,必须有两把钥匙,不然死锁
lock.lock();
try {
System.out.println(Thread.currentThread().getName()+"发短信");
call();
} finally {
lock.unlock();
lock.unlock();
}
}
public void call() {
lock.lock();
try {
System.out.println(Thread.currentThread().getName()+"打电话");
} finally {
lock.unlock();
}
}
}
自旋锁:
package com.cxx.juc.day007;
import java.util.concurrent.atomic.AtomicReference;
/**
* TODO
*
* @author cxx
* @version 1.0
* @date 2021/12/23 10:26
*/
public class spinlock {
AtomicReference<Thread> atomicReference = new AtomicReference<>();
public void slock(){
Thread thread = Thread.currentThread();
System.out.println(Thread.currentThread().getName()+" lock OK");
while (!atomicReference.compareAndSet(null,thread)) {}
}
public void sunlock(){
Thread thread = Thread.currentThread();
System.out.println(Thread.currentThread().getName()+" unlock OK");
atomicReference.compareAndSet(thread,null);
}
}
package com.cxx.juc.day007;
import java.util.concurrent.TimeUnit;
/**
* TODO
*
* @author cxx
* @version 1.0
* @date 2021/12/23 10:31
*/
public class spindemo {
public static void main(String[] args) {
spinlock spinlock = new spinlock();
new Thread(()->{
spinlock.slock();
try {
TimeUnit.SECONDS.sleep(3);
} catch (Exception e) {
e.printStackTrace();
} finally {
spinlock.sunlock();
}
},"A").start();
new Thread(()->{
spinlock.slock();
try {
TimeUnit.SECONDS.sleep(1);
} catch (Exception e) {
e.printStackTrace();
} finally {
spinlock.sunlock();
}
},"B").start();
}
}
死锁: