JUC学习笔记

119 阅读24分钟

JUC

1. 什么是JUC

java.util 工具包

image.png

普通的实现多线程的方式如Runnable 没有返回值,效率比Callable低。

2. 线程和进程

进程

一个程序,QQ.exe,Music.exe 程序的集合。

image.png

一个进程往往可以包含多个线程,至少包含一个。

Java 默认有几个线程?

2个。main、GC

线程

开了一个进程Typora,写字,自动保存(单独的线程负责)

对于java而言:Thread/Runable/Callable

Java真的可以开启线程吗?

不可以,实际是调用的底层的C++,java无法直接操作硬件。

并发、并行

并发

多个线程操作同一个资源

并行

多个线程可以同时执行。

并发变成的本质:

充分利用CPU的资源。

线程的状态

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:
         * <ul>
         *   <li>{@link Object#wait() Object.wait} with no timeout</li>
         *   <li>{@link #join() Thread.join} with no timeout</li>
         *   <li>{@link LockSupport#park() LockSupport.park}</li>
         * </ul>
         *
         * <p>A thread in the waiting state is waiting for another thread to
         * perform a particular action.
         *
         * For example, a thread that has called <tt>Object.wait()</tt>
         * on an object is waiting for another thread to call
         * <tt>Object.notify()</tt> or <tt>Object.notifyAll()</tt> on
         * that object. A thread that has called <tt>Thread.join()</tt>
         * 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:
         * <ul>
         *   <li>{@link #sleep Thread.sleep}</li>
         *   <li>{@link Object#wait(long) Object.wait} with timeout</li>
         *   <li>{@link #join(long) Thread.join} with timeout</li>
         *   <li>{@link LockSupport#parkNanos LockSupport.parkNanos}</li>
         *   <li>{@link LockSupport#parkUntil LockSupport.parkUntil}</li>
         * </ul>
         */
        TIMED_WAITING,

        /**
         * Thread state for a terminated thread.
         * The thread has completed execution.
         */
        TERMINATED;
    }

Thread类中有个枚举类State

wait/sleep 的区别

  1. 来自不同的类

    wait->Object

    sleep->Thread

  2. 关于锁的释放

    wait会释放锁

    sleep不会释放锁

  3. 使用的范围不同

    wait必须在同步代码块中

    sleep可以在任何地方

  4. 是否需要捕获异常

    wait不需要捕获异常

    sleep必须要捕获异常

3. Lock 锁

传统的Syncronized

Lock

image.png

基本使用方法

  • Lock l = ...; l.lock(); try { // access the resource protected by this lock } finally { l.unlock(); } 
    

ReentrantLock: 可重入锁(常用)

ReentrantReadWriteLock.ReadLock:读锁

ReentrantReadWriteLock.WriteLock:写锁

    public ReentrantLock() {
        sync = new NonfairSync(); //NonfairSync 非公平锁
    }

    /**
     * Creates an instance of {@code ReentrantLock} with the
     * given fairness policy.
     *
     * @param fair {@code true} if this lock should use a fair ordering policy
     */
    public ReentrantLock(boolean fair) {
        sync = fair ? new FairSync() : new NonfairSync();//FairSync 公平锁
    }

公平锁

十分公平,可以按先来后到的顺序

非公平锁

十分不公平,可以插队(默认)

示例

import java.util.concurrent.locks.ReentrantLock;

public class TestLock implements Runnable{
    int ticketNums = 10;

    //定义lock锁
    private final ReentrantLock lock = new ReentrantLock();

    @Override
    public void run() {
        while(true){
            try {
                lock.lock();
                if(ticketNums > 0){
                    Thread.sleep(1000);
                    System.out.println(ticketNums--);
                }else{
                    break;
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                lock.unlock();
            }
        }

    }

    public static void main(String[] args) {
        TestLock testLock =  new TestLock();

        new Thread(testLock).start();
        new Thread(testLock).start();
        new Thread(testLock).start();
    }
}

Syncronized 和 Lock 区别

  1. Syncronized 是内置的java关键字,Lock是一个java类
  2. Syncronized 无法判断获取锁的状态,Lock可以判断是否获取到了锁
  3. Syncronized 会自动释放锁,Lock 必须要手动释放锁。如果不释放,会导致死锁!
  4. Syncronized 等待的线程会傻傻的等,Lock锁就不一定会等待下去
  5. Syncronized 可重入锁,不可以中断,非公平。Lock,可重入锁,可以中断锁,公平与否可以自己设置
  6. Syncronized 适合锁少量的代码同步问题,Lock 锁适合锁大量的同步代码

总结来说,Lock锁灵活性更高!

锁是什么

锁是什么,如何判断锁是谁?

--参见后面第7节内容。

4. 传统的生产者消费者问题

启动两个线程没有问题

public class A {
    public static void main(String[] args) {
        Data data = new Data();
        new Thread(()->{
            try {
                for (int i = 0; i < 10; i++) {
                    data.increment();
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        },"A").start();
        new Thread(()->{
            try {
                for (int i = 0; i < 10; i++) {
                    data.decrement();
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        },"B").start();
    }

}

class Data{
    int number;

    //+1
    public synchronized void increment() throws InterruptedException {
        if(number != 0){
            this.wait();
        }
        number++;
        System.out.println(Thread.currentThread().getName() + "=>" + number);
        this.notifyAll();
    }

    //-1
    public synchronized void decrement() throws InterruptedException {
        if(number == 0){
            this.wait();
        }
        number--;
        System.out.println(Thread.currentThread().getName() + "=>" + number);
        this.notifyAll();
    }
}


测试结果如下:

A=>1
B=>0
A=>1
B=>0
A=>1
B=>0
A=>1
B=>0
A=>1
B=>0
A=>1
B=>0
A=>1
B=>0
A=>1
B=>0
A=>1
B=>0
A=>1
B=>0

启动4个线程存在虚假唤醒问题

启动A、C两个线程执行加运算,B、D两个线程执行减运算

public class A {
    public static void main(String[] args) {
        Data data = new Data();
        new Thread(()->{
            try {
                for (int i = 0; i < 10; i++) {
                    data.increment();
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        },"A").start();
        new Thread(()->{
            try {
                for (int i = 0; i < 10; i++) {
                    data.decrement();
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        },"B").start();
        new Thread(()->{
            try {
                for (int i = 0; i < 10; i++) {
                    data.increment();
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        },"C").start();
        new Thread(()->{
            try {
                for (int i = 0; i < 10; i++) {
                    data.decrement();
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        },"D").start();
    }
}


class Data{
    int number;

    //+1
    public synchronized void increment() throws InterruptedException {
        if(number != 0){
            this.wait();
        }
        number++;
        System.out.println(Thread.currentThread().getName() + "=>" + number);
        this.notifyAll();
    }

    //-1
    public synchronized void decrement() throws InterruptedException {
        if(number == 0){
            this.wait();
        }
        number--;
        System.out.println(Thread.currentThread().getName() + "=>" + number);
        this.notifyAll();
    }
}


测试结果如下:

A=>1
B=>0
A=>1
B=>0
A=>1
B=>0
A=>1
B=>0
C=>1
B=>0
A=>1
B=>0
C=>1
B=>0
A=>1
B=>0
C=>1
D=>0
C=>1
B=>0
A=>1
B=>0
C=>1
D=>0
C=>1
A=>2    //不正常的数据
C=>3    //不正常的数据
D=>2    //不正常的数据
D=>1
D=>0
C=>1
A=>2
C=>3
D=>2
D=>1
D=>0
C=>1
A=>2
D=>1
D=>0

产生原因

查看java API 文档,java.lang.Object 类的wait()方法:

image-20220301192851056

修改方法

if 判断改成 while 判断即可。

class Data{
    int number;

    //+1
    public synchronized void increment() throws InterruptedException {
        while(number != 0){  //修改点
            this.wait();
        }
        number++;
        System.out.println(Thread.currentThread().getName() + "=>" + number);
        this.notifyAll();
    }

    //-1
    public synchronized void decrement() throws InterruptedException {
        while(number == 0){  //修改点
            this.wait();
        }
        number--;
        System.out.println(Thread.currentThread().getName() + "=>" + number);
        this.notifyAll();
    }
}

5. Lock 版的生产者消费者问题

image.png

Lock 使用方式与syncronized 方式对比如上。

主要使用Condition作为同步监视器:

condition.await(): 等待

condition.signalAll():唤醒

实现代码

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class B {
    public static void main(String[] args) {
        Data1 data = new Data1();
        new Thread(()->{
            try {
                for (int i = 0; i < 10; i++) {
                    data.increment();
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        },"A").start();
        new Thread(()->{
            try {
                for (int i = 0; i < 10; i++) {
                    data.decrement();
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        },"B").start();
        new Thread(()->{
            try {
                for (int i = 0; i < 10; i++) {
                    data.increment();
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        },"C").start();
        new Thread(()->{
            try {
                for (int i = 0; i < 10; i++) {
                    data.decrement();
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        },"D").start();
    }
}


class Data1{
    int number;
    Lock lock = new ReentrantLock();
    Condition condition = lock.newCondition();

    //+1
    public void increment() throws InterruptedException {
        lock.lock();
        try {
            while(number != 0){
                condition.await();
            }
            number++;
            System.out.println(Thread.currentThread().getName() + "=>" + number);
            condition.signalAll();
        } catch (Exception e) {
            e.printStackTrace();
        }finally {
            lock.unlock();
        }

    }

    //-1
    public void decrement() throws InterruptedException {
        lock.lock();
        try {
            while(number == 0){
                condition.await();
            }
            number--;
            System.out.println(Thread.currentThread().getName() + "=>" + number);
            condition.signalAll();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
}


测试结果

A=>1
B=>0
A=>1
D=>0
A=>1
D=>0
A=>1
D=>0
A=>1
D=>0
A=>1
D=>0
A=>1
D=>0
A=>1
D=>0
A=>1
D=>0
A=>1
D=>0
C=>1
D=>0
C=>1
B=>0
C=>1
B=>0
C=>1
B=>0
C=>1
B=>0
C=>1
B=>0
C=>1
B=>0
C=>1
B=>0
C=>1
B=>0
C=>1
B=>0

从上面的执行结果可以看出,代码逻辑是没有问题的,能够交替加和减。

但是没有做到有序的执行,A->B->C->D

6. Condition 实现精准通知唤醒

实现代码

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class C {
    public static void main(String[] args) {
        Data3 data = new Data3();
        new Thread(()->{
            for (int i = 0; i < 10; i++) {
                data.printA();
            }
        },"A").start();
        new Thread(()->{
            for (int i = 0; i < 10; i++) {
                data.printB();
            }
        },"B").start();
        new Thread(()->{
            for (int i = 0; i < 10; i++) {
                data.printC();
            }
        },"C").start();
    }
}

class Data3{
    private Lock lock = new ReentrantLock();
    private Condition condition1 = lock.newCondition();
    private Condition condition2 = lock.newCondition();
    private Condition condition3 = lock.newCondition();

    private int number = 1;

    public void printA(){
        lock.lock();
        try {
            while(number != 1){
                condition1.await();
            }
            System.out.println(Thread.currentThread().getName() + "AAAAAA");
            //唤醒B
            number = 2;
            condition2.signal();//注意是用B的Condition去调用signal()方法
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }

    }

    public void printB(){
        lock.lock();
        try {
            while(number != 2){
                condition2.await();
            }
            System.out.println(Thread.currentThread().getName() + "BBBBBB");
            //唤醒C
            number = 3;
            condition3.signal();//注意是用C的Condition去调用signal()方法
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

    public void printC(){
        lock.lock();
        try {
            while(number != 3){
                condition3.await();
            }
            System.out.println(Thread.currentThread().getName() + "CCCCCC");
            //唤醒A
            number = 1;
            condition1.signal();//注意是用A的Condition去调用signal()方法
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

}

测试结果

AAAAAAA
BBBBBBB
CCCCCCC
AAAAAAA
BBBBBBB
CCCCCCC
AAAAAAA
BBBBBBB
CCCCCCC
AAAAAAA
BBBBBBB
CCCCCCC
AAAAAAA
BBBBBBB
CCCCCCC
AAAAAAA
BBBBBBB
CCCCCCC
AAAAAAA
BBBBBBB
CCCCCCC
AAAAAAA
BBBBBBB
CCCCCCC
AAAAAAA
BBBBBBB
CCCCCCC
AAAAAAA
BBBBBBB
CCCCCCC

7. 八锁现象彻底理解锁

关于锁的8个问题。

syncronized 锁的对象是方法的调用者

注意

  • 如果是static方法,则锁的是Class类;

  • 如果是非static方法,则锁的是new的实例对象;

  • 如果是普通的方法,则不需要等待锁

参考如下视频:

www.bilibili.com/video/BV1B7…

8. CopyOnWriteArrayList

并发下ArrayList 不安全。

有以下三种解决方案:

  1. Vector
  2. Collections.synchronizedList
  3. CopyOnWriteArrayList
import java.util.List;
import java.util.UUID;
import java.util.concurrent.CopyOnWriteArrayList;

public class ListTest {
    public static void main(String[] args) {
        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);
            }).start();
        }
    }
}

CopyOnWrite 写入时复制

COW 是计算机程序设计领域的一种优化策略。

在写入的时候进行复制,避免覆盖造成数据问题。

9. CopyOnWriteArraySet

多线程下用HashSet不安全:

import java.util.HashSet;
import java.util.Set;
import java.util.UUID;

public class SetTest {
    public static void main(String[] args) {
        Set<String> set = new HashSet<>();
        for (int i = 0; i < 30; i++) {
            new Thread(()->{
                set.add(UUID.randomUUID().toString().substring(0,5));
                System.out.println(set);
            },String.valueOf(i)).start();
        }
    }
}

会抛如下的异常:

java.util.ConcurrentModificationException   //并发修改异常
	at java.util.HashMap$HashIterator.nextNode(HashMap.java:1429)
	at java.util.HashMap$KeyIterator.next(HashMap.java:1453)
	at java.util.AbstractCollection.toString(AbstractCollection.java:461)
	at java.lang.String.valueOf(String.java:2982)
	at java.io.PrintStream.println(PrintStream.java:821)
	at SetTest.lambda$main$0(SetTest.java:12)
	at SetTest$$Lambda$1/2716239.run(Unknown Source)
	at java.lang.Thread.run(Thread.java:745)
java.util.ConcurrentModificationException

改进

import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.CopyOnWriteArraySet;

public class SetTest {
    public static void main(String[] args) {
//        Set<String> set = new HashSet<>();
//        Set<String> set = Collections.synchronizedSet(new HashSet<>()); //这种也是没有问题的
        Set<String> set = new CopyOnWriteArraySet<>();
        for (int i = 0; i < 30; i++) {
            new Thread(()->{
                set.add(UUID.randomUUID().toString().substring(0,5));
                System.out.println(set);
            },String.valueOf(i)).start();
        }
    }
}

hashset 的底层是什么?

就是HashMap。

10. ConcurrentHashMap

import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;

public class MapTest {
    public static void main(String[] args) {
        Map<String,String> map = new ConcurrentHashMap<String, String>();

        for (int i = 0; i < 30; i++) {
            new Thread(()->{
                map.put(Thread.currentThread().getName(), UUID.randomUUID().toString().substring(0,5));
                System.out.println(map);
            },String.valueOf(i)).start();
        }
    }
}

11. 走近Callable

@FunctionalInterface
public interface Callable<V>返回结果并可能引发异常的任务。 实现者定义一个没有参数的单一方法,称为call 。 
Callable接口类似于Runnable ,因为它们都是为其实例可能由另一个线程执行的类设计的。 然而,A Runnable不返回结果,也不能抛出被检查的异常。 

该Executors类包含的实用方法,从其他普通形式转换为Callable类。

Callable的特点:

  • 可以有返回结果
  • 允许抛出异常
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

public class CallableTest {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        MyThread myThread = new MyThread();
        FutureTask futureTask = new FutureTask(myThread);//FutureTask相当于一个适配类

        new Thread(futureTask, "A").start();
        new Thread(futureTask, "B").start();//结果会被缓存,提高效率

        //可能会阻塞,需要等待
        Integer o = (Integer) futureTask.get();//获取Callable的返回结果的方法
        System.out.println("Callable 返回结果:" + o);
    }

}

class MyThread implements Callable<Integer> {
    @Override
    public Integer call() throws Exception {
        System.out.println("call()" + ":" + Thread.currentThread().getName());
        return 1024;
    }
}

测试结果:

call():A
Callable 返回结果:1024

注意:

  • 有缓存
  • 结果可能需要等待,会阻塞

12. 常用的辅助类

12.1 CountDownLatch

减法计数器

  • public class CountDownLatch
    extends Object
    

    允许一个或多个线程等待直到在其他线程中执行的一组操作完成的同步辅助。

    A CountDownLatch用给定的计数初始化。 await方法阻塞,直到由于countDown()方法的调用而导致当前计数达到零,之后所有等待线程被释放,并且任何后续的await 调用立即返回。 这是一个一次性的现象 - 计数无法重置。 如果您需要重置计数的版本,请考虑使用CyclicBarrier

    A CountDownLatch是一种通用的同步工具,可用于多种用途。 一个CountDownLatch为一个计数的CountDownLatch用作一个简单的开/关锁存器,或者门:所有线程调用await在门口等待,直到被调用countDown()的线程打开。 一个CountDownLatch初始化N可以用来做一个线程等待,直到N个线程完成某项操作,或某些动作已经完成N次。

    CountDownLatch一个有用的属性是,它不要求调用countDown线程等待计数到达零之前继续,它只是阻止任何线程通过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 = 0; i < 6; i++) {
            new Thread(()->{
                System.out.println(Thread.currentThread().getName() +" Go out");
                countDownLatch.countDown();//数量-1
            },String.valueOf(i)).start();
        }

        countDownLatch.await();//等待计数器归零,然后才执行后面的逻辑
        System.out.println("Close Door");

    }
}

测试结果

0 Go out
2 Go out
1 Go out
3 Go out
4 Go out
5 Go out
Close Door

原理

countDownLatch.countDown(); //数量-1

countDownLatch.await(); //等待计数器归零,然后才执行后面的逻辑

每次有线程调用countDown() 后数量-1,如果计数器变为0,countDownLatch.await() 就会被唤醒,继续执行后面的逻辑。

12.2 CyclicBarrier

加法计数器

  • public class CyclicBarrier
    extends Object
    

    允许一组线程全部等待彼此达到共同屏障点的同步辅助。 循环阻塞在涉及固定大小的线程方的程序中很有用,这些线程必须偶尔等待彼此。 屏障被称为循环 ,因为它可以在等待的线程被释放之后重新使用。

示例

import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;

public class CyclicBarrierDemo {
    public static void main(String[] args) {
        //集齐7颗龙珠召唤神龙
        //召唤神龙的线程
        CyclicBarrier cyclicBarrier = new CyclicBarrier(7,()->{
            System.out.println("召唤神龙成功!");
        });

        for (int i = 1; i < 8; i++) {
            final int tmp = i;
            new Thread(()->{
                System.out.println(Thread.currentThread().getName() + "收集" + tmp + "个龙珠");
                try {
                    cyclicBarrier.await();//等待
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } catch (BrokenBarrierException e) {
                    e.printStackTrace();
                }
            }).start();
        }
    }
}

测试结果

Thread-0收集1个龙珠
Thread-2收集3个龙珠
Thread-1收集2个龙珠
Thread-5收集6个龙珠
Thread-3收集4个龙珠
Thread-4收集5个龙珠
Thread-6收集7个龙珠
召唤神龙成功!

注意:

CyclicBarrier 的 await() 方法是在每个任务的线程内的。而CountDownLatch 的 await() 方法是在外面。

CyclicBarrier 没有加1的方法,不需要执行加1操作。而CountDownLatch 每执行完一个子任务后要执行减1操作。

12.3 Semaphore

  • public class Semaphore
    extends Object
    implements Serializable
    

    一个计数信号量。在概念上,信号量维持一组许可证。如果有必要,每个acquire()都会阻塞,直到许可证可用,然后才能使用它。每个release()添加许可证,潜在地释放阻塞获取方。但是,没有使用实际的许可证对象;Semaphore只保留可用数量的计数,并相应地执行。

    信号量通常用于限制线程数,而不是访问某些(物理或逻辑)资源。

示例

import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;

public class SemaphoreDemo {
    public static void main(String[] args) {
        //线程数量 停车位。限流!
        Semaphore semaphore = new Semaphore(3);

        for (int i = 1; i < 7; i++) {
            new Thread(()-> {
                try {
                    //acquire() 得到
                    semaphore.acquire();
                    System.out.println(Thread.currentThread().getName() + "抢到车位");
                    TimeUnit.SECONDS.sleep(2);
                    System.out.println(Thread.currentThread().getName() + "离开车位");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    semaphore.release();//release()释放
                }
            },String.valueOf(i)).start();
        }
    }
}

测试结果

1抢到车位
3抢到车位
2抢到车位
1离开车位
2离开车位
3离开车位
4抢到车位
6抢到车位
5抢到车位
4离开车位
5离开车位
6离开车位

原理

semaphore.acquire() :获得,如果已经满了,就等待,等待被释放为止

semaphore.release(): 释放,会将当前的信号量释放+1,然后唤醒等待的线程

作用:

多个共享资源互斥的使用!并发限流,控制最大的线程数。

13. ReadWriteLock

java.util.concurrent.locks

Interface ReadWriteLock


  • public interface ReadWriteLock
    

    A ReadWriteLock维护一对关联的locks ,一个用于只读操作,一个用于写入。read lock可以由多个阅读器线程同时进行,只要没有作者。write lock是独家的。

    所有ReadWriteLock实现必须保证的存储器同步效应writeLock操作(如在指定Lock接口)也保持相对于所述相关联的readLock 。 也就是说,一个线程成功获取读锁定将会看到在之前发布的写锁定所做的所有更新。

    读写锁允许访问共享数据时的并发性高于互斥锁所允许的并发性。 它利用了这样一个事实:一次只有一个线程( 写入线程)可以修改共享数据,在许多情况下,任何数量的线程都可以同时读取数据(因此读取器线程)。 从理论上讲,通过使用读写锁允许的并发性增加将导致性能改进超过使用互斥锁。 实际上,并发性的增加只能在多处理器上完全实现,然后只有在共享数据的访问模式是合适的时才可以。

示例

import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

public class ReadWriteLockDemo {
    //读写锁示例
    public static void main(String[] args) {
//        MyCacheLock myCache = new MyCacheLock();//加了读写锁
        MyCache myCache = new MyCache();//没有加读写锁
        for (int i = 1; i <= 5; i++) {
            final int tmp = i;
            new Thread(()->{
                myCache.put(tmp+"",tmp+"");
            },String.valueOf(i)).start();
        }

        for (int i = 1; i <= 5; i++) {
            final int tmp = i;
            new Thread(()->{
                myCache.get(tmp+"");
            },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() + "写入begin" + key);
        map.put(key, value);
        System.out.println(Thread.currentThread().getName() + "写入over" + key);
    }

    //取,读
    public void get(String key){
        System.out.println(Thread.currentThread().getName() + "读取begin" + key);
        map.get(key);
        System.out.println(Thread.currentThread().getName() + "读取over" + key);
    }
}


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() + "写入begin" + key);
            map.put(key, value);
            System.out.println(Thread.currentThread().getName() + "写入over" + key);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.writeLock().unlock();
        }
    }

    //取,读的时候所有线程都可以读
    public void get(String key){
        lock.readLock().lock();
        try {
            System.out.println(Thread.currentThread().getName() + "读取begin" + key);
            map.get(key);
            System.out.println(Thread.currentThread().getName() + "读取over" + key);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.readLock().unlock();
        }
    }
}

测试结果

  • 没有加读写锁

    2写入begin2
    1写入begin1
    2写入over2
    1写入over1
    3写入begin3
    3写入over3
    4写入begin4
    4写入over4
    5写入begin5
    5写入over5
    1读取begin1
    1读取over1
    2读取begin2
    2读取over2
    4读取begin4
    5读取begin5
    3读取begin3
    5读取over5
    4读取over4
    3读取over3
    

    从结果可以看出,写入的时候存在插队的情况的。

  • 加了读写锁

    1写入begin1
    1写入over1
    2写入begin2
    2写入over2
    3写入begin3
    3写入over3
    4写入begin4
    4写入over4
    5写入begin5
    5写入over5
    1读取begin1
    1读取over1
    3读取begin3
    3读取over3
    2读取begin2
    2读取over2
    4读取begin4
    5读取begin5
    5读取over5
    4读取over4
    

    从结果可以看出写入的时候是不会被插队的,1个线程写完了才会执行后面的写操作,但是读是没有限制的。

注意几个专有名词

独占锁:就是写锁,一次只能被一个线程占有

共享锁:就是读锁,多个线程可以同时占有

14. 阻塞队列

14.1 BlockingQueue

java.util.concurrent

Interface BlockingQueue

什么情况下我们会使用阻塞队列?

  • 多线程并发处理

  • 线程池

image.png

学会使用队列

添加、移除

四组API

方式抛出异常有返回值(不抛出异常)阻塞等待超时等待
添加addoffer()putoffer(e,time,timeUnit)
移除removepoll()takepoll(time,timUnit)
检测队首元素elementpeek--
  1. 抛出异常
  2. 不会抛出异常
  3. 阻塞等待
  4. 超时等待

14.2 同步队列SynchronousQueue

java.util.concurrent

Class SynchronousQueue

  • java.lang.Object


  • public class SynchronousQueue<E>
    extends AbstractQueue<E>
    implements BlockingQueue<E>, Serializable
    

    A blocking queue其中每个插入操作必须等待另一个线程相应的删除操作,反之亦然。 同步队列没有任何内部容量,甚至没有一个容量。 你不能peek在同步队列,因为一个元素,当您尝试删除它才存在; 您无法插入元素(使用任何方法),除非另有线程正在尝试删除它; 你不能迭代,因为没有什么可以迭代。 队列的头部是第一个排队的插入线程尝试添加到队列中的元素; 如果没有这样排队的线程,那么没有元素可用于删除,并且poll()将返回null 。 为了其他Collection方法(例如contains )的目的, SynchronousQueue充当空集合。 此队列不允许null元素。

SynchronousQueue 和其他的 BlockingQueue 不一样,SynchronousQueue 不存储元素,put了一个元素,必须从里面先take取出来,否则不能再put进去值。

示例

import java.util.concurrent.BlockingDeque;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.SynchronousQueue;
import java.util.concurrent.TimeUnit;

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();
            } finally {
            }
        }, "T1").start();

        new Thread(() -> {
            try {
                TimeUnit.SECONDS.sleep(3);
                System.out.println(Thread.currentThread().getName() + " get " + blockingQueue.take());
                TimeUnit.SECONDS.sleep(3);
                System.out.println(Thread.currentThread().getName() + " get " + blockingQueue.take());
                TimeUnit.SECONDS.sleep(3);
                System.out.println(Thread.currentThread().getName() + " get " + blockingQueue.take());
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
            }
        }, "T2").start();
    }
}

测试结果

T1 put 1
T2 get 1
T1 put 2
T2 get 2
T1 put 3
T2 get 3

15. 池化技术及线程池使用

15.1 线程池的三大方法

线程池的好处:

  1. 降低资源的消耗
  2. 提高相应的速度
  3. 方便管理

线程复用,控制最大并发数,管理线程。

线程池的三大方法

import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * 线程池示例
 */
public class ThreadPoolDemo {
    public static void main(String[] args) {
        //创建线程池的三种方式
        ExecutorService threadPool = Executors.newSingleThreadExecutor();//创建单个线程
//        ExecutorService threadPool = Executors.newFixedThreadPool(5);//创建一个固定的线程池的大小
//        ExecutorService threadPool = Executors.newCachedThreadPool();//可伸缩的
        try {
            for (int i = 0; i < 10; i++) {
                threadPool.execute(()->{
                    System.out.println(Thread.currentThread().getName() + " ok");
                });
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            //最后要关闭线程池
            threadPool.shutdown();
        }
    }
}

  • 单个线程的结果:
pool-1-thread-1 ok
pool-1-thread-1 ok
pool-1-thread-1 ok
pool-1-thread-1 ok
pool-1-thread-1 ok
pool-1-thread-1 ok
pool-1-thread-1 ok
pool-1-thread-1 ok
pool-1-thread-1 ok
pool-1-thread-1 ok
  • 固定为5个的线程池的结果
pool-1-thread-1 ok
pool-1-thread-5 ok
pool-1-thread-3 ok
pool-1-thread-2 ok
pool-1-thread-4 ok
pool-1-thread-2 ok
pool-1-thread-3 ok
pool-1-thread-5 ok
pool-1-thread-1 ok
pool-1-thread-4 ok
  • 可伸缩的线程池的结果
pool-1-thread-1 ok
pool-1-thread-6 ok
pool-1-thread-5 ok
pool-1-thread-4 ok
pool-1-thread-3 ok
pool-1-thread-2 ok
pool-1-thread-7 ok
pool-1-thread-8 ok
pool-1-thread-10 ok
pool-1-thread-9 ok

注意:

可伸缩的线程池,并不是有多少个线程就创建多少个线程,当上面的循环次数很大,如超过50次时,也不一定会有50个线程大小。

将循环次数改成50:

for (int i = 0; i < 50; i++) {//将循环次数改成50
                threadPool.execute(()->{
                    System.out.println(Thread.currentThread().getName() + " ok");
                });
            }

测试结果如下:

pool-1-thread-1 ok
pool-1-thread-6 ok
pool-1-thread-5 ok
pool-1-thread-4 ok
pool-1-thread-2 ok
pool-1-thread-3 ok
pool-1-thread-2 ok
pool-1-thread-7 ok
pool-1-thread-9 ok
pool-1-thread-8 ok
pool-1-thread-10 ok
pool-1-thread-4 ok
pool-1-thread-1 ok
pool-1-thread-3 ok
pool-1-thread-6 ok
pool-1-thread-3 ok
pool-1-thread-5 ok
pool-1-thread-7 ok
pool-1-thread-2 ok
pool-1-thread-6 ok
pool-1-thread-2 ok
pool-1-thread-7 ok
pool-1-thread-10 ok
pool-1-thread-8 ok
pool-1-thread-9 ok
pool-1-thread-2 ok
pool-1-thread-6 ok
pool-1-thread-7 ok
pool-1-thread-14 ok
pool-1-thread-2 ok
pool-1-thread-10 ok
pool-1-thread-12 ok
pool-1-thread-11 ok
pool-1-thread-3 ok
pool-1-thread-11 ok
pool-1-thread-5 ok
pool-1-thread-1 ok
pool-1-thread-18 ok  //最多的时候也就18个
pool-1-thread-4 ok
pool-1-thread-17 ok
pool-1-thread-16 ok
pool-1-thread-14 ok
pool-1-thread-2 ok
pool-1-thread-10 ok
pool-1-thread-15 ok
pool-1-thread-6 ok
pool-1-thread-7 ok
pool-1-thread-13 ok
pool-1-thread-8 ok
pool-1-thread-9 ok

15.2 线程池七大参数

查看上面线程池的三大方法的底层原理,可以发现都是调用了ThreadPoolExecutor() 方法。

//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.corePoolSize = corePoolSize;
        this.maximumPoolSize = maximumPoolSize;
        this.workQueue = workQueue;
        this.keepAliveTime = unit.toNanos(keepAliveTime);
        this.threadFactory = threadFactory;
        this.handler = handler;
    }

对上面几个参数的理解可以用下图:

image.png

15.3 自定义线程池及四大拒绝策略

采用默认的拒绝策略(AbortPolicy)

import java.util.concurrent.*;

/**
 * 自定义线程池
 */
public class ThreadPoolDemo2 {
    public static void main(String[] args) {
        ExecutorService service = new ThreadPoolExecutor(
                2,//核心线程池的大小
                5,//最大核心线程数
                3, TimeUnit.SECONDS,
                new LinkedBlockingQueue<>(3),//阻塞队列的大小
                Executors.defaultThreadFactory(),
                new ThreadPoolExecutor.AbortPolicy());//默认的拒绝策略
        try {
            for (int i = 1; i <= 9; i++) {
                //使用线程池来创建线程
                service.execute(()->{
                    System.out.println(Thread.currentThread().getName() + " ok");
                });
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            service.shutdown();
        }

    }

}

测试结果

pool-1-thread-2 ok
pool-1-thread-4 ok
pool-1-thread-3 ok
pool-1-thread-1 ok
pool-1-thread-3 ok
pool-1-thread-4 ok
pool-1-thread-2 ok
pool-1-thread-5 ok
java.util.concurrent.RejectedExecutionException: Task ThreadPoolDemo2$$Lambda$1/27095111@1075dc0 rejected from java.util.concurrent.ThreadPoolExecutor@14c265e[Running, pool size = 5, active threads = 0, queued tasks = 0, completed tasks = 8]
	at java.util.concurrent.ThreadPoolExecutor$AbortPolicy.rejectedExecution(ThreadPoolExecutor.java:2047)
	at java.util.concurrent.ThreadPoolExecutor.reject(ThreadPoolExecutor.java:823)
	at java.util.concurrent.ThreadPoolExecutor.execute(ThreadPoolExecutor.java:1369)
	at ThreadPoolDemo2.main(ThreadPoolDemo2.java:18)

可以看出,采用默认的拒绝策略,当开的线程数量达到最大核心线程池的大小maximumPoolSize时,会抛出异常。

但是如果将创建的线程数量改为8,即:

for (int i = 1; i <= 8; i++) {
                //使用线程池来创建线程
                service.execute(()->{
                    System.out.println(Thread.currentThread().getName() + " ok");
                });
            }

结果如下

pool-1-thread-1 ok
pool-1-thread-5 ok
pool-1-thread-3 ok
pool-1-thread-4 ok
pool-1-thread-2 ok
pool-1-thread-3 ok
pool-1-thread-1 ok
pool-1-thread-5 ok

没有报错,所以最大允许的线程数量 = 最大核心线程数(5)+ 阻塞队列的大小(3)

采用由调用者处理的拒绝策略(CallerRunsPolicy)

import java.util.concurrent.*;

/**
 * 自定义线程池
 */
public class ThreadPoolDemo2 {
    public static void main(String[] args) {
        ExecutorService service = new ThreadPoolExecutor(
                2,
                5,
                3, TimeUnit.SECONDS,
                new LinkedBlockingQueue<>(3),//阻塞队列的大小
                Executors.defaultThreadFactory(),
                new ThreadPoolExecutor.CallerRunsPolicy());//由调用者处理的拒绝策略
        try {
            for (int i = 1; i <= 15; i++) {
                //使用线程池来创建线程
                service.execute(()->{
                    System.out.println(Thread.currentThread().getName() + " ok");
                });
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            service.shutdown();
        }

    }

}

测试结果:

main ok
pool-1-thread-3 ok
pool-1-thread-4 ok
pool-1-thread-3 ok
pool-1-thread-2 ok
pool-1-thread-1 ok
pool-1-thread-3 ok
pool-1-thread-4 ok
pool-1-thread-5 ok
main ok
main ok
main ok
pool-1-thread-2 ok
pool-1-thread-2 ok
pool-1-thread-1 ok

可以看出超过5个线程时,一部分线程会由调用的main主线程去处理。

直接丢弃任务(DiscardPolicy)

new ThreadPoolExecutor.DiscardPolicy());//直接丢弃任务不抛出异常的拒绝策略

 for (int i = 1; i <= 15; i++) {//同样测试15个任务
      //使用线程池来创建线程
      service.execute(()->{
           System.out.println(Thread.currentThread().getName() + " ok");
      });
 }

测试结果

pool-1-thread-2 ok
pool-1-thread-5 ok
pool-1-thread-4 ok
pool-1-thread-3 ok
pool-1-thread-1 ok
pool-1-thread-4 ok
pool-1-thread-5 ok
pool-1-thread-2 ok

可以看出,当线程个数超过8时,会丢弃任务,但是不会抛出异常。

尝试和最早的线程竞争(DiscardOldestPolicy)

new ThreadPoolExecutor.DiscardOldestPolicy());//队列满了,尝试和最早的竞争

测试结果

pool-1-thread-1 ok
pool-1-thread-5 ok
pool-1-thread-1 ok
pool-1-thread-4 ok
pool-1-thread-3 ok
pool-1-thread-2 ok
pool-1-thread-1 ok
pool-1-thread-5 ok

15.4 最大线程数到底该怎么设置

CPU密集型

有几核就是几,可以保持CPU的效率最高。

获取CPU的核数的方法:

        System.out.println("获取到的CPU的核数:" + Runtime.getRuntime().availableProcessors());

IO 密集型

判断程序中十分耗IO的线程的个数,比如有15个大型任务,IO十分占用资源,那么就定为它的2倍,30个最大线程数。

线程池总结:

3大方法、7大参数、4种拒绝策略。

16. 四大函数式接口

函数式接口:只有一个方法的接口

@FunctionalInterface
public interface Runnable {
}

好处:简化编程。

16.1 Function 函数型接口

package function;

import java.util.function.Function;

public class FunctionDemo {
    public static void main(String[] args) {
        //函数型接口
        Function function = new Function<String,String>() {
            @Override
            public String apply(String str) {
                return str;
            }
        };
        //使用Lambda表达式的简化版
        Function<String,String> function1= str->{
            return str;
        };
        //使用Lambda表达式的再简化版
        Function<String,String> function2 = str -> str+" jeff";
        System.out.println(function.apply("Hello"));
        System.out.println(function1.apply("Man"));
        System.out.println(function2.apply("Hi"));
    }
}

测试结果:

Hello
Man
Hi jeff

16.2 Predicate 断定型接口

package function;

import java.util.function.Predicate;

/**
 * 断定型接口:有一个输入参数,返回值只能是布尔值
 */
public class PredicateDemo {
    public static void main(String[] args) {
        Predicate<String> predicate = str -> str.isEmpty();
        Predicate<String> predicate1 = new Predicate<String>() {
            @Override
            public boolean test(String s) {
                return s.isEmpty();
            }
        };
        System.out.println(predicate.test("lambda"));
        System.out.println(predicate1.test(""));
    }
}

测试结果:

false
true

16.3 Consumer 消费型接口

package function;

import thread.C;

import java.util.function.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<String> consumer1 = s -> System.out.println(s);
        consumer.accept("abcd");
        consumer1.accept("lambda");
    }
}

测试结果:

abcd
lambda

16.4 Supplier 供给型接口

package function;

import java.util.function.Supplier;

/**
 * Supplier 供给型接口 没有参数,只有返回值
 */
public class SupplierDemo {
    public static void main(String[] args) {
        Supplier supplier = new Supplier() {
            @Override
            public Object get() {
                System.out.println("get()");
                return 1024;
            }
        };
        Supplier supplier1 = () -> "lambda";
        System.out.println(supplier.get());
        System.out.println(supplier1.get());
    }

}

测试结果:

get()
1024
lambda

17. Stream 流式计算

什么是Stream 流式计算?

大数据:存储+计算

集合、MySQL本质就是存储东西的

计算都应该交给流来操作。

示例

package stream;

import java.util.Arrays;
import java.util.List;
import java.util.Locale;
import java.util.stream.Collectors;

/**
 * 现在有5个用户,筛选:
 * 1.ID 必须是偶数
 * 2.年龄必须大于23岁
 * 3.用户名转为大写字母
 * 4.用户名字母倒着排序
 * 5.只输出一个用户
 */
public class StreamDemo {
    public static void main(String[] args) {
        User u1= new User(1,"a",21);
        User u2= new User(2,"b",22);
        User u3= new User(3,"c",23);
        User u4= new User(4,"d",24);
        User u5= new User(6,"e",25);
        //集合用来存储
        List<User> list = Arrays.asList(u1,u2,u3,u4,u5);
        //计算交给Stream流
        //lambda 表达式、链式编程、函数式接口、Stream 流式计算
        list.stream().filter(u->{return u.getId()%2==0;})
                .filter(u->u.getAge()>23)
                .map(u->u.getName().toUpperCase(Locale.ROOT))
                .sorted((uu1,uu2)->uu2.compareTo(uu1))
                .limit(1)
                .forEach(System.out::println);

    }
}

输出结果:

E

18. ForkJoin

分支合并

什么是ForkJoin?

ForkJoin 在JDK 1.7,并行执行任务,提高效率。大数据量的情况使用!

image.png

大任务拆分成小任务

ForkJoin 特点

工作窃取。

ForkJoin 操作

package frokjoin;

import java.util.concurrent.RecursiveTask;

public class ForkJoinDemo extends RecursiveTask<Long> {
    private Long start;//1
    private Long end;//1990900000
    //临界值
    private Long temp = 10000L;

    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 {
            //forkjoin
            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() + task2.join();
        }
    }
}

测试类:

package frokjoin;

import java.util.concurrent.ExecutionException;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.ForkJoinTask;
import java.util.stream.LongStream;

public class Test {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        test1();
        test2();
        test3();
    }

    //普通的方法
    public static void test1() {
        Long sum = 0L;
        long start = System.currentTimeMillis();
        for (Long l = 1L; l <= 10_0000_0000; l++) {
            sum += l;
        }
        long end = System.currentTimeMillis();
        System.out.println("test1 sum=" + sum + ",时间是:" + (end - start));
    }


    //使用forkjoin的方式
    public static void test2() throws ExecutionException, InterruptedException {
        long start = System.currentTimeMillis();
        ForkJoinPool forkJoinPool = new ForkJoinPool();
        ForkJoinTask<Long> task = new ForkJoinDemo(0L, 10_0000_0000L);
        ForkJoinTask<Long> submit = forkJoinPool.submit(task);
        Long sum = submit.get();
        long end = System.currentTimeMillis();
        System.out.println("test2 sum=" + sum + ",时间是:" + (end - start));
    }

    //Stream 并行流的方式
    public static void test3() {
        long start = System.currentTimeMillis();
        //Stream 并行流
        long sum = LongStream.rangeClosed(0L, 10_0000_0000L).parallel().sum();
        long end = System.currentTimeMillis();
        System.out.println("test3 sum=" + sum + ",时间:" + (end - start));

    }
}

测试的结果:

test1 sum=500000000500000000,时间是:16681
test2 sum=499934463999828390,时间是:10139
test3 sum=500000000500000000,时间:2286

Stream并行流的时间 < forkjoin的方式< 普通累加的方式

19. 异步回调

示例

package future;

import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;

/**
 * 异步调用示例
 */
public class FutureDemo {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        //发起一个异步请求
        CompletableFuture<Integer> completableFuture = CompletableFuture.supplyAsync(()->{
            System.out.println(Thread.currentThread().getName() + " supplyAsync=>Integer");
            //int i = 10/0;
            return 1024;
        });
        System.out.println(completableFuture.whenComplete((t,u)->{
            System.out.println("t 表示success的结果:" + t);
            System.out.println("u 表示error的结果:" +  u);
        }).exceptionally(e->{
            //捕获异常
            System.out.println(e.getMessage());
            return 233;//返回异常的结果
        }).get());

    }
}

测试结果:

ForkJoinPool.commonPool-worker-1 supplyAsync=>Integer
t 表示success的结果:1024
u 表示error的结果:null
1024

如果将下面这行注释放开:

int i = 10/0;

则异步任务会抛出异常并被捕获到,结果为:

ForkJoinPool.commonPool-worker-1 supplyAsync=>Integer
t 表示success的结果:null
u 表示error的结果:java.util.concurrent.CompletionException: java.lang.ArithmeticException: / by zero
java.lang.ArithmeticException: / by zero
233

20. JMM

20.1 volatile 的理解

volatile 的特点:

  1. 保证可见性
  2. 不保证原子性
  3. 禁止指令重拍

20.2 什么是JMM

JMM:java memory model,Java 内存模型。

JMM的一些同步的约定:

  1. 线程解锁前,必须把共享变量的值立刻刷回主内存;
  2. 线程加锁前,必须把主内存中的最新值读取到自己的工作内存中
  3. 加锁和解锁是同一把锁

内存交互的8种操作:

image.png

内存交互操作有8种,虚拟机实现必须保证每一个操作都是原子的,不可在分的(对于double和long类型的变量来说,load、store、read和write操作在某些平台上允许例外)

  • lock (锁定):作用于主内存的变量,把一个变量标识为线程独占状态

  • unlock (解锁):作用于主内存的变量,它把一个处于锁定状态的变量释放出来,释放后的变量才可以被其他线程锁定

  • read (读取):作用于主内存变量,它把一个变量的值从主内存传输到线程的工作内存中,以便随后的load动作使用

  • load (载入):作用于工作内存的变量,它把read操作从主存中变量放入工作内存中

  • use (使用):作用于工作内存中的变量,它把工作内存中的变量传输给执行引擎,每当虚拟机遇到一个需要使用到变量的值,就会使用到这个指令

  • assign (赋值):作用于工作内存中的变量,它把一个从执行引擎中接受到的值放入工作内存的变量副本中

  • store (存储):作用于主内存中的变量,它把一个从工作内存中一个变量的值传送到主内存中,以便后续的write使用

  • write  (写入):作用于主内存中的变量,它把store操作从工作内存中得到的变量的值放入主内存的变量中

JMM对这八种指令的使用,制定了如下规则:

  • 不允许read和load、store和write操作之一单独出现。即使用了read必须load,使用了store必须write

  • 不允许线程丢弃他最近的assign操作,即工作变量的数据改变了之后,必须告知主存

  • 不允许一个线程将没有assign的数据从工作内存同步回主内存

  • 一个新的变量必须在主内存中诞生,不允许工作内存直接使用一个未被初始化的变量。就是怼变量实施use、store操作之前,必须经过assign和load操作

  • 一个变量同一时间只有一个线程能对其进行lock。多次lock后,必须执行相同次数的unlock才能解锁

  • 如果对一个变量进行lock操作,会清空所有工作内存中此变量的值,在执行引擎使用这个变量前,必须重新load或assign操作初始化变量的值

  • 如果一个变量没有被lock,就不能对其进行unlock操作。也不能unlock一个被其他线程锁住的变量

  • 对一个变量进行unlock操作之前,必须把此变量同步回主内存

20.3 不使用锁volatile怎么保证原子性

image.png

java.util.concurrent.atomic 包下有几个类,可以实现原子性操作。

/**
 * volatile 的使用示例
 */
public class VolatileDemo {
    private static volatile int num = 0;//使用volatile关键字

    public static void add(){
        num++;
    }

    public static void main(String[] args) {
        //理论上num结果应该为2万
        for (int i = 1; i <= 20; i++) {
            new Thread(()->{
                for (int j = 0; j < 1000; j++) {
                    add();
                }
            }).start();
        }

        while(Thread.activeCount() > 2){//main GC
            Thread.yield();
        }
        System.out.println(Thread.currentThread().getName() + " " + num);

    }
}

测试结果(有时候是对的,为20000,但总有些时候是错误的):

main 19906

理论上应该是20000,可实际上不是,就是因为num ++ 操作不是原子性操作,volatile 不能保证原子性,线程不安全。

在不加lock 和 synchronized 情况下,怎么保证原子性?

package tvolatile;

import java.util.concurrent.atomic.AtomicInteger;

/**
 * volatile 的使用示例
 */
public class VolatileDemo {
    private static volatile AtomicInteger num = new AtomicInteger();

    public static void add(){
        //AtomicInteger + 1 的方法,CAS
        num.getAndIncrement();
    }

    public static void main(String[] args) {
        //理论上num结果应该为2万
        for (int i = 1; i <= 20; i++) {
            new Thread(()->{
                for (int j = 0; j < 1000; j++) {
                    add();
                }
            }).start();
        }

        while(Thread.activeCount() > 2){//main GC
            Thread.yield();
        }
        System.out.println(Thread.currentThread().getName() + " " + num);

    }
}

测试结果:

main 20000

20.4 指令重排

什么是指令重排?

你写的程序,计算机并不是按照你写的那样去执行的。

从源代码到执行会经历几个阶段:

源代码--》编译器优化的重排--》指令并行也可能重排--》内存系统也会重排--》执行

==处理器在进行指令重排时会考虑数据之间的依赖性!==

volatile 可以避免指令重排。

20.5 volatile 在单例模式中的应用及单例模式扩展

单例模式的几种类型:

恶汉式单例

package single;

/**
 * 饿汉式单例  
 * 缺点:可能会浪费空间
 */
public class Hungry {
    //可能会浪费空间
    private byte[] data1 = new byte[1024*1024];
    private byte[] data2 = new byte[1024*1024];
    private byte[] data3 = new byte[1024*1024];
    private byte[] data4 = new byte[1024*1024];

    private Hungry(){

    }
    private final static Hungry HUNGRY = new Hungry();

    public static Hungry getInstance(){
        return HUNGRY;
    }

}

懒汉式单例

package single;

/**
 * 懒汉式单例
 */
public class LazyMan {

    private LazyMan(){
       System.out.println(Thread.currentThread().getName() + " 生成实例");
    }
    private static LazyMan lazyMan;

    //单线程下是单例,但是并发的时候并不一定是单例了
    public static LazyMan getInstance(){
        if(lazyMan == null){
            lazyMan = new LazyMan();
        }
        return lazyMan;
    }

    //多线程下测试单例
    public static void main(String[] args) {
        for (int i = 0; i < 10; i++) {
            new Thread(()->{
                LazyMan.getInstance();
            }).start();
        }
    }
}

测试结果:

Thread-0 生成实例
Thread-3 生成实例
Thread-2 生成实例
Thread-1 生成实例

从结果可以看出,多线程下生成了多次实例,不安全。

DCL懒汉式 (双重检测锁)

package single;

/**
 * 懒汉式单例
 */
public class LazyMan {

    private LazyMan() {
        System.out.println(Thread.currentThread().getName() + " 生成实例");
    }

    private static volatile LazyMan lazyMan;

    //单线程下是单例,但是并发的时候并不一定是单例了
    public static LazyMan getInstance() {
        if (lazyMan == null) {
            synchronized (LazyMan.class) {
                if (lazyMan == null) {
                    lazyMan = new LazyMan();//不是一个原子性操作
                    /**
                     * 1. 分配内存空间
                     * 2. 执行构造方法,初始化对象
                     * 3. 把这个对象指向这个空间
                     * 123
                     * 132 A
                     *     B
                     * 当A按132执行时,此时lazyMan可能没有完成构造,而lazyMan != null,B判断时会直接返回Null
                     * 需要加volatile修饰变量,禁止指令重排
                     */
                }
            }
        }

        return lazyMan;
    }

    //多线程下测试单例
    public static void main(String[] args) {
        for (int i = 0; i < 10; i++) {
            new Thread(() -> {
                LazyMan.getInstance();
            }).start();
        }
    }
}

静态内部类实现

package single;

/**
 * 静态内部类
 */
public class Holder {
    private  Holder(){
    }

    public static Holder getInstance(){
        return InnerClass.HOLDER;
    }

    public static class InnerClass{
        private static final Holder HOLDER = new Holder();
    }
}

优点:

外部类加载时并不需要立即加载内部类,内部类不被加载则不去初始化INSTANCE,故而不占内存。

不仅能确保线程安全,也能保证单例的唯一性,同时也可以延迟加载。

==注意:==

==上面这些方式严格说都不是安全的,还是可以通过反射去破解单例模式,通过枚举的方式可以避免通过反射去破解。==

枚举的方式

package single;

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;

/**
 * 枚举的方式实现单例
 * 枚举本身也是个Class类
 */
public enum EnumSingle {
    INSTANCE;

    public EnumSingle getInstance(){
        return INSTANCE;
    }
}

class Test{
    //通过反射去破坏枚举的单例模式
    public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
        EnumSingle instance1 = EnumSingle.INSTANCE;
        System.out.println(instance1);

        Constructor<EnumSingle> declaredConstructor = EnumSingle.class.getDeclaredConstructor(String.class,int.class);
        declaredConstructor.setAccessible(true);
        EnumSingle instance2 = declaredConstructor.newInstance();
        System.out.println(instance2);

    }
}

测试结果:

INSTANCE
Exception in thread "main" java.lang.IllegalArgumentException: Cannot reflectively create enum objects
	at java.lang.reflect.Constructor.newInstance(Constructor.java:416)
	at single.Test.main(EnumSingle.java:26)

从测试结果可以看出,第一个实例正常返回,可是第二个实例没有生成,抛出了异常IllegalArgumentException

Cannot reflectively create enum objects

扩展:

查看反射中的方法 newInsance() 源码,可以发现,这是JDK中对枚举做的特殊处理,不允许通过反射去创建枚举对象。

public T newInstance(Object ... initargs)
        throws InstantiationException, IllegalAccessException,
               IllegalArgumentException, InvocationTargetException
    {
        if (!override) {
            if (!Reflection.quickCheckMemberAccess(clazz, modifiers)) {
                Class<?> caller = Reflection.getCallerClass();
                checkAccess(caller, clazz, null, modifiers);
            }
        }
        if ((clazz.getModifiers() & Modifier.ENUM) != 0)
            throw new IllegalArgumentException("Cannot reflectively create enum objects");
        ConstructorAccessor ca = constructorAccessor;   // read volatile
        if (ca == null) {
            ca = acquireConstructorAccessor();
        }
        @SuppressWarnings("unchecked")
        T inst = (T) ca.newInstance(initargs);
        return inst;
    }

21. CAS

Compare and Set,比较并更新。比较当前工作内存中的值和主内存中的值(CPU操作),如果这个值是期望的,那么就执行操作,如果不是就一直循环!

package cas;

import java.util.concurrent.atomic.AtomicInteger;

public class CASDemo {
    public static void main(String[] args) {
        AtomicInteger atomicInteger = new AtomicInteger(2022);
        //比较并更新,如果期望值达到了,那么就更新,否则就不更新。CAS 是CPU的并发原语
        System.out.println(atomicInteger.compareAndSet(2022, 2021));
        System.out.println(atomicInteger.get());

        //第一次期望值2022,达到了期望,将值更新成了2021
        //第二次由于期望还是2022,没有达到期望,故不会更新,值还是2021
        System.out.println(atomicInteger.compareAndSet(2022, 2021));
        System.out.println(atomicInteger.get());
    }
}

测试结果:

true
2021
false
2021

缺点:

  1. 循环会耗时;
  2. 一次性只能保证一个共享变量的原子性
  3. ABA 问题

22. 原子引用解决ABA问题

可以引入原子引用解决ABA问题,对应的思想是乐观锁机制

package cas;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicStampedReference;

public class ABADemo {
    public static void main(String[] args) {
        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("a2 result:" + atomicStampedReference.compareAndSet(1, 2,
                    atomicStampedReference.getStamp(), atomicStampedReference.getStamp()+1));
            System.out.println("a2=>" + atomicStampedReference.getStamp());

            System.out.println("a3 result:" + 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(2);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("b2 result:" + atomicStampedReference.compareAndSet(1, 6,
                    stamp, stamp + 1));
            System.out.println("b2=>" + atomicStampedReference.getStamp());
        },"b").start();


    }
}

测试结果:

a1=>1
b1=>1
a2 result:true
a2=>2
a3 result:true
b2 result:false
a3=>3
b2=>3

可以看出,b线程的CAS操作是没有操作成功的,因为版本号变了。

23. 各种锁的理解

23.1 公平锁、非公平锁

公平锁:非常公平,不能插队,必须先来后到

非公平锁:非常不公平,可以插队

    /**
     * Creates an instance of {@code ReentrantLock}.
     * This is equivalent to using {@code ReentrantLock(false)}.
     */
    public ReentrantLock() {
        sync = new NonfairSync();
    }

    /**
     * Creates an instance of {@code ReentrantLock} with the
     * given fairness policy.
     *
     * @param fair {@code true} if this lock should use a fair ordering policy
     */
    public ReentrantLock(boolean fair) {
        sync = fair ? new FairSync() : new NonfairSync();
    }

23.2 可重入锁

可重入锁(递归锁),拿到了外面的锁后,就可以拿到里面的锁。

Synchronized 版

package lock;

/**
 * 可重入锁
 * synchronized 版
 */
public class KeChongRuLockDemo {
    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");
    }

}

测试结果:

Asms
Acall
Bsms
Bcall

ReentrantLock 版

package lock;

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 * 可重入锁
 * ReentrantLock 版
 */
public class KeChongRuLockDemo1 {
    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();
        }
    }

}

测试结果:

Asms
Acall
Bsms
Bcall

23.3 自旋锁

在AtomicInteger中有个方法用到了自旋锁:

  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;
    }

自定义自旋锁:

package lock;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.locks.ReentrantLock;

/**
 * 自旋锁
 */
public class SpinLockDemo {
    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);
    }

}

class TestSpinLock{
    public static void main(String[] args) throws InterruptedException {
//        ReentrantLock reentrantLock = new ReentrantLock();
//        reentrantLock.lock();
//        reentrantLock.unlock();

        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(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                lock.myUnLock();
            }
        },"T2").start();
    }
}

测试结果:

T1==> mylock
T2==> mylock
T1==> myUnlock
T2==> myUnlock

23.4 死锁

死锁示例

package lock;

import java.util.concurrent.TimeUnit;

/**
 * 死锁
 */
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;
    }

    @Override
    public void run() {
        synchronized (lockA){
            System.out.println(Thread.currentThread().getName() + " lock:" + lockA + "=>get" + lockB);
            try {
                TimeUnit.SECONDS.sleep(2);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            synchronized (lockB){
                System.out.println(Thread.currentThread().getName() + " lock:" + lockB + "=>get" + lockA);
            }
        }
    }
}

测试结果:

T1 lock:lockA=>getlockB
T2 lock:lockB=>getlockA

而且可以看到程序没有终止的,说明确实产生了死锁。

死锁怎么排查

  1. 使用 jps -l 定位进程号

    D:\work\jdk_1.8_64\bin>jps -l
    16448 lock.DeadLockDemo
    18544 org.jetbrains.jps.cmdline.Launcher
    19072 sun.tools.jps.Jps
    17240
    
  2. 使用 jstack 进程号 找到死锁问题

    D:\work\jdk_1.8_64\bin>jstack 16448
    2022-03-06 21:00:57
    Full thread dump Java HotSpot(TM) 64-Bit Server VM (25.291-b10 mixed mode):
    
    "DestroyJavaVM" #14 prio=5 os_prio=0 tid=0x000001cd0bfec000 nid=0x1888 waiting on condition [0x0000000000000000]
       java.lang.Thread.State: RUNNABLE
    
    "T2" #13 prio=5 os_prio=0 tid=0x000001cd289bc000 nid=0x2538 waiting for monitor entry [0x00000070610ff000]
       java.lang.Thread.State: BLOCKED (on object monitor)
            at lock.MyThread.run(DeadLockDemo.java:39)
            - waiting to lock <0x000000076be94440> (a java.lang.String)
            - locked <0x000000076be94478> (a java.lang.String)
            at java.lang.Thread.run(Thread.java:748)
    
    "T1" #12 prio=5 os_prio=0 tid=0x000001cd289bb000 nid=0x5b3c waiting for monitor entry [0x0000007060fff000]
       java.lang.Thread.State: BLOCKED (on object monitor)
            at lock.MyThread.run(DeadLockDemo.java:39)
            - waiting to lock <0x000000076be94478> (a java.lang.String)
            - locked <0x000000076be94440> (a java.lang.String)
            at java.lang.Thread.run(Thread.java:748)
    
    "Service Thread" #11 daemon prio=9 os_prio=0 tid=0x000001cd2896e000 nid=0x5be0 runnable [0x0000000000000000]
       java.lang.Thread.State: RUNNABLE
    
    "C1 CompilerThread3" #10 daemon prio=9 os_prio=2 tid=0x000001cd288c9800 nid=0x5350 waiting on condition [0x0000000000000000]
       java.lang.Thread.State: RUNNABLE
    
    "C2 CompilerThread2" #9 daemon prio=9 os_prio=2 tid=0x000001cd288bd800 nid=0x5b08 waiting on condition [0x0000000000000000]
       java.lang.Thread.State: RUNNABLE
    
    "C2 CompilerThread1" #8 daemon prio=9 os_prio=2 tid=0x000001cd288ba800 nid=0x4c48 waiting on condition [0x0000000000000000]
       java.lang.Thread.State: RUNNABLE
    
    "C2 CompilerThread0" #7 daemon prio=9 os_prio=2 tid=0x000001cd288b8800 nid=0x4ae8 waiting on condition [0x0000000000000000]
       java.lang.Thread.State: RUNNABLE
    
    "Monitor Ctrl-Break" #6 daemon prio=5 os_prio=0 tid=0x000001cd288a8800 nid=0x4c14 runnable [0x00000070608fe000]
       java.lang.Thread.State: RUNNABLE
            at java.net.SocketInputStream.socketRead0(Native Method)
            at java.net.SocketInputStream.socketRead(SocketInputStream.java:116)
            at java.net.SocketInputStream.read(SocketInputStream.java:171)
            at java.net.SocketInputStream.read(SocketInputStream.java:141)
            at sun.nio.cs.StreamDecoder.readBytes(StreamDecoder.java:284)
            at sun.nio.cs.StreamDecoder.implRead(StreamDecoder.java:326)
            at sun.nio.cs.StreamDecoder.read(StreamDecoder.java:178)
            - locked <0x000000076bd8f6b8> (a java.io.InputStreamReader)
            at java.io.InputStreamReader.read(InputStreamReader.java:184)
            at java.io.BufferedReader.fill(BufferedReader.java:161)
            at java.io.BufferedReader.readLine(BufferedReader.java:324)
            - locked <0x000000076bd8f6b8> (a java.io.InputStreamReader)
            at java.io.BufferedReader.readLine(BufferedReader.java:389)
            at com.intellij.rt.execution.application.AppMainV2$1.run(AppMainV2.java:49)
    
    "Attach Listener" #5 daemon prio=5 os_prio=2 tid=0x000001cd26acd800 nid=0x5a68 waiting on condition [0x0000000000000000]
       java.lang.Thread.State: RUNNABLE
    
    "Signal Dispatcher" #4 daemon prio=9 os_prio=2 tid=0x000001cd26acc800 nid=0xbe4 runnable [0x0000000000000000]
       java.lang.Thread.State: RUNNABLE
    
    "Finalizer" #3 daemon prio=8 os_prio=1 tid=0x000001cd26a9b000 nid=0x2fe0 in Object.wait() [0x00000070605ff000]
       java.lang.Thread.State: WAITING (on object monitor)
            at java.lang.Object.wait(Native Method)
            - waiting on <0x000000076bc08ee0> (a java.lang.ref.ReferenceQueue$Lock)
            at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:144)
            - locked <0x000000076bc08ee0> (a java.lang.ref.ReferenceQueue$Lock)
            at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:165)
            at java.lang.ref.Finalizer$FinalizerThread.run(Finalizer.java:216)
    
    "Reference Handler" #2 daemon prio=10 os_prio=2 tid=0x000001cd26a8e000 nid=0x47d0 in Object.wait() [0x00000070604ff000]
       java.lang.Thread.State: WAITING (on object monitor)
            at java.lang.Object.wait(Native Method)
            - waiting on <0x000000076bc06c00> (a java.lang.ref.Reference$Lock)
            at java.lang.Object.wait(Object.java:502)
            at java.lang.ref.Reference.tryHandlePending(Reference.java:191)
            - locked <0x000000076bc06c00> (a java.lang.ref.Reference$Lock)
            at java.lang.ref.Reference$ReferenceHandler.run(Reference.java:153)
    
    "VM Thread" os_prio=2 tid=0x000001cd26a63800 nid=0x8c0 runnable
    
    "GC task thread#0 (ParallelGC)" os_prio=0 tid=0x000001cd0c003000 nid=0x57a0 runnable
    
    "GC task thread#1 (ParallelGC)" os_prio=0 tid=0x000001cd0c004800 nid=0x3090 runnable
    
    "GC task thread#2 (ParallelGC)" os_prio=0 tid=0x000001cd0c006000 nid=0x118 runnable
    
    "GC task thread#3 (ParallelGC)" os_prio=0 tid=0x000001cd0c007000 nid=0x5614 runnable
    
    "GC task thread#4 (ParallelGC)" os_prio=0 tid=0x000001cd0c009800 nid=0x4510 runnable
    
    "GC task thread#5 (ParallelGC)" os_prio=0 tid=0x000001cd0c00a800 nid=0xdd0 runnable
    
    "GC task thread#6 (ParallelGC)" os_prio=0 tid=0x000001cd0c00d800 nid=0x5728 runnable
    
    "GC task thread#7 (ParallelGC)" os_prio=0 tid=0x000001cd0c00f000 nid=0x589c runnable
    
    "VM Periodic Task Thread" os_prio=2 tid=0x000001cd289b8000 nid=0x4ddc waiting on condition
    
    JNI global references: 12
    
    
    Found one Java-level deadlock:
    =============================
    "T2":
      waiting to lock monitor 0x000001cd26a97d28 (object 0x000000076be94440, a java.lang.String),
      which is held by "T1"
    "T1":
      waiting to lock monitor 0x000001cd26a9a3a8 (object 0x000000076be94478, a java.lang.String),
      which is held by "T2"
    
    Java stack information for the threads listed above:
    ===================================================
    "T2":
            at lock.MyThread.run(DeadLockDemo.java:39)
            - waiting to lock <0x000000076be94440> (a java.lang.String)
            - locked <0x000000076be94478> (a java.lang.String)
            at java.lang.Thread.run(Thread.java:748)
    "T1":
            at lock.MyThread.run(DeadLockDemo.java:39)
            - waiting to lock <0x000000076be94478> (a java.lang.String)
            - locked <0x000000076be94440> (a java.lang.String)
            at java.lang.Thread.run(Thread.java:748)
    
    Found 1 deadlock.
    
    

    可以看到有Found one Java-level deadlock信息,进而可以定位到死锁的线程。