一、什么是JUC?
Java里面的三个包
java.util.concurrent
java.util.concurrent.atomic 原子性
java.util.concurrent.locks 锁
二、线程和进程
进程:程序执行的一次过程,一个进程包含一个或多个线程。进程是资源分配的单位。
线程:指程序执行过程中,负责实现某个功能的单位。线程是CPU调度和执行的单位。
并发:同一时刻,多个线程交替执行。(一个CPU交替执行线程)。
并行:同一时刻,多个线程同时执行。(多个CPU同时执行多个线程)。
# 获取cpu的核数(cpu密集型;io密集型)
System.out.println(Runtime.getRuntime().availableProcessors());
并发编程的本质:充分利用cpu资源。
问:Java真的可以开启线程吗?
Java创建的Thread类调用start方法,底层是把线程放到一个组里面,然后调用一个本地方法start();方法底层是C++,Java无法操作硬件。
三、多线程回顾
线程的6种状态:
1.NEW:新生的尚未启动的线程状态。
2.RUNNABLE:可运行的线程状态,正在Java虚拟机中执行,但是它可能正在等待来自操作系统的其他资源,例如处理器运行。
3.BLOCKED:等待线程的线程状态。因为调用了方法Object.wait,Thread.join,LockSupport.park。
4.WAITING:具有指定等待时间的等待线程状态。因为调用了方法:Thread.sleep,Object.wait(long),Thread.join(long),LockSupport.parkNanos,LockSupport.parkUntil
5.TIMED_WAITING:超时等待。
6.TERMINATED:终止线程的线程状态。线程已完成执行,终止。
sleep与wait的区别:
1.sleep是Thread类的本地方法。wait是Object类的方法。
2.sleep抱着锁睡觉,不释放锁。wait会释放锁。
3.sleep不需要和synchronized关键字一起使用,可以在任意地方sleep。wait必须在同步代码块中使用。
4.sleep不需要被唤醒(时间到了自动退出阻塞)。wait需要被唤醒。
5.sleep一般用于当前线程休眠,或者轮循暂停操作。wait用于多线程之间的通信。
四、Lock锁
1.传统的synchronized
public class SaleTicket {
public static void main(String[] args) {
//多线程操作同一个资源类
//将资源类丢入线程
Ticket ticket = new Ticket();
new Thread(()-> {
for (int i = 0; i < 60; i++) {
ticket.sale();
}
},"A").start();
new Thread(()-> {
for (int i = 0; i < 60; i++) {
ticket.sale();
}
},"B").start();
new Thread(()-> {
for (int i = 0; i < 60; i++) {
ticket.sale();
}
},"C").start();
}
}
//资源类
class Ticket{
private int number=50;
//卖票的方式
public synchronized void sale(){
if(number>0){
System.out.println(Thread.currentThread().getName()+"卖出了"+(number--)+"票,剩余"+number);
}
}
}
2.Lock锁
Lock锁是一个接口,他有3个实现类:
- ReentrantLock类
- ReentrantReadWriteLock.ReadLock
- ReentrantReadWriteLock.WriteLock
public class SaleTicket2 {
public static void main(String[] args) {
//多线程操作同一个资源类
//将资源类丢入线程
Ticket2 ticket = new Ticket2();
new Thread(()->{ for (int i = 0; i < 60; i++) ticket.sale(); },"A").start();
new Thread(()->{ for (int i = 0; i < 60; i++) ticket.sale(); },"B").start();
new Thread(()->{ for (int i = 0; i < 60; i++) ticket.sale(); },"C").start();
}
}
//Lock三部曲
//1.new ReentrantLock();
//2.lock.lock();//加锁
//3.lock.unlock();
class Ticket2{
private int number=50;
Lock lock=new ReentrantLock();
//卖票的方式
public void sale(){
lock.lock();//加锁
lock.tryLock();
try {
//业务代码块
if(number>0){
System.out.println(Thread.currentThread().getName()+"卖出了"+(number--)+"票,剩余"+number);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
3.公平锁和非公平锁(锁的底层):
公平锁:十分公平,先来后到。
非公平锁:十分不公平,可以插队。(默认的都是非公平锁)
4.Lock锁与Synchronized的区别:
(1)Synchronized是内置Java关键字。Lock是一个Java类。
(2)Synchronized无法获取锁的状态。Lock可以判断是否获得了锁。(boolean b=lock.tryLock();)。
(3)Synchronized会自动释放锁。Lock要手动释放锁,如果不释放锁,会造成死锁。
(4)Synchronized线程1获得锁阻塞时,线程2会一直等待下去。Lock锁线程1获得锁阻塞时,线程2等待足够的时间后会中断等待,去做其他的事情。
(5)Synchronized是可重入锁,不可以中断的,非公平锁。Lock锁是可重入锁,非公平锁(可以自己设置)。lock.lockInterruptibly();方法:当两个线程同时通过该方法想获取某个锁时,假若此时线程A获取到了锁,而线程B只有在等待,那么对线程B调用threadB.interrupt()方法能够中断线程B的等待过程。
(6)Synchronized适合锁少量的代码同步问题。Lock适合锁大量的同步代码。
5.生产者消费者
(1)Synchronized版
问题:A B C D 四个线程存在虚假唤醒问题。将条件语句if改成while即可解决。
public class A {
public static void main(String[] args) {
Data data = new Data();
new Thread(()->{
for (int i = 0; i < 10; i++) {
try {
data.decrement();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"A").start();
new Thread(()->{
for (int i = 0; i < 10; i++) {
try {
data.increment();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"B").start();
new Thread(()->{
for (int i = 0; i < 10; i++) {
try {
data.increment();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"C").start();
new Thread(()->{
for (int i = 0; i < 10; i++) {
try {
data.decrement();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"D").start();
}
}
//判断等待
//业务
//通知
class Data{
private int num=0;
//+1
public synchronized void increment() throws InterruptedException {
while(num!=0){
//等待
this.wait();
}
num++;
System.out.println(Thread.currentThread().getName()+"=>"+num);
//通知线程,我+1执行完毕
this.notify();
}
//-1
public synchronized void decrement() throws InterruptedException {
while (num==0){
//等待
this.wait();
}
num--;
System.out.println(Thread.currentThread().getName()+"=>"+num);
//通知其他线程,我-1完毕
this.notify();
}
}
(2)JUC版
public class B {
public static void main (String[]args){
Data2 data = new Data2();
new Thread(() -> { for (int i = 0; i < 10; i++) { data.decrement(); } }, "A").start();
new Thread(() -> { for (int i = 0; i < 10; i++) { data.increment(); } }, "B").start();
new Thread(() -> { for (int i = 0; i < 10; i++) { data.decrement(); } }, "C").start();
new Thread(() -> { for (int i = 0; i < 10; i++) { data.increment(); } }, "D").start();
}
}
//判断等待
//业务
//通知
class Data2 {
private int num=0;
Lock lock=new ReentrantLock();
Condition condition=lock.newCondition();
public void increment(){
lock.lock();
try {
while(num!=0){
condition.await();
}
num++;
System.out.println(Thread.currentThread().getName()+"=>"+num);
//通知其他线程,我+1执行完毕
condition.signalAll();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
//-1
public void decrement(){
lock.lock();
try {
while (num == 0) {
//等待
condition.await();
}
num--;
System.out.println(Thread.currentThread().getName() + "=>" + num);
//通知其他线程,我-1完毕
condition.signalAll();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
(3)Condition实现精准通知唤醒。
public class C {
public static void main(String[] args) {
Data3 data3=new Data3();
new Thread(()->{
for (int i = 0; i < 10; i++) {
data3.printA();
}
},"A").start();
new Thread(()->{
for (int i = 0; i < 10; i++) {
data3.printB();
}
},"B").start();
new Thread(()->{
for (int i = 0; i < 10; i++) {
data3.printC();
}
},"C").start();
}
}
class Data3{
//资源类
private Lock lock=new ReentrantLock();
Condition condition1 = lock.newCondition();
Condition condition2 = lock.newCondition();
Condition condition3 = lock.newCondition();
private int num=1;
public void printA(){
lock.lock();
try {
//业务,判断->执行->通知
while (num!=1){
condition1.await();//使当前线程等待直到发出信号或中断,或指定的等待时间过去。
}
System.out.println(Thread.currentThread().getName()+"=>AAA");
num=2;
condition2.signal();//唤醒一个等待进程,condition2
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void printB(){
lock.lock();
try {
while (num!=2){
condition2.await();
}
System.out.println(Thread.currentThread().getName()+"=>BBB");
num=3;
condition3.signal();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void printC(){
lock.lock();
try {
while (num!=3){
condition3.await();
}
System.out.println(Thread.currentThread().getName()+"=>CCC");
num=1;
condition1.signal();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
五、8锁现象(关于锁的8个问题)
如何判断锁的是谁!永远的知道什么锁,锁到底锁的是谁!
Synchronized锁的是方法的调用者!即new出来的对象。
/*
* 8锁,就是关于锁的8个问题
* 1、标准情况下,两个线程先打印发短息还是打电话?//发短信
* 2、sendSms延迟4秒,两个线程先打印发短息还是打电话?//发短信
* */
public class Test1 {
public static void main(String[] args) throws InterruptedException {
Phone phone=new Phone();
new Thread(()->{
phone.sendSms();
},"A").start();
TimeUnit.SECONDS.sleep(1);
new Thread(()->{
phone.call();
},"B").start();
}
}
class Phone{
//synchronized 锁的对象是方法的调用者!即new出来的对象phone
//两个方法用的是同一个锁,谁先拿到谁执行!
public synchronized void sendSms(){
try {
TimeUnit.SECONDS.sleep(4);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("发短信");
}
public synchronized void call(){
System.out.println("打电话");
}
}
/*
* 3、增加了一个普通方法后,先执行发短信还是听音乐//听音乐
* 4、两个对象,两个同步方法,两个线程先打印发短息还是打电话?//打电话
* 原因:因为发短信方法睡觉了4秒中,调用方法的对象获取锁的速度比不上不睡觉的call()方法
* */
public class Test2 {
public static void main(String[] args) throws InterruptedException {
Phone2 phone=new Phone2();
Phone2 phone2=new Phone2();
new Thread(()->{
phone.sendSms();
},"A").start();
new Thread(()->{
phone2.call();
},"B").start();
}
}
class Phone2{
//synchronized 锁的对象是方法的调用者!
//两个方法用的是同一个锁,谁先拿到谁执行!
public synchronized void sendSms(){
try {
TimeUnit.SECONDS.sleep(4);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("发短信");
}
public synchronized void call(){
System.out.println("打电话");
}
//这里没有锁。不受锁的影响!
public void music(){
System.out.println("听音乐");
}
}
static锁的是Class类。
/*
* 5、增加两个静态的同步方法,只有一个对象,两个线程先打印发短息还是打电话?//发短信
* 6、增加两个静态的同步方法,两个对象,两个线程先打印发短息还是打电话?//发短信
* */
public class Test3 {
public static void main(String[] args) throws InterruptedException {
//两个对象的Class类模板只有一个,static锁的是Class
Phone3 phone=new Phone3();
Phone3 phone1=new Phone3();
new Thread(()->{
phone.sendSms();
},"A").start();
TimeUnit.SECONDS.sleep(1);
new Thread(()->{
phone1.call();
},"B").start();
}
}
class Phone3{
//synchronized 锁的对象是方法的调用者!
//static 静态方法 类一加载就有了!Class 模板
public static synchronized void sendSms(){
try {
TimeUnit.SECONDS.sleep(4);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("发短信");
}
public static synchronized void call(){
System.out.println("打电话");
}
}
/*
* 7、一个静态同步方法,一个普通同步方法,两个线程先打印发短息还是打电话?//打电话
* 原因:这时候有两个锁,要等待的锁就输给不用等待的锁了!
* 8、2个对象,两个线程先打印发短息还是打电话?//打电话
* 原因同上
* */
public class Test4 {
public static void main(String[] args) throws InterruptedException {
//两个对象的Class模板只有一个,static锁的是Class
Phone4 phone=new Phone4();
Phone4 phone1=new Phone4();
new Thread(()->{
phone.sendSms();
},"A").start();
TimeUnit.SECONDS.sleep(1);
new Thread(()->{
phone1.call();
},"B").start();
}
}
class Phone4{
//static 静态同步方法,锁的是Class类模板
public static synchronized void sendSms(){
try {
TimeUnit.SECONDS.sleep(4);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("发短信");
}
//普通的同步方法,锁的是调用者
public synchronized void call(){
System.out.println("打电话");
}
}
六、集合类不安全
并发下ArrayList是不安全的
public class ListTest {
public static void main(String[] args) {
/*
* 并发下ArrayList是不安全的
* List<String> list=new ArrayList<>();
* ConcurrentModificationException 并发修改异常!
* 解决方案:
*1.List<String> list = new Vector<>();
* 2.List<Object> list = Collections.synchronizedList(new ArrayList<>());
* 3.CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<>();
* */
//CopyOnWrite写入时复制 COW 计算机程序设计领域的一种优化策略
//多个线程调用的时候,list,读取的时候,固定的,写入(覆盖)
//在写入的时候避免覆盖造成数据问题
//读写分离 MyCat
//CopyOnWriteArrayList比Vector好在哪里? 效率高
// CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<>();
CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<>();
for (int i = 0; i < 20; i++) {
new Thread(()->{
list.add(UUID.randomUUID().toString().substring(0,5));
System.out.println(list);
},String.valueOf(i)).start();
}
}
}
并发下HashMap是不安全的
/*
* ConcurrentModificationException 并发修改异常!
*
* */
public class MapTest {
public static void main(String[] args) {
//map是这样用的吗?工作中不用 HashMap
//默认等价于什么? new HashMap<>(16,0.75) 加载因子,初始化容量
Map<String,String> map=new ConcurrentHashMap<>();
for (int i = 0; i < 10; i++) {
new Thread(()->{
map.put(Thread.currentThread().getName(), UUID.randomUUID().toString().substring(0,5));
System.out.println(map);
},String.valueOf(i)).start();
}
}
}
并发下HashSet是不安全的
/*
* 报错:ConcurrentModificationException 并发修改异常!
* 解决方法:
* 1.Set<String> set = Collections.synchronizedSet(new HashSet<>());
* 2. CopyOnWriteArraySet<String> set = new CopyOnWriteArraySet<>();
* */
public class SetTest {
public static void main(String[] args) {
//Set<String> set = new HashSet<>();
CopyOnWriteArraySet<String> set= new CopyOnWriteArraySet<>();
for (int i = 0; i < 40; i++) {
new Thread(()->{
set.add(UUID.randomUUID().toString().substring(0,5));
System.out.println(set);
},String.valueOf(i)).start();
}
}
}
七、Callable接口
Callable接口类似于Runnable接口,线程第三种创建方式。
1.可以抛出异常。
2.可以有返回值。
3.方法不同于Runnable接口。实现的是call()方法而不是run()方法。
public class CallableTest {
public static void main(String[] args) throws ExecutionException, InterruptedException {
new Thread().start();
MyThread myThread = new MyThread();
//适配类
FutureTask futureTask = new FutureTask(myThread);
new Thread(futureTask,"A").start();
Integer o = (Integer) futureTask.get();//获取Callable的返回结果
System.out.println(o);
}
}
class MyThread implements Callable<Integer>{
@Override
public Integer call() throws Exception {
System.out.println("call()");
return 1024;
}
}
原理图:
八、常用辅助类(AbstractQueuedSynchronized) AQS
CountDownLatch
应用场景:一个或者多个线程,等待其他多个线程完成某件事情之后才能执行;
每次有线程调用,数量-1,当计数器归零,countDownLatch.await()就会被唤醒向下执行。
import java.util.concurrent.CountDownLatch;
//计数器
public class CountDownLatchDemo {
public static void main(String[] args) throws InterruptedException {
//总数是6
CountDownLatch countDownLatch = new CountDownLatch(6);
for (int i = 1; i <= 6; i++) {
new Thread(()->{
System.out.println(Thread.currentThread().getName()+"Go out");
countDownLatch.countDown();//数量减一
},String.valueOf(i)).start();
}
countDownLatch.await();//等待计数器归零,然后再向下执行
System.out.println("Close Door");
}
}
CysclicBarrier
应用场景:多个线程互相等待,直到到达同一个同步点,再继续一起执行。
//加法计数器
public class CyclicBarrierDemo {
public static void main(String[] args) {
/*
* 集齐7颗龙珠召唤神龙
* 召唤龙珠的线程
* */
CyclicBarrier cyclicBarrier = new CyclicBarrier(7,()->{
System.out.println("召唤神龙成功!");
});
for (int i = 1; i <= 7; i++) {
final int temp=i;//新创建的线程相当于类,不能直接拿到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:信号量
public class SemaphoreDemo {
public static void main(String[] args) {
Semaphore semaphore = new Semaphore(3);//只有三个信号量
for (int i = 1; i <= 6; i++) {
new Thread(()->{
try {
semaphore.acquire();//acquire() 得到
System.out.println(Thread.currentThread().getName()+"抢到车位");//有三个线程抢到车位
TimeUnit.SECONDS.sleep(2);//这三个线程2秒后离开车位
System.out.println(Thread.currentThread().getName()+"离开车位");
TimeUnit.SECONDS.sleep(2);//两秒之后剩下的三个线程抢到车位
} catch (InterruptedException e) {
e.printStackTrace();
}
finally {
semaphore.release();//最后释放所有资源
}
},String.valueOf(i)).start();
}
}
}
原理:
semaphore.acquire();获得,假设已经满了则等待,等待其他线程释放。
semaphore.release();释放,会将当前的信号量释放+1,然后唤醒等待的线程。
九、读写锁
ReadWriteLock接口有一个实现类 ReentrantReadWriteLock。
规则:读可以被多个线程同时读,写的时候只能有一个线程去写。
独占锁(写锁)。
共享锁(读锁)。
public class ReadWriteLockDemo {
public static void main(String[] args) {
MyCacheLock myCacheLock = new MyCacheLock();
for (int i = 1; i <= 5; i++) {
final int temp=i;
new Thread(()->{
myCacheLock.put(temp+"key",temp+"value");
},String.valueOf(i)).start();
}
for (int i = 1; i <= 5; i++) {
final int temp=i;
new Thread(()->{
myCacheLock.get(temp+"");
},String.valueOf(i)).start();
}
}
}
/*
* 自定义缓存
* */
class MyCache{
private volatile Map<String,Object> map= new HashMap<>();
public void put(String key,Object value){
System.out.println(Thread.currentThread().getName()+"写入"+key);
map.put(key,value);
System.out.println(Thread.currentThread().getName()+"写入OK");
}
public void get(String key){
System.out.println(Thread.currentThread().getName()+"读取"+key);
Object o = map.get(key);
System.out.println(Thread.currentThread().getName()+"读取OK");
}
}
//加锁
class MyCacheLock{
private volatile Map<String,Object> map= new HashMap<>();
//更加细粒度的控制
private ReadWriteLock lock=new ReentrantReadWriteLock();
//存写入的时候,只希望同时只有一个线程写
public void put(String key,Object value){
lock.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 {
lock.writeLock().unlock();
}
}
public void get(String key){
lock.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 {
lock.readLock().unlock();
}
}
}
十、阻塞队列(B)
//BlockQueue的四组API
public class Test {
public static void main(String[] args) throws InterruptedException {
//list
//set
//BlockingQueue 不是新东西
test4();
}
//1.抛出异常
public static void test1(){
//队列大小
ArrayBlockingQueue blockingQueue = new ArrayBlockingQueue<>(3);
System.out.println(blockingQueue.add("a"));
System.out.println(blockingQueue.add("b"));
System.out.println(blockingQueue.add("c"));
//抛出异常,IllegalStateException
//System.out.println(blockingQueue.add("d"));
System.out.println("========================");
System.out.println(blockingQueue.remove());
System.out.println(blockingQueue.element());//检测队首元素
System.out.println(blockingQueue.remove());
System.out.println(blockingQueue.remove());
//抛出异常,NoSuchElementException
//System.out.println(blockingQueue.remove());
}
//2.有返回值,不抛出异常
public static void test2(){
ArrayBlockingQueue blockingQueue = new ArrayBlockingQueue<>(3);
System.out.println(blockingQueue.offer("a"));
System.out.println(blockingQueue.offer("b"));
System.out.println(blockingQueue.offer("c"));
System.out.println(blockingQueue.offer("d"));//返回false,不抛出异常
System.out.println(blockingQueue.peek());//检测队首元素
System.out.println("=========================");
System.out.println(blockingQueue.poll());
System.out.println(blockingQueue.poll());
System.out.println(blockingQueue.poll());
System.out.println(blockingQueue.poll());//返回null,不抛出异常
}
//3.等待,阻塞
public static void test3() throws InterruptedException {
ArrayBlockingQueue blockingQueue = new ArrayBlockingQueue<>(3);
blockingQueue.put("a");
blockingQueue.put("b");
blockingQueue.put("c");
//blockingQueue.put("d");//队列没有位置了,一直阻塞
System.out.println("=========================");
System.out.println(blockingQueue.take());
System.out.println(blockingQueue.take());
System.out.println(blockingQueue.take());
// System.out.println(blockingQueue.take());//没有这个元素,一直阻塞
}
//4.等待,超时
public static void test4() throws InterruptedException {
ArrayBlockingQueue blockingQueue = new ArrayBlockingQueue<>(3);
blockingQueue.offer("a");
blockingQueue.offer("b");
blockingQueue.offer("c");
blockingQueue.offer("d", 2,TimeUnit.SECONDS);//超过2秒就退出
System.out.println("=========================");
System.out.println(blockingQueue.poll());
System.out.println(blockingQueue.poll());
System.out.println(blockingQueue.poll());
blockingQueue.poll(2,TimeUnit.SECONDS);//超过2秒就退出
}
}
SynchronousQueue:同步队列
没有容量,进去一个元素,必须等待取出这个元素后,才能放下一个元素。put()、take()。
/*
* 同步队列
* 和其他的BlockingQueue 不一样,SynchronousQueue 不存储元素
* put了一个元素,必须从里面先take取出来,否则不能再put进去
* */
public class SynchronousQueueDemo {
public static void main(String[] args) {
BlockingQueue<String> blockingQueue = new SynchronousQueue<>();//同步队列
new Thread(()->{
try {
System.out.println(Thread.currentThread().getName()+"=>"+"put 1");
blockingQueue.put("1");
System.out.println(Thread.currentThread().getName()+"=>"+"put 2");
blockingQueue.put("2");
System.out.println(Thread.currentThread().getName()+"=>"+"put 3");
blockingQueue.put("3");
} catch (InterruptedException e) {
e.printStackTrace();
}
},"T1").start();
new Thread(()->{
try {
TimeUnit.SECONDS.sleep(3);
System.out.println(Thread.currentThread().getName()+"=>"+blockingQueue.take());
TimeUnit.SECONDS.sleep(3);
System.out.println(Thread.currentThread().getName()+"=>"+blockingQueue.take());
TimeUnit.SECONDS.sleep(3);
System.out.println(Thread.currentThread().getName()+"=>"+blockingQueue.take());
} catch (InterruptedException e) {
e.printStackTrace();
}
},"T2").start();
}
}
十一、线程池
线程池的好处:线程复用/可以控制最大并发数/管理线程。
1.降低资源的消耗;
2.提高响应的速度;
3.方便管理。
掌握3大方法、7大参数、4大拒绝策略。
三大方法
//Executors 工具类,三大方法
public class Demo01 {
public static void main(String[] args) {
//ExecutorService threadPool = Executors.newSingleThreadExecutor();//单个线程
//ExecutorService threadPool = Executors.newFixedThreadPool(5);//创建一个固定的线程池大小,最多可以开启5个线程
ExecutorService threadPool = Executors.newCachedThreadPool();//可伸缩的,遇强则强,遇弱则弱
try {
for (int i = 0; i < 100; i++) {//看CPU能同时开启多少个线程,不一定是100个
//使用了线程池之后,使用线程池来创建线程
threadPool.execute(()->{
System.out.println(Thread.currentThread().getName()+"ok");
});
}
} catch (Exception e) {
e.printStackTrace();
} finally {
//线程池用完,程序结束,关闭线程池
threadPool.shutdown();
}
}
}
七大参数:
//自定义的线程池 ThreadPoolExecutor
1.核心线程池大小
2.最大核心线程池大小
3.超时了没有人调用就会释放
4.超时单位
5.队列阻塞
6.线程工厂,用于创建线程,一般不用动
7.拒绝策略
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()//银行满了,还有人进来,不处理这个人的,抛出异常RejectedExecutionException
* new ThreadPoolExecutor.CallerRunsPolicy()//哪来的去哪里!
* new ThreadPoolExecutor.DiscardPolicy()//队列满了,不会抛出异常
* new ThreadPoolExecutor.DiscardOldestPolicy()//队列满了,尝试和最早的线程竞争,也不会抛出异常
* */
public class Demo2 {
public static void main(String[] args) {
//自定义的线程池 ThreadPoolExecutor
//1.CPU 密集型 几核,就是几,可以保证CPU的效率最高
//2.IO 密集型 判断你程序中十分耗IO的线程
//程序 15个大型任务,io十分占用资源
//获取CPU的核数
System.out.println(Runtime.getRuntime().availableProcessors());
ExecutorService threadPool =new ThreadPoolExecutor(
2,//核心线程池大小
Runtime.getRuntime().availableProcessors(),//最大核心线程池大小,即CPU的核数,一般不写死
3,//超时3秒没有调用就释放
TimeUnit.SECONDS,
new LinkedBlockingDeque<>(3),//队列阻塞
Executors.defaultThreadFactory(),//线程工厂
new ThreadPoolExecutor.DiscardOldestPolicy());//拒绝策略,有4种
try {
for (int i = 1; i <=20; i++) {
//使用自定义的线程池
threadPool.execute(() -> {
System.out.println(Thread.currentThread().getName() + "ok");
});
}
}catch (Exception e) {
e.printStackTrace();
} finally {
threadPool.shutdown();
}
}
}
小结及拓展:
1.CPU 密集型 CPU是几核,就是几,可以保证CPU的效率最高。
2.IO 密集型 大于你程序中十分耗IO的线程。一般设置为它的两倍。
十二、四大函数式接口(重点!!!)
必须掌握:lambda表达式、链式编程、函数式接口、stream流式计算。
函数式接口:只有一个抽象方法的接口。
Function函数式接口:有一个输入参数,有一个输出参数。
/*
* 函数型接口,有一个输入参数,有一个输出
* 只要是函数式接口,都可以用lambda表达式简化
* */
public class FunctionDemo {
public static void main(String[] args) {
Function function=new Function<String,String>() {
@Override
public String apply(String str) {
return str;
}
};
System.out.println(function.apply("adjkfhg"));
// Function<String,String> function=str->{
// return str;
// };
// System.out.println(function.apply("ajdkfh"));
}
}
Predicate断定型接口:判断字符串是否为空。有一个输入参数,返回boolean值。
public class PredicateDemo {
public static void main(String[] args) {
//判断字符串是否为空
// Predicate<String> predicate=new Predicate<String>() {
// @Override
// public boolean test(String s) {
// return s.isEmpty();
// }
// };
// System.out.println(predicate.test("asdg"));
Predicate<String> predicate=str->{return str.isEmpty();};
System.out.println(predicate.test("sdd"));
}
}
Consumer消费型接口:有一个输入参数,没有返回值。
/*
* 只有参数,没有返回值
* */
public class ConsumerDemo {
public static void main(String[] args) {
// Consumer<String> consumer=new Consumer<String>() {
// @Override
// public void accept(String s) {
// System.out.println(s);
// }
// };
// consumer.accept("akjfh");
Consumer<String> consumer=str->{
System.out.println(str);
};
consumer.accept("aaaa");
}
}
Supplier供给型接口:有返回值,没有参数。
/*
* 供给型接口,没有参数只有返回值
* */
public class SupplierDemo {
public static void main(String[] args) {
// Supplier<Integer> supplier=new Supplier<Integer>() {
// @Override
// public Integer get() {
// return 1024;
// }
// };
// System.out.println(supplier.get());
Supplier<Integer> supplier=()->{return 1024;};
System.out.println(supplier.get());
}
}
十三:stream流式计算
/*
* 一行代码完成此题
* 1.ID必须是偶数
* 2.年龄必须大于23岁
* 3.用户名转为大写字母
* 4.用户名字母倒着排序
* 5.只能输出一个用户
* */
public class StreamTest {
public static void main(String[] args) {
User user1 = new User(1, "a", 21);
User user2 = new User(2, "b", 22);
User user3 = new User(3, "c", 23);
User user4 = new User(4, "d", 24);
User user5 = new User(6, "e", 25);
List<User> list = Arrays.asList(user1, user2, user3, user4, user5);
//计算交给stream流
//lambda表达式,链式编程,函数式接口,stream流式计算
list.stream()
.filter(user -> {return user.getId()%2==0;})
.filter(user -> {return user.getAge()>23;})
.map(user -> {return user.getName().toUpperCase();})
.sorted((u1,u2)->{return u2.compareTo(u1);})
.limit(1)
.forEach(System.out::println);
}
}
十四:ForkJoin详解
什么是forkjoin?并行执行任务!提高效率,大数据量!
特点:工作窃取。
分支合并
里面维护的都是双端队列
自定义ForkJoin类:
/*
* 如何使用ForkJoin
* 1.通过ForkJoinPool执行
* 2.计算任务forkjoinPool.execute(ForkJoinTask task)
* 3.计算类要继承ForkJoinTask
* */
public class ForkJoinDemo extends RecursiveTask<Long> {
private Long start;
private Long end;
private Long temp;
public ForkJoinDemo(Long start, Long end) {
this.start = start;
this.end = end;
}
@Override
protected Long compute() {
if((end-start)<temp){
Long sum=0L;
for (Long i=start;i<end;i++){
sum+=i;
}
return sum;
}else{
long middle=(start+end)/2;
ForkJoinDemo task1 = new ForkJoinDemo(start, middle);
task1.fork();
ForkJoinDemo task2 = new ForkJoinDemo(middle + 1, end);
task2.fork();
return task1.join()+task1.join();
}
}
}
测试:
public class ForkJoinTest {
public static void main(String[] args) throws ExecutionException, InterruptedException {
test1();//7323ms
//test2();//6039
//test3();//285ms
}
//普通方法
public static void test1(){
Long sum=0L;
long start = System.currentTimeMillis();
for (Long i = 0L; i < 10_0000_0000L; i++) {
sum+=i;
}
long end = System.currentTimeMillis();
System.out.println("sum="+sum+"时间:"+(end-start));
}
//ForkJoin方法
public static void test2() throws ExecutionException, InterruptedException {
long start=System.currentTimeMillis();
ForkJoinPool forkJoinPool = new ForkJoinPool();
ForkJoinDemo task = new ForkJoinDemo(0L, 10_0000_0000L);
ForkJoinTask<Long> submit = forkJoinPool.submit(task);
Long sum=submit.get();
Long end=System.currentTimeMillis();
System.out.println("sum="+sum+"时间:"+(end-start));
}
//stream流方法
public static void test3(){
long start=System.currentTimeMillis();
long sum=LongStream.rangeClosed(0L,10_0000_0000L).parallel().reduce(0,Long::sum);
long end=System.currentTimeMillis();
System.out.println("sum="+sum+"时间:"+(end-start));
}
}
十五:异步回调
Future设计的初衷:对将来的某个事件的结果进行建模。
public class Demo01 {
public static void main(String[] args) throws ExecutionException, InterruptedException {
// CompletableFuture<Void> completableFuture=CompletableFuture.runAsync(()->{
// try {
// TimeUnit.SECONDS.sleep(2);
// } catch (InterruptedException e) {
// e.printStackTrace();
// }
// System.out.println(Thread.currentThread().getName()+"runAsync=>Void");
// });
// System.out.println("1111");
// completableFuture.get();//获取阻塞执行结果
//有返回值的supplyAsync异步回调
//ajax,成功和失败的回调
CompletableFuture<Integer> completableFuture1=CompletableFuture.supplyAsync(()->{
System.out.println(Thread.currentThread().getName()+"supplyAsync=>Integer");
//int i=10/0;
return 1024;
});
System.out.println(completableFuture1.whenComplete((t,u)->{
System.out.println("t=>"+t);//正常的返回结果
System.out.println("u=>"+u);//错误信息
}).exceptionally((e)->{
System.out.println(e.getMessage());
return 233;
}).get());
}
}
十六、JMM
面试:请谈谈对Volatile的理解。
volatile是java虚拟机提供的轻量级的同步机制。
1.保证可见性(与JMM挂钩)。
2.不保证原子性。
3.禁止指令重排。
可见性:
public class JMMDemo {
//不加 volatile ,程序就会死循环!
//加 volatile,保证可见性!
private volatile static int num=0;
public static void main(String[] args) {//main
new Thread(()->{//对主内存的变化不知道
while (num==0){
}
}).start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
num=1;
System.out.println(num);
}
}
不保证原子性:
原子性:不可分割。线程A在执行任务的时候,是不能被打扰的,也不能被分割。要么同时成功,要么同时失败。
//volatile不保证原子性,用 AtomicInteger 解决原子性问题
public class Vdemo02 {
private volatile static AtomicInteger num=new AtomicInteger();
public static void add(){//不是一个原子性操作
//num++;
num.getAndIncrement();//AtomicInteger的加一方法,CAS!!!!
}
public static void main(String[] args) {
//理论上num结果应该为20000
for (int i = 1; i <= 20; i++) {
new Thread(()->{
for (int j = 1; j <=1000 ; j++) {
add();
}
}).start();
}
while (Thread.activeCount()>2){//main gc
Thread.yield();
}
System.out.println(Thread.currentThread().getName()+" "+num);
}
}
指令重排:
计算机并不一定按照你写的程序去执行。
源代码->编译器优化的重排->指令并行也可能重排->内存系统也会重排->执行。
处理器在进行指令重排是,考虑数据之间的依赖性。
volatile避免指令重排:内存屏障(CPU的指令)。
1.保证特定操作的执行顺序。
2.可以保证某些变量的内存可见性。
什么是JMM?
Java的内存模型,是一个概念,不存在。
JMM同步约定:
- 线程解锁前,必须把共享变量立即刷回主内存。
- 线程加锁前,必须读取主内存中最新值到工作内存中。
- 加锁和解锁是同一把锁。
Java内存模型定义了8种操作来完成,虚拟机必须保证每一种操作都是原子的、不可拆分的(double和long类型例外)。
- lock(锁定):作用于主内存的变量,它把一个变量标识为一条线程独占的状态。
- unlock(解锁):作用于主内存的变量,它把一个处于锁定状态的变量释放出来,释放后的变量才可以被其他线程锁定。
- read(读取):作用于主内存的变量,它把一个变量的值从主内存传输到线程的工作内存中,以便随后的load动作使用。
- load(载入):作用于工作内存的变量,它把read操作从主内存中得到的变量值放入工作内存的变量副本中。
- use(使用):作用于工作内存的变量,它把工作内存中一个变量的值传递给执行引擎,每当虚拟机遇到一个需要使用到变量的值的字节码指令时将会执行这个操作。
- assign(赋值):作用于工作内存的变量,它把一个从执行引擎接收到的值赋给工作内存的变量,每当虚拟机遇到一个给变量赋值的字节码指令时执行这个操作。
- store(存储):作用于工作内存的变量,它把工作内存中一个变量的值传送到主内存中,以便随后的write操作使用。
- write(写入):作用于主内存的变量,它把store操作从工作内存中得到的变量的值放入主内存的变量中。
8种操作必须满足的规则:
- 不允许read和load、store和write操作之一单独出现。即不允许一个变量从主内存读取了但工作内存不接受;或者从工作内存发起回写了但主内存不接受的情况出现。
- 不允许一个线程丢弃它的最近的assign操作。即变量在工作内存中改变了之后必须把该变化同步回主内存。
- 不允许一个线程无原因地(没有发生过任何assign操作)把数据从线程的工作内存同步回主内存。
- 一个新的变量只能在主内存中“诞生”,不允许在工作内存中直接使用一个未被初始化(load或assign)的变量,换句话说,就是对一个变量实施use、store操作之前,必须先执行过了assign和load操作。
- 一个变量在同一时刻只允许一条线程对其进行lock操作,但lock操作可以被同一条线程重复执行多次,多次执行lock后,只有执行相同次数的unlock操作,变量才会被解锁。
- 如果对一个变量执行lock操作,那将会清空工作内存中此变量的值,在执行引擎使用这个变量前,需要重新执行load或assign操作初始化变量的值。
- 如果一个变量事先没有被lock操作锁定,那就不允许对它执行unlock操作,也不允许去unlock一个被其他线程锁定住的变量。
- 对一个变量执行unlock操作之前,必须先把此变量同步回主内存中(执行store、write操作)。
十七、单例模式
1.饿汉式单例模式
/**
* 饿汉式单例模式,一加载就创建单例对象
*/
public class Hungry {
private Hungry(){
}
private static final Hungry hungry = new Hungry();
public static Hungry getInstance(){
return hungry;
}
}
2.懒汉式单例模式
public class Lazy {
//解决单例模式被反射破坏的方法
private static boolean hello=false;
private Lazy(){
if(hello==false){
hello=true;
}else{
throw new RuntimeException("不要试图使用反射破坏代码!");
}
System.out.println(Thread.currentThread().getName()+"ok");
}
//避免指令重排
private volatile static Lazy lazy;
// 双重检测锁模式的懒汉式单例 DCL懒汉式
public static Lazy getInstance(){
//加锁
if(lazy==null){
synchronized (Lazy.class){//锁的是class类
if(lazy==null){
lazy=new Lazy();//不是一个原子性操作
/*
* 1.分配内存空间
* 2.执行构造方法,初始化对象
* 3.把这个对象指向这个空间
* */
}
}
}
return lazy;
}
//多线程并发
//不加synchronized锁的情况下,无法保证单例模式,加了才能保证单例模式
// public static void main(String[] args){
// for (int i = 0; i < 10; i++) {
// new Thread(()->{
// lazy.getInstance();
// }).start();
// }
// }
//利用反射破坏单例模式
public static void main(String[] args) throws Exception{
//破坏单例模式
//方法一:
/*Lazy instance1 = Lazy.getInstance();
Constructor<Lazy> declaredConstructor = Lazy.class.getDeclaredConstructor(null);
//无视私有构造器
declaredConstructor.setAccessible(true);
//通过反射创建一个对象
Lazy instance2 = declaredConstructor.newInstance();
System.out.println(instance1);
System.out.println(instance2);*/
//破坏单例模式
//方法二:
/*Constructor<Lazy> declaredConstructor = Lazy.class.getDeclaredConstructor(null);
//无视私有构造器
declaredConstructor.setAccessible(true);
//通过反射创建一个对象
Lazy instance1 = declaredConstructor.newInstance();
Lazy instance2 = declaredConstructor.newInstance();//两个对象都用反射创建
System.out.println(instance1);
System.out.println(instance2);*/
//破坏单例模式
//方法三:
Field hello = Lazy.class.getDeclaredField("hello");
hello.setAccessible(true);
Constructor<Lazy> declaredConstructor = Lazy.class.getDeclaredConstructor(null);
//无视私有构造器
declaredConstructor.setAccessible(true);
//通过反射创建一个对象
Lazy instance1 = declaredConstructor.newInstance();
hello.set(instance1,false);
Lazy instance2 = declaredConstructor.newInstance();//两个对象都用反射创建
System.out.println(instance1);
System.out.println(instance2);
}
}
3.静态内部类创建单例模式
//静态内部类
public class Holder {
private Holder(){
}
public static Holder getInstance(){
return InnerClass.HOLDER;
}
public static class InnerClass{
private static final Holder HOLDER=new Holder();
}
}
4.枚举创建单例(反射机制无法破坏)
//enum 本身也是一个class类
//反射不能破坏枚举的单例
public enum EnumSingle {
INSTANCE;
public EnumSingle getInstance(){
return INSTANCE;
}
}
class Test{
public static void main(String[] args) throws Exception {
EnumSingle instance1 = EnumSingle.INSTANCE;
Constructor<EnumSingle> declaredConstructor = EnumSingle.class.getDeclaredConstructor(String.class,int.class);
declaredConstructor.setAccessible(true);
EnumSingle instance2 = declaredConstructor.newInstance();
System.out.println(instance1);
System.out.println(instance2);
}
}
十八:深入理解CAS
什么是CAS:比较当前工作内存中的值和主内存中的值,如果这个值是期望的,那么则执行操作!如果不是就一直循环!
public class WhatCAS {
public static void main(String[] args) {
AtomicInteger atomicInteger = new AtomicInteger(2020);
//public final boolean compareAndSet(int expect, int update) 期望 更新
System.out.println(atomicInteger.compareAndSet(2020, 2021));
System.out.println(atomicInteger.get());
atomicInteger.getAndIncrement();
System.out.println(atomicInteger.compareAndSet(2020, 2021));
System.out.println(atomicInteger.get());
}
}
Unsafe类
自旋锁
CAS的缺点:
1.循环会耗时。
2.一次性只能保证一个共享变量的原子性。
3.存在ABA问题。
什么是ABA问题?就是狸猫换太子的意思。
解决:原子引用,带版本号的原子操作。
//解决ABA问题,运用原子引用!!!
//与乐观锁的原理相同!
public class CASDemo {
//AtomicStampedReference注意,如果泛型是一个包装类,注意对象的引用问题
public static void main(String[] args) {
//AtomicInteger atomicInteger = new AtomicInteger(2023);
//对于我们平时写的SQL:比较并交换!
AtomicStampedReference<Integer> reference = new AtomicStampedReference<>(1,1);
new Thread(()->{
int stamp = reference.getStamp();//获得目前版本号
System.out.println("a1=>"+stamp);
//加延时,保证版本号a1和b1刚拿到的时候是相同的
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
//相当于乐观锁的version+1
System.out.println(reference.compareAndSet(1, 2,
reference.getStamp(), reference.getStamp() + 1));
System.out.println("a2=>"+reference.getStamp());
//将期望值又改为1
System.out.println(reference.compareAndSet(2, 1,
reference.getStamp(), reference.getStamp() + 1));
System.out.println("a3=>"+reference.getStamp());
},"a").start();
new Thread(()->{
int stamp = reference.getStamp();//获得目前版本号
System.out.println("b1=>"+stamp);
//加延时,保证版本号a1和b1刚拿到的时候是相同的
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
//相当于乐观锁的version+1
System.out.println(reference.compareAndSet(1, 6,
stamp, stamp + 1));
System.out.println("b2=>"+reference.getStamp());
},"b").start();
}
}
十九、各种锁的理解
1.公平锁:非常公平,不能插队,必须先来后到!
2.非公平锁:不公平,可以插队,(默认都是非公平)。
public ReentrantLock() {
sync = new NonfairSync();
}
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
3.可重入锁(递归锁)
Synchronized版
//Synchronized
public class Demo01 {
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()+"sms");
call();//第二把锁
}
public synchronized void call(){
System.out.println(Thread.currentThread().getName()+"call");
}
}
lock版
public class Demo02 {
public static void main(String[] args) {
Phone2 phone = new Phone2();
new Thread(()->{
phone.sms();
},"A").start();
new Thread(()->{
phone.sms();
},"B").start();
}
}
class Phone2{
Lock lock=new ReentrantLock();
public void sms(){
lock.lock();//第一把锁
try {
System.out.println(Thread.currentThread().getName()+"sms");
call();//第二把锁
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void call(){
lock.lock();
try {
System.out.println(Thread.currentThread().getName()+"call");
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
4.自旋锁
Spinlock
//自旋锁
public class SpinlockDemo {
//不同类型默认的值不同 int-> 0 Thread为空-> null
//通过原子引用实现
AtomicReference<Thread> atomicReference=new AtomicReference<>();
//加锁
public void myLock(){
Thread thread = Thread.currentThread();
System.out.println(Thread.currentThread().getName()+"==> myLock");
//自旋锁
while(!atomicReference.compareAndSet(null,thread)){
}
}
//解锁
public void myUnLock(){
Thread thread = Thread.currentThread();
System.out.println(Thread.currentThread().getName()+"==>myUnLock");
atomicReference.compareAndSet(thread,null);
}
}
test测试类:
public class TestSpinLock {
public static void main(String[] args) throws InterruptedException {
// ReentrantLock reentrantLock=new ReentrantLock();
// reentrantLock.lock();
// reentrantLock.unlock();
//底层使用的是自旋锁 CAS
SpinlockDemo lock = new SpinlockDemo();
new Thread(()->{
lock.myLock();
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
lock.myUnLock();
}
},"T1").start();
TimeUnit.SECONDS.sleep(1);
new Thread(()->{
lock.myLock();
try {
TimeUnit.SECONDS.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
lock.myUnLock();
}
},"T2").start();
}
}
5.死锁
死锁:线程A持有锁A,想要获得锁B;线程B持有锁B,想要获得锁A。
public class DeadLockDemo {
public static void main(String[] args) {
String lockA="lockA";
String lockB="lockB";
new Thread(new MyThread(lockA,lockB),"T1").start();
new Thread(new MyThread(lockB,lockA),"T2").start();
}
}
class MyThread implements Runnable{
private String lockA;
private String lockB;
public MyThread(String lockA,String lockB){
this.lockA=lockA;
this.lockB=lockB;
}
@SneakyThrows
@Override
public void run() {
synchronized (lockA){
System.out.println(Thread.currentThread().getName()+"lock:"+lockA+"=>get"+lockB);
TimeUnit.SECONDS.sleep(2);
synchronized (lockB){
System.out.println(Thread.currentThread().getName()+"lock:"+lockB+"=>get"+lockA);
}
}
}
}
解决方法:查看堆栈信息
1.使用 jps -l 定位进程号。
2.使用 jstack 进程号 查看进程信息
面试或者工作中,排查问题:
1.日志
2.堆栈信息