java.util工具包下的三个接口及工具类
**业务:**普通的线程代码:Thread
**Runnable接口:**没有返回值、效率相比于Callable
较低
线程、进程使用一句话来概括
**进程:**一个程序,如QQ.exe,Music.exe程序的集合
**线程:**比如当前开启了一个Typora进程,在其中进行打字,保存,删除等单一操作是由特定的线程负责的
Java程序真的可以开启线程吗?看源码!
/**
* Causes this thread to begin execution; the Java Virtual Machine
* calls the run
method of this thread.
*
* The result is that two threads are running concurrently: the
* current thread (which returns from the call to the
* start
method) and the other thread (which executes its
* run
method).
*
* It is never legal to start a thread more than once.
* In particular, a thread may not be restarted once it has completed
* execution.
*
* @exception IllegalThreadStateException if the thread was already
* started.
* @see #run()
* @see #stop()
*/
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 */
}
}
}
private native void start0();
并发、并行的区别
并发编程:并发、并行
并发:(多线程操作同一资源)
并行:(多个线程同时执行)
线程池
。并发编程的本质:充分利用CPU的资源!
线程的几个状态
/**
* A thread state. A thread can be in one of the following states:
*
* - {@link #NEW}
* A thread that has not yet started is in this state.
*
* - {@link #RUNNABLE}
* A thread executing in the Java virtual machine is in this state.
*
* - {@link #BLOCKED}
* A thread that is blocked waiting for a monitor lock
* is in this state.
*
* - {@link #WAITING}
* A thread that is waiting indefinitely for another thread to
* perform a particular action is in this state.
*
* - {@link #TIMED_WAITING}
* A thread that is waiting for another thread to perform an action
* for up to a specified waiting time is in this state.
*
* - {@link #TERMINATED}
* A thread that has exited is in this state.
*
*
*
*
* A thread can be in only one state at a given point in time.
* These states are virtual machine states which do not reflect
* any operating system thread states.
*
* @since 1.5
* @see #getState
*/
public enum State {
// 创建
NEW,
// 运行
RUNNABLE,
// 阻塞
BLOCKED,
// 等待-一直等待
WAITING,
// 超时等待-限制时间(过时不候)
TIMED_WAITING,
// 销毁
TERMINATED;
}
wait/sleep的区别
1. 来自不同的类
wait() ==>>>
Object类 sleep() ==>>>
Thread类
2. 关于锁的释放
wait
会释放锁,sleep
如同人睡着了,忘记释放锁的操作!
3.使用的范围不同
wait ==>>>
同步代码块 sleep ==>>>
任意地方
4.异常捕获
二者都需要进行中断异常捕获!
传统的synchronized锁
Lock锁
公平锁:十分公平,先来先得
非公平锁:十分不公平:可以插队(默认)
Synchronized锁和Lock锁的区别
1. Synchronized是内置的java关键字,Lock是一个java接口;
2. Synchronized无法判断获取锁的状态,Lock可以判断是否获取到了锁;
3. Synchronized会自动释放锁,Lock必须要手动释放锁,如果不释放锁,就会产生死锁;
4. Synchronized 线程(获取锁时会进入阻塞状态)、其余线程只能一直等待当前线程释放锁;Lock可以尝试获取当前锁,未必会一直等待下去;
5. Synchronized 可重入锁,不可以中断,为非公平锁;Lock为可重入锁,可以判断锁的状态,默认为非公平锁,但可以自行设置;
6. Synchronized 适合锁少量代码同步问题,Lock适合锁大量的同步代码块。
锁是什么?如何判断锁的对象是谁?
面试程序:单例模式、八大排序算法、生产者和消费者、死锁
Synchronized wait notify版本为老版本!
问题存在,A、B两个线程执行没有任何问题;A、B、C、D多线程同时启动,还能保证线程同步吗?—虚假唤醒
JUC版本的生产者和消费者问题,话不多说,见JDK源码:
class BoundedBuffer {
final Lock lock = new ReentrantLock();
final Condition notFull = lock.newCondition();
final Condition notEmpty = lock.newCondition();
final Object[] items = new Object[100];
int putptr, takeptr, count;
public void put(Object x) throws InterruptedException {
lock.lock(); try {
while (count == items.length)
notFull.await();
items[putptr] = x;
if (++putptr == items.length) putptr = 0;
++count;
notEmpty.signal();
} finally { lock.unlock(); }
}
public Object take() throws InterruptedException {
lock.lock(); try {
while (count == 0)
notEmpty.await();
Object x = items[takeptr];
if (++takeptr == items.length) takeptr = 0;
--count;
notFull.signal();
return x;
} finally { lock.unlock(); }
}
}
Condition接口的优势在什么地方?— 精准通知和唤醒线程
对象–>多个/class–>唯一
8锁之问题一--->>标准情况下,两个线程是发短信先进行,还是打电话先进行(先执行发短信,再执行打电话)
八锁问题之二--->>增加了非同步方法后,线程执行不受锁的影响(先执行非同步方法);
* 两个对象,两个同步方法,谁先执行,就先有锁锁住方法调用者,没有延迟的先执行
八锁问题之三--->>增加两个静态同步方法,静态方法在类加载就产生,为.class模板,一个类中的所有静态方法使用的是同一个
* Class模板,所以static方法会顺序执行,无论有多少个对象进行方法调用
八锁问题之四--->>一个普通的同步方法和一个静态的同步方法,在一个对象进行调用时;
* 一个普通的同步方法和一个静态的同步方法,在两个对象进行调用时;我的都是先执行静态同步方法
小结
new
出来的实例,this
关键字可修饰,是一个类的具体实例;
static
Class
模板,是一个类的唯一模板
List不安全
ConcurrentModificationException
并发修改异常!解决方案如下:
1、List list = new Vector<>();
2、List list = Collections.synchronizedList(new ArrayList<>());
3、List list = new CopyOnWriteArrayList<>(); 因为Vector中读写方法都为同步方法,在执行效率方面不如CopyOnWriteArrayList的非同步方法效率高,CopyOnWriteArrayList中使用的是Lock锁,对锁的状态操作比较灵活
Set不安全
解决方案如下:
1、Set set = Collections.synchronizedSet(new HashSet<>()); 请爸爸来帮忙
2、Set set = new CopyOnWriteArraySet<>();
hashSet
底层是什么?/**
* Constructs a new, empty set; the backing HashMap instance has
* default initial capacity (16) and load factor (0.75).
**/
public HashSet() {
map = new HashMap<>();
}
/**
* add方法 set 的底层本质就是map, hashmap中key是无法重复的
**/
public boolean add(E e) {
return map.put(e, PRESENT)==null;
}
// Dummy value to associate with an Object in the backing Map
private static final Object PRESENT = new Object();
Map不安全
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-viaGIiok-1625019931941)(D:\APP-文档\学习笔记\java多线程-JUC.assets\image-20210324160816584.png)]
Callable、Runnable异同
原理:
countDownLatch.countDown(); // 初始化数量减1
countDownLatch.await(); // 等待计数器归零,再执行其下面的程序(起拦截的作用),每次使用countdown()方法将计数器值减1,当计数器值为0时,await()方法就会被唤醒,继续执行后续的程序!
for (int i = 1; i <= 7; i++) {
final int temp = i;
// lambda表达式不能直接操作i
new Thread(()-> {
System.out.println(Thread.currentThread().getName() + "收集了" + temp + "个龙珠");
try {
// 等待
cyclicBarrier.await();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
}).start();
}
原理:
semaphore.acquire(); // 获得资源,如果资源全被使用,就等待资源被释放--信号量-1操作
semaphore.release(); // 释放资源,--信号量+1操作,等待别的线程进行调度
作用:
多个共享资源互斥的使用,并发限流,控制最大线程数
**ReadWriteLock**
自定义缓存---ReadWriteLock(独占锁-写锁[一次只能被一个线程占有];共享锁-读锁[一次可以被多个线程占有])
1、读-读:可以共存;
2、读-写:不能共存;
3、写-写:不能共存
示例:
class MyCacheLock {
private volatile Map<String, Object> map = new HashMap<>();
/**
* 读写锁,更加细粒度的控制
**/
private final ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
/**
* 存/写操作
*/
public void put(String key, Object value) {
readWriteLock.writeLock().lock();
try {
System.out.println(Thread.currentThread().getName() + "写入" + key);
map.put(key, value);
System.out.println(Thread.currentThread().getName() + "写入ok");
} catch (Exception e) {
e.printStackTrace();
} finally {
readWriteLock.writeLock().unlock();
}
}
/**
* 取/读操作
*/
public void get(String key) {
readWriteLock.readLock().lock();
try {
System.out.println(Thread.currentThread().getName() + "读取" + key);
Object o = map.get(key);
System.out.println(Thread.currentThread().getName() + "读取ok");
} catch (Exception e) {
e.printStackTrace();
} finally {
readWriteLock.readLock().unlock();
}
}
}
阻塞/队列:
[写操作]
–如果队列满了,就必须阻塞等待读操作进行;[读操作]
–如果队列为空,就必须阻塞等待写操作进行。
什么情况下会使用阻塞队列?—
多线程并发执行/线程池使用线程队列
添加/移除
四组API
方式 | 抛出异常 | 有返回值,不抛出异常 | 阻塞等待 | 超时等待 |
---|---|---|---|---|
添加 | add() | offer() | put() | offer(E e, long timeout, TimeUnit unit) |
移除 | remove() | poll() | take() | poll(long timeout, TimeUnit unit) throws InterruptedException |
检测判断队列首 | element() | peek() |
同步队列-SynchronousQueue
没有容量,进入一个元素,必须等待其被取出之后才能再次放入元素
同步队列-和其他的BlockQueue不同,SynchronousQueue不存储元素向队列put一个元素,必须从里面先使用take操作取出,否则不能再put其他元素
[三大方法、七大参数、四种拒绝策略]
**池化技术:**事先准备好一些资源,有人要使用,就来读这些现成的资源,使用完成后归还!
池化技术
==>优化资源的使用!线程池、连接池、内存池、对象池…
1、降低资源的消耗;
2、提高响应的速度;
3、方便管理。
1. 线程池的三大方法:
// 单个线程
Executors.newSingleThreadExecutor();
// 创建固定大小的线程池
Executors.newFixedThreadPool(5);
// 可伸缩线程池,遇强则强、遇弱则弱
Executors.newCachedThreadPool();
2. 线程池的七大参数:
源码分析:
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE, // 越等于21亿:容易导致OOM
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
// 本质:ThreadPoolExecutor()来创建线程池
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;
}
手动创建一个线程池:
ExecutorService threadPool = new ThreadPoolExecutor(
2,
5,
3,
TimeUnit.SECONDS,
new LinkedBlockingDeque<>(3),
Executors.defaultThreadFactory(),
// 队列满了会尝试和最早的线程竞争,也不会抛出异常
new ThreadPoolExecutor.DiscardOldestPolicy()
);
四种拒绝策略:
{new ThreadPoolExecutor.AbortPolicy());}-->阻塞队列已满,还有线程访问,就不接受此线程并抛出异常
{new ThreadPoolExecutor.CallerRunsPolicy());}-->哪里来的就回到哪里去!
{new ThreadPoolExecutor.DiscardPolicy());}-->队列满了会丢掉其余的线程,不会抛出异常
{new ThreadPoolExecutor.DiscardOldestPolicy());}-->队列满了会尝试和最早的线程竞争,也不会抛出异常
小结和扩展:
线程池中的最大线程数如何设置:
自定义线程池:最大线程到底如何定义:
1、cpu密集型:电脑cpu为几核,最大线程池就定义为多大,这样可以保持cpu的效率最高
2、IO密集型:判断程序中十分消耗IO的线程(15个大型任务,io十分占用资源)
**函数式接口:**只有一个方法的接口
@FunctionalInterface
public interface Runnable {
public abstract void run();
}
// @FunctionalInterface:简化编程模式,在新版本的框架中大量使用
// forEach(参数为消费者类的函数式接口)
Function:函数式接口
// 工具类:输出输入的值
Function<String, String> function = str -> str;
Predicate:断定式接口
// 判定接口:有一个输入参数,其返回值只能是boolean值
Predicate<String> predicate = String::isEmpty;
Consumer:消费型接口
// 消费型接口:只有输入,没有输出
Consumer<String> consumer = System.out::println;
Supplier:供给型接口
// 供给型接口:没有参数,只有返回值
Supplier supplier = () -> 1024;
什么是Stream流式计算?
list.stream()
.filter(user -> user.getId() % 2 == 0)
.filter(user -> user.getAge() > 23)
.map(user -> user.getName().toUpperCase())
.sorted(Comparator.reverseOrder())
.limit(1)
.forEach(System.out::println);
什么是ForkJoin?
ForkJoin
在JDK1.7
出现,并行执行任务!提高效率。大数据量!
ForkJoin的特点:工作窃取–提高线程工作效率
如何使用ForkJoin:
ForkJoinPool
接口调用:forkjoinTask.execute(函数式接口)
Future设计初衷:对未来的某个结果进行建模
// 发送一个请求 - 没有返回值的异步回调
CompletableFuture<Void> completableFuture = CompletableFuture.runAsync(() -> {
try {
TimeUnit.SECONDS.sleep(4);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "runAsync=>Void");
});
System.out.println("1111");
completableFuture.get(); // 获取阻塞执行结果
// 有返回值的异步回调 - ajax包含有成功/失败的回调函数
CompletableFuture<Integer> completableFuture = CompletableFuture.supplyAsync(() -> {
System.out.println(Thread.currentThread().getName() + "runAsync=>Integer");
int i = 10/0;
return 1024;
});
completableFuture.whenComplete((t, u) -> {
System.out.println("t => " + t); // 正常的返回结果
System.out.println("u => " + u);
}).exceptionally(e -> {
System.out.println(e.getMessage());
return 233;
});
Volatile关键字的理解!
Volatile是java虚拟机提供的轻量级的同步机制!
什么是JMM?
**JMM:**java内存模型,是个抽象的概念或者约定!
8种操作:
Java内存模型中定义了8种操作来完成,虚拟机保证了每种操作都是原子的。
如果要把一个变量从主存复制到工作内存:顺序执行 read 和 load 操作。
如果要把变量从工作内存同步会主存:顺序执行 store 和 write 操作。
1、保证可见性
private static volatile int num = 0; // volatile关键字修饰可以保证可见性
2、不保证原子性–不可分割(线程在执行任务的时候,不能被打扰,也不能被分割。要么同时成功,要么同时失败!)
// 理论上是20x1000=20000
for (int i = 1; i <= 20; i++) {
new Thread(()-> {
for (int j = 0; j < 1000; j++) {
add();
}
}).start();
}
while (Thread.activeCount() > 2) {
Thread.yield();
}
System.out.println(Thread.currentThread().getName() + " " + num);
// 执行结果:main 15595(小于20000)
如果不适用Lock和Synchronized锁,如果保证原子性?
什么是指令重排?
源代码-》编译器优化的重排-》指令并行也可能重拍-》内存系统也会重拍-》执行
int x = 1; // 1
int y = 2; // 2
x = x + 5; // 3
y = x * x; // 4
// 期望的操作1234 但是2134/1324也可以达到相同的效果
// 不可能是4123
线程A | 线程B |
---|---|
x = a | y = b |
b = 1 | a = 2 |
正常的结果:x = 0; y = 0。但是可能由于指令重排,下面表格中的顺序也有可能发生!
线程A | 线程B |
---|---|
b = 1 | a = 2 |
x = a | y = b |
指令重排序导致的诡异结果:x = 2; y = 1。
volatile可以避免指令重排!
内存屏障/CPU指令作用:
饿汉式 DCL懒汉式,深究!
package com.juc.singleton;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
/**
* @Description: 懒汉式单例模式
* @author: L-B-S
* @Date: 2021/3/26 18:37
* @modified_By:
* @Version: 0.0$
*/
public class Lazy {
private static volatile Lazy lazy;
private static boolean flag = false;
private Lazy() {
synchronized (Lazy.class) {
if (!flag) {
flag = true;
} else {
throw new RuntimeException("不要试图用反射破坏异常");
}
}
}
public static Lazy getInstance() {
// 加锁
if (lazy == null) {
synchronized (Lazy.class) {
if (lazy == null) {
// 不是一个原子性操作:1、分配内存空间;2、执行构造方法,初始化对象;3、将这个对象指向内存空间
lazy = new Lazy();
}
}
}
return lazy;
}
// 单线程下安全
public static void main(String[] args) throws Exception {
// Lazy instance1 = Lazy.getInstance();
Field flag = Lazy.class.getDeclaredField("flag");
flag.setAccessible(true);
Constructor<Lazy> declaredConstructor = Lazy.class.getDeclaredConstructor(null);
declaredConstructor.setAccessible(true);
Lazy instance1 = declaredConstructor.newInstance();
flag.set(instance1, false);
Lazy instance2 = declaredConstructor.newInstance();
System.out.println(instance1 == instance2);
}
}
什么是CAS?
- 缺点:
- 1、循环会耗时;
- 2、一次性只能保证一个共享变量的原子性;
- 3、容易导致ABA问题。
CAS核心问题:ABA问题(简称:狸猫换太子)
// 捣乱的线程
AtomicInteger atomicInteger = new AtomicInteger(2020);
System.out.println(atomicInteger.compareAndSet(2020, 2021));
System.out.println(atomicInteger.get());
System.out.println(atomicInteger.compareAndSet(2021, 2020));
System.out.println(atomicInteger.get());
// 期望的线程
System.out.println(atomicInteger.compareAndSet(2020, 6666));
System.out.println(atomicInteger.get());
ABA问题的解决方案:引入原子引用,完美解决,每次更改都会有版本记录!
package com.juc.cas;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicStampedReference;
/**
* @Description: CAS---CPU的并发原语!
* @author: L-B-S
* @Date: 2021/3/26 20:17
* @modified_By:
* @Version: 0.0$
*/
public class CASDemo {
public static void main(String[] args) {
// int Integer 注意:如果泛型是一个包装类,在比较时注意对象的引用问题
AtomicStampedReference<Integer> atomicStampedReference = new AtomicStampedReference<>(1, 1);
new Thread(() -> {
// 获取版本号
int stamp = atomicStampedReference.getStamp();
System.out.println("a1 => " + stamp);
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(atomicStampedReference.compareAndSet(1, 2,
atomicStampedReference.getStamp(), atomicStampedReference.getStamp() + 1));
System.out.println("a2 => " + atomicStampedReference.getStamp());
System.out.println(atomicStampedReference.compareAndSet(2, 1,
atomicStampedReference.getStamp(), atomicStampedReference.getStamp() + 1));
System.out.println("a3 => " + atomicStampedReference.getStamp());
}, "A").start();
new Thread(() -> {
int stamp = atomicStampedReference.getStamp();
System.out.println("b1 => " + stamp);
try {
TimeUnit.SECONDS.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(atomicStampedReference.compareAndSet(1, 3,
stamp, stamp + 1));
}, "B").start();
System.out.println("b2 => " + atomicStampedReference.getStamp());
}
}
公平锁:非常公平(不能插队)
非公平锁:非常不公平(可以插队,默认都是非公平的)
/**
* Creates an instance of {@code ReentrantLock}.
* This is equivalent to using {@code ReentrantLock(false)}.
*/
public ReentrantLock() {
sync = new NonfairSync();
}
Lock lock = new ReentrantLock(true); // 重载方法:传入true,为公平锁,false则为非公平锁
Synchronized关键字实现可重入锁
class Phone1 {
public synchronized void sendMsg() {
System.out.println(Thread.currentThread().getName() + " => sendMsg");
call();
}
public synchronized void call() {
System.out.println(Thread.currentThread().getName() + " => call");
}
}
Lock接口实现可重入锁
class Phone2 {
Lock lock = new ReentrantLock();
public synchronized void sendMsg() {
// 第一把锁--锁必须配对,加几次锁必须解几次锁
lock.lock();
try {
System.out.println(Thread.currentThread().getName() + " => sendMsg");
// 达到call方法内的锁
call();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public synchronized void call() {
// 第二把锁
lock.lock();
try {
System.out.println(Thread.currentThread().getName() + " => call");
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
细节
:需要注意锁的配对情况-》即使用了几把锁就必须有几把锁的钥匙(解锁),否则会造成死锁情况SpinLock:
public final int getAndAddInt(Object var1, long var2, int var4) {
int var5;
do {
var5 = this.getIntVolatile(var1, var2);
} while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));
return var5;
}
死锁是什么?
解决死锁:
1、使用 jps -l 定位进程号;
D:\java_study\juc-study>jps -l
9936 com.juc.lock.DeadLockDemo
12804 sun.tools.jps.Jps
17828 org.jetbrains.jps.cmdline.Launcher
12872
14024 org.jetbrains.idea.maven.server.RemoteMavenServer36
2、使用 jstack进程号 找到死锁问题
e.printStackTrace();
} finally {
lock.unlock();
}
}
public synchronized void call() {
// 第二把锁
lock.lock();
try {
System.out.println(Thread.currentThread().getName() + " => call");
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
- `细节`:**需要注意锁的配对情况-》即使用了几把锁就必须有几把锁的钥匙(解锁),否则会造成死锁情况**
#### 3、自旋锁
> **SpinLock:**
```java
public final int getAndAddInt(Object var1, long var2, int var4) {
int var5;
do {
var5 = this.getIntVolatile(var1, var2);
} while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));
return var5;
}
死锁是什么?
解决死锁:
1、使用 jps -l 定位进程号;
D:\java_study\juc-study>jps -l
9936 com.juc.lock.DeadLockDemo
12804 sun.tools.jps.Jps
17828 org.jetbrains.jps.cmdline.Launcher
12872
14024 org.jetbrains.idea.maven.server.RemoteMavenServer36
2、使用 jstack进程号 找到死锁问题