基于《狂神说Java》JUC并发编程--学习笔记

前言:


本笔记仅做学习与复习使用,不存在刻意抄袭。

------------------------------------------------------------------------------------------------------------

给各位学友强烈推荐《遇见狂神说》他的整套Java学习路线使我获益匪浅!!!

点击跳转至遇见狂神说哔哩哔哩首页

如果你也是狂神的小迷弟,可以加我好友一起探讨学习。
 

JUC简述

JUC是什么?

JUC就是java.util.concurrent包,俗称java并发包,是Java开发工程师学习并发的时候需要掌握的内容。

为什么要学习JUC?

我认为起码有以下几点:

  1. 一个初级java程序员进阶学习的必经之路;
  2. 让我们深刻了解并发的思想,为以后的微服务,分布式打好基础;
  3. 能在脑海中构建一个计算机底层处理问题的大概模型,这对研究算法是很有帮助的;
  4. 最后一点,也是最现实的一点,这是面试高频考点,学习JUC并发编程,除了能丰富知识,还能提高今后面对面试的底气。

接下来就是《狂神说Java》JUC并发编程的课程笔记和我自己的总结了,或许,没有看视频的学友看着会觉得有些混乱或者逻辑错误。这里还是强调一点,各位最好是配合视频学习,笔记只当作初学或者今后复习就好。
 

一、学习方式

源码+官方文档(面试高频)

这里简述一下juc在jdk帮助文档的位置(jdk帮助文档请自行百度下载):

我们打开jdk帮助文档

基于《狂神说Java》JUC并发编程--学习笔记_第1张图片

点击左侧目录:

基于《狂神说Java》JUC并发编程--学习笔记_第2张图片

基于《狂神说Java》JUC并发编程--学习笔记_第3张图片

 

二、回忆多线程的学习内容

2.1、实现多线程的三种方式

  • 继承Thread类(其实Thread类根本上也实现了Runnable接口)

我们按住ctrl加鼠标左键,点击Thread :

基于《狂神说Java》JUC并发编程--学习笔记_第4张图片

跳转到:

基于《狂神说Java》JUC并发编程--学习笔记_第5张图片

 可以看到其实现了Runnable接口。

这里简单写个小例子回忆一下继承Thread类如何使用。

package com.example.demo1.demo02;
/**
 * @author liar
 */
public class TestThread {
    public static void main(String[] args) {
        Eat eat = new Eat();
        Out out = new Out();
        eat.start();
        out.start();
    }
}
class Eat extends Thread{
    @Override
    public void run() {
        for (int i = 0; i < 500; i++) {
            System.out.println( "我在吃饭呢");
        }
    }
}
class Out extends Thread{
    @Override
    public void run() {
        for (int i = 0; i < 500; i++) {
            System.out.println("我在拉屎呢");
        }
    }
}

其核心是重写run方法;其运行结果是:

基于《狂神说Java》JUC并发编程--学习笔记_第6张图片

  • 实现Runnable接口
package com.example.demo1.demo02;

/**
 * @author liar
 */
public class TestRunnable {
    public static void main(String[] args) {
        Who who = new Who();
        FBI fbi = new FBI();
        Thread who1 = new Thread(who,"who");
        Thread fbi1 = new Thread(fbi,"FBI");
        who1.start();
        fbi1.start();
    }
}
class Who implements Runnable{
    @Override
    public void run() {
        for (int i = 0; i < 1000; i++) {
            System.out.println("Who are you ?");
        }
    }
}
class FBI implements Runnable{
    @Override
    public void run() {
        for (int i = 0; i < 1000; i++) {
            System.out.println("Open the door!!!FBI!!!");
        }
    }
}

基于《狂神说Java》JUC并发编程--学习笔记_第7张图片

 运行结果也如预期。

  • 实现Callable接口
package com.example.demo1.demo02;

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

public class TestCallable {
    public static void main(String[] args) {
        Hit hit = new Hit();
        Connection connection = new Connection();
        FutureTask futureTask1 = new FutureTask(hit);
        FutureTask futureTask2 = new FutureTask(connection);
        Thread t1 = new Thread(futureTask1);
        Thread t2 = new Thread(futureTask2);
        t1.start();
        t2.start();
        try {
            //get()方法的返回值即为FutureTask构造器参数Callable实现类重写的call()的返回值
            Object o1 = futureTask1.get();
            Object o2 = futureTask2.get();
            System.out.println(o1);
            System.out.println(o2);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
    }
}
class Hit implements Callable{
    @Override
    public Object call() throws Exception {
        for (int i = 0; i < 500; i++) {
            System.out.println("怎么样!!你打我啊!!!");
        }
        return "hit";
    }
}
class Connection implements Callable{
    @Override
    public Object call() throws Exception {
        for (int i = 0; i < 500; i++) {
            System.out.println("小伙子,出来混,是要讲人脉的!!!");
        }
        return "connection";
    }
}

其运行结果:

基于《狂神说Java》JUC并发编程--学习笔记_第8张图片

 其与前两者的区别是,可以通过FutureTask获取返回值的。


 2.2、总结

普通的线程代码:继承Thread类

Runnable接口:没有返回值,效率相比于Callable接口较低

2.3、线程和进程

关于线程和进程,如果不能用一句话说出来,说明你掌握的还不够扎实!

进程:

  • 一个正在执行的程序
  • 例如:QQ.exe;是运行程序的集合
  • 一个进程可以包含多个线程且至少包含一个线程

Java真的能开启线程吗?

  • 不能开启线程
  • 分析Thread类的原码了解,最后该类还是调用了本地方法(native)
  • Java的底层是c++

2.4、并发和并行

并发:(多线程操作同一资源)

  • CPU一核,模拟出来多线程;天下武功唯快不破,快速交替

并行:(多个人同时走路)

  • CPU多核,多个线程同时执行

并发编程的本质:充分利用CPU的资源

2.5、线程有几个状态

public enum State {
  NEW,//新生

  RUNNABLE,//运行

  BLOCKED,//阻塞

  WAITING,//等待,一直等待

  TIMED_WAITING,//超时等待,过期不候

  TERMINATED;//终止
}

2.6、wait和sleep的区别

来自不同的类:

  • wait==>Object
  • sleep==>Thread

关于锁的释放

  • wait:会释放锁
  • sleep:“抱着锁睡觉,不会释放锁”

使用的范围是不同的

  • wait:必须在同步代码块中使用
  • sleep:任何地方

是否需要捕获异常

  • wait:不需要捕获异常(什么叫中断异常?)
  • sleep:需要捕获异常(存在超时等待的情况)
     

三、Lock锁(重点)

3.1、回忆"synchronized"

作用:

保证线程安全,例如当多个线程访问统一资源时,会发生数据紊乱问题;

怎么理解:

队列+锁

实例:没有synchronized修饰的方法

package com.example.demo1.demo02;
/**
 * @author liar
 */
public class UnsafeTicket {
    public static void main(String[] args) {
        BuyTicket ticket = new BuyTicket();
        new Thread(ticket,"倒霉的我").start();
        new Thread(ticket,"幸运的你们").start();
        new Thread(ticket,"可恶的黄牛党").start();
    }
}
class BuyTicket implements Runnable{
    private int tickets = 10;
    private boolean flag = true;
    @Override
    public void run() {
        while (flag){
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            buy();
        }
    }
    //在方法名前加上"synchronized",那么该方法就变成了同步方法了
    //!!!锁的是"this"!!!
    //'synchronized'默认锁的是this
    private synchronized void buy(){
        if (tickets<=0){
            flag=false;
            //这个return放在这里,如果符合if条件,那么返回出去,不执行下面的sout语句
            return;
        }
        System.out.println(Thread.currentThread().getName()+"买到了第"+tickets--+"张票");
    }
}

我们看一下运行效果:

基于《狂神说Java》JUC并发编程--学习笔记_第9张图片

 实例:有synchronized修饰的方法

package com.example.demo1.demo02;
/**
 * @author liar
 */
public class UnsafeTicket {
    public static void main(String[] args) {
        BuyTicket ticket = new BuyTicket();
        new Thread(ticket,"倒霉的我").start();
        new Thread(ticket,"幸运的你们").start();
        new Thread(ticket,"可恶的黄牛党").start();
    }
}
class BuyTicket implements Runnable{
    private int tickets = 10;
    private boolean flag = true;
    @Override
    public void run() {
        while (flag){
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            buy();
        }
    }
    //在方法名前加上"synchronized",那么该方法就变成了同步方法了
    //!!!锁的是"this"!!!
    //'synchronized'默认锁的是this
    private synchronized void buy(){
        if (tickets<=0){
            flag=false;
            //这个return放在这里,如果符合if条件,那么返回出去,不执行下面的sout语句
            return;
        }
        System.out.println(Thread.currentThread().getName()+"买到了第"+tickets--+"张票");
    }
}

基于《狂神说Java》JUC并发编程--学习笔记_第10张图片

 这下,就是线程安全的了。

3.2、Lock接口(Lock锁)

基于《狂神说Java》JUC并发编程--学习笔记_第11张图片

 

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

//基本的卖票例子
/*
    真正的多线程开发,公司中的开发,降低耦合性
    线程就是一个单独的资源类,没有任何附属操作
    1、属性、方法
 */
public class SaleTicketDemo2 {
    public static void main(String[] args) {
        //并发:多线程操作同一个类,把资源类丢入线程
        Ticket2 ticket = new Ticket2();
        //@FunctionalInterface 函数式接口,jdk1.8 lambda表达式 (参数)->{代码}
        new Thread( () ->{
            for (int i = 0; i < 40; i++) {
                ticket.sale();
            }
        },"A").start();
        new Thread( () ->{
            for (int i = 0; i < 40; i++) {
                ticket.sale();
            }
        },"B").start();
        new Thread( () ->{
            for (int i = 0; i < 40; i++) {
                ticket.sale();
            }
        },"C").start();

    }
}

//Lock三部曲
//1.new ReentrantLock();
//2. lock.lock();    加锁
//3.finally -->  lock.unlock(); 解锁
class Ticket2{
    //属性方法
    private int number = 50;
    Lock lock = new ReentrantLock();

    public  void sale(){
        lock.lock();    //加锁

        try{
            //业务代码
            if(number > 0){
                System.out.println(Thread.currentThread().getName() + "卖出了" + (number--) + "票,剩余" + number);
            }
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            //解锁
            lock.unlock();
        }

    }
}

3.3、Synchronized和Lock的区别


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

3.5、思考

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

四、生产者和消费者问题

面试:单例模式、排序算法、生产者和消费者问题、死锁

4.1、synchronized版

package com.my.pc;
/*
* 线程之间的通信问题:生产者和消费者问题(等待唤醒,通知唤醒)
*
* 线程交替执行:
*   生产者和消费者操作同一个变量
*       生产者:+1
*       消费者:-1
* */
public class Test {
    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();
            }
        },"生产者").start();

        new Thread(()->{
            try {
                for (int i = 0; i < 10; i++) {
                    data.decrement();
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        },"消费者").start();
    }
}

//资源类
/*
* 编写资源类的思路:
*   判断等待、业务、通知
* */
class Data{
    private int number = 0;

    public synchronized void increment() throws InterruptedException {
        if (number!=0){
            //生产者等待
            this.wait();
        }
        number++;
        System.out.println(Thread.currentThread().getName()+"=>"+number);
        //唤醒消费者进程进行“消费”
        this.notifyAll();
    }

    public synchronized void decrement() throws InterruptedException {
        if (number==0){
            //消费者等待
            this.wait();
        }
        number--;
        System.out.println(Thread.currentThread().getName()+"=>"+number);
        //唤醒生产者进程进行“生产”
        this.notifyAll();
    }

}

这样会出现问题!!–虚假唤醒

场景:当出现多个生产者和消费者时,会出现数据紊乱?

原因:就是这个if语句只判断一次:当消费者线程阻塞,来唤醒生产者线程时,多个生产者线程都会被唤醒,但是只有第二个生产者线程执行了if判断,然后第二个生产者线程阻塞(基于if语句的机理),因为第一个生产者线程能够正常执行,而剩余的其他生产者线程,则会执行if语句后面的方法(“意思就是后面的生产者线程在if判断前面生产者线程时,利用了if语句的bug捡了漏”)

(1)if判断流水线状态为空时,线程被阻塞,这时if判断就完成了,线程被唤醒后直接执行线程剩余操作

(2)while判断流水线状态为空时,线程被阻塞,这时的while循环没有完成,线程被唤醒后会先进行while判断

解决方法:

把if语句改为while语句

package com.my.pc;
/*
* 线程之间的通信问题:生产者和消费者问题(等待唤醒,通知唤醒)
*
* 线程交替执行:
*   生产者和消费者操作同一个变量
*       生产者:+1
*       消费者:-1
* */
public class Test {
    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();
            }
        },"生产者").start();

        new Thread(()->{
            try {
                for (int i = 0; i < 10; i++) {
                    data.decrement();
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        },"消费者").start();

        new Thread(()->{
            try {
                for (int i = 0; i < 10; i++) {
                    data.decrement();
                }
            } 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{
    private int number = 0;

    public synchronized void increment() throws InterruptedException {
/*        if (number!=0){
            //生产者等待
            this.wait();
        }*/
        while(number!=0){
            //生产者等待
            this.wait();
        }
        number++;
        System.out.println(Thread.currentThread().getName()+"=>"+number);
        //唤醒消费者进程进行“消费”
        this.notifyAll();
    }

    public synchronized void decrement() throws InterruptedException {
        /*if (number==0){
            //消费者等待
            this.wait();
        }*/
        while (number==0){
            //消费者等待
            this.wait();
        }
        number--;
        System.out.println(Thread.currentThread().getName()+"=>"+number);
        //唤醒生产者进程进行“生产”
        this.notifyAll();
    }

}

4.2、JUC版

基于《狂神说Java》JUC并发编程--学习笔记_第12张图片

 基于《狂神说Java》JUC并发编程--学习笔记_第13张图片

基于《狂神说Java》JUC并发编程--学习笔记_第14张图片

package com.my.pc;

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

/*
 * 线程之间的通信问题:生产者和消费者问题(等待唤醒,通知唤醒)
 *
 * 线程交替执行:
 *   生产者和消费者操作同一个变量
 *       生产者:+1
 *       消费者:-1
 * */
public class Test01 {
    public static void main(String[] args) {
        Data01 data = new Data01();

        new Thread(()->{
            try {
                for (int i = 0; i < 10; i++) {
                    data.increment();
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        },"生产者").start();

        new Thread(()->{
            try {
                for (int i = 0; i < 10; i++) {
                    data.decrement();
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        },"消费者").start();

        new Thread(()->{
            try {
                for (int i = 0; i < 10; i++) {
                    data.decrement();
                }
            } 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 Data01{
    private int number = 0;

    Lock lock = new ReentrantLock();

    Condition condition = lock.newCondition();

    public synchronized 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();
        }
    }

    public synchronized void decrement() 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();
        }
    }
}

4.3、思考

Condition绝不仅仅只是覆盖原来的wait和notify,它肯定还有别的优势

–Condition实现精准通知唤醒

package com.my.pc;
/*
* 该例来了解Condition的优势和好处
* */

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

//让线程按照顺序执行,先执行线程A,再执行线程B,最后执行线程C,之后再从头执行线程A
public class Test02 {
    public static void main(String[] args) {
        Data02 data02 = new Data02();
        new Thread(()->{
            for (int i = 0; i < 10; i++) {
                data02.printA();
            }
        },"A").start();
        new Thread(()->{
            for (int i = 0; i < 10; i++) {
                data02.printB();
            }
        },"B").start();
        new Thread(()->{
            for (int i = 0; i < 10; i++) {
                data02.printC();
            }
        },"C").start();
    }
}

class Data02{
    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();
            }
            number=2;
            System.out.println(Thread.currentThread().getName()+"=>AAAAAAAA");

            //唤醒进程,唤醒指定的进程
            condition2.signal();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }

    }

    public void printB(){
        lock.lock();

        try {
            //业务代码
            while (number!=2){
                condition2.await();
            }
            number=3;
            System.out.println(Thread.currentThread().getName()+"=>BBBBBBBB");

            condition3.signal();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

    public void printC(){
        lock.lock();

        try {
            //业务代码
            while (number!=3){
                condition3.await();
            }
            number=1;
            System.out.println(Thread.currentThread().getName()+"=>CCCCCCCC");

            condition1.signal();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
}

之前锁在对象,之后锁在lock

五、8锁现象彻底理解锁

5.1、8锁现象

其实就是关于锁的八个问题;通过8锁现象,来深入理解锁。

标准情况下,是先打印“发短信”还是“打电话”–(发短息 打电话)

普通同步方法(被synchronized关键字)“锁”的是方法调用者–phone对象(变量名)
同一个资源类中所有方法共用一把“锁”
所有线程被顺序创建,也就顺序打印
2、sendMessage延迟4秒,是先打印“发短信”还是“打电话”–(发短息 打电话)

给第一个sendMessage方法延迟四秒
那么会出现一种状况:一秒后,在A线程还在等待“休眠”结束时,B线程开启,之后线程A和B开始竞争资源,但是因为A线程还在休眠,所以B线程直接获取到资源,先进行打印

package com.my.lock8;

import java.util.concurrent.TimeUnit;

/*
* 8锁现象:其实就是8个关于锁的问题
*   1、标准情况下,是先打印“发短信”还是“打电话”--(发短息  打电话)
*   2、sendMessage延迟4秒,是先打印“发短信”还是“打电话”--(发短息  打电话)
*   上述出现两种情况的原因:synchronized;
* */
public class Test01 {
    public static void main(String[] args) {
        Phone phone = new Phone();

        new Thread(()->{
            phone.sendMessage();
        },"A").start();

        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        new Thread(()->{
            phone.call();
        },"B").start();
    }
}

class Phone{
    //synchronized:锁的对象是方法调用者--phone对象
    //两个方法共用同一把锁,谁先拿到谁执行
    public synchronized void sendMessage(){
        try {
            TimeUnit.SECONDS.sleep(4);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("发短信");
    }

    public synchronized void call(){
        System.out.println("打电话");
    }
}

3、较之前资源类中新增一个普通方法hello,让线程B去执行,那么输出结果是先打印“Hello”还是“发短信”–(Hello 发短信)

hello是一个普通方法,给这个方法没有加锁
线程A要执行的方法中需要休眠4秒
但是hello方法没有加锁,更不可能和其他方法共用一把锁
因此,在休眠1秒后,B线程直接执行,之后等休眠4秒后A线程才执行
4、新建俩个资源类对象,第一个对象调用发短信方法,第二个调用打电话方法,那么输出结果是先打印“发短信”还是“打电话”–(打电话 发短信)

普通同步方法(被synchronized)它锁的对象是“方法调用者”–phone对象(变量名)
所有一个“方法调用者”就是一把“锁”
这里相当于有两把“锁”
那么两个AB线程可以随机执行,但是因为A线程的方法要休眠4秒,所有B线程先执行完毕

package com.my.lock8;

import java.util.concurrent.TimeUnit;

/*
 * 8锁现象:其实就是8个关于锁的问题
 *   3、较之前资源类中新增一个普通方法hello,让线程B去执行,那么输出结果是先打印“Hello”还是“发短信”--(Hello  发短信)
 *   4、新建俩个资源类对象,第一个对象调用发短信方法,第二个调用打电话方法,那么输出结果是先打印“发短信”还是“打电话”--(打电话  发短信)
 * */
public class Test02 {
    public static void main(String[] args) {
        //实例化两个对象
        synchronized:锁的对象是方法调用者--phone对象,因此两个对象就是两把锁!!
        Phone02 phone1 = new Phone02();
        Phone02 phone2 = new Phone02();

        new Thread(()->{
            phone1.sendMessage();
        },"A").start();

        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        new Thread(()->{
            phone2.call();
        },"B").start();
    }
}

class Phone02{
    //synchronized:锁的对象是方法调用者--phone对象
    //两个方法共用同一把锁,谁先拿到谁执行
    public synchronized void sendMessage(){
        try {
            TimeUnit.SECONDS.sleep(4);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("发短信");
    }

    public synchronized void call(){
        System.out.println("打电话");
    }

    //普通方法:不是同步方法,不受锁的限制
    public void hello(){
        System.out.println("Hello");
    }
}

5、将资源类中的两个方法修改为静态方法,创建一个资源类对象,调用两个方法,判断先打印“发短信”还是“打电话”–(发短信 打电话)

静态同步方法(被static修饰的普通同步方法)
资源类中的所有静态同步方法“锁”的是该资源类(Phone)的类模板(Class模板)
因此,还是按照创建线程的的顺序执行(A线程执行完后,释放锁;B线程拿到锁执行)
6、创建两个资源类对象,两个静态同步方法,调用两个方法,判断先打印“发短信”还是“打电话”–(发短信 打电话)

不要被误导:因为静态同步方法锁的是Class类模板,而不是普通同步方法的方法调用者phone对象(变量名)
所有还是按照线程的创建顺序执行

package com.my.lock8;

import java.util.concurrent.TimeUnit;

/*
 * 8锁现象:其实就是8个关于锁的问题
 *   5、将资源类中的两个方法修改为静态方法,创建一个资源类对象,调用两个方法,判断先打印“发短信”还是“打电话”--(发短信  打电话)
 *   6、创建两个资源类对象,两个静态同步方法,调用两个方法,判断先打印“发短信”还是“打电话”--(打电话  发短信)
 * */
public class Test03 {
    public static void main(String[] args) {
        //两个资源类对象共用一个Class模板,而且synchronized锁的是Class模板
        Phone03 phone1 = new Phone03();
        Phone03 phone2 = new Phone03();

        new Thread(()->{
            phone1.sendMessage();
        },"A").start();

        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        new Thread(()->{
            phone2.call();
        },"B").start();
    }
}

class Phone03{
    //synchronized:锁的对象是Class模板
    //static 静态方法
    //类一旦被加载就有了,Class模板,一个类有且仅有一个唯一的Class模板
    public static synchronized void sendMessage(){
        try {
            TimeUnit.SECONDS.sleep(4);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("发短信");
    }

    public static synchronized void call(){
        System.out.println("打电话");
    }
}

7、将两个方法一个设置为静态方法,一个设置为普通同步方法,创建一个资源类对象,执行两个方法,先打印“发短信”还是“打电话”(打电话 发短信)

静态同步方法锁的是Class类模板,普通同步方法锁的是方法调用者;因此在这里也相当于两把“锁”,线程之间互不干扰
线程A的休眠时间长,因此先执行了B线程
8、创建两个资源类对象,调用下面的两个方法,先打印“发短信”还是“打电话”(打电话 发短信)

两把“锁”
A线程与B线程执行之间互不打扰

package com.my.lock8;

import java.util.concurrent.TimeUnit;

/*
 * 8锁现象:其实就是8个关于锁的问题
 *   7、将两个方法一个设置为静态方法,一个设置为普通同步方法,创建一个资源类对象,执行两个方法,先打印“发短信”还是“打电话”(打电话  发短信)
 *   8、创建两个资源类对象,调用下面的两个方法,先打印“发短信”还是“打电话”(打电话  发短信)
 * */
public class Test04 {
    public static void main(String[] args) {
        Phone04 phone1 = new Phone04();
        Phone04 phone2 = new Phone04();

        new Thread(()->{
            phone1.sendMessage();
        },"A").start();

        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        new Thread(()->{
            phone2.call();
        },"B").start();
    }
}

class Phone04{
    //静态的同步方法:锁的是Class模板
    public static synchronized void sendMessage(){
        try {
            TimeUnit.SECONDS.sleep(4);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("发短信");
    }
    //普通的同步方法:锁的是方法调用者
    public synchronized void call(){
        System.out.println("打电话");
    }
}

5.2、总结

  • 明白synchronized修饰的普通同步方法和加上static关键字的静态同步方法的区别
  • 普通方法和同步方法的区别

六、集合类不安全

6.1、List结合不安全

package com.my.list;

import java.util.*;
import java.util.concurrent.CopyOnWriteArrayList;

//集合是不安全的

//java.util.ConcurrentModificationException:并发修改异常
public class ListTest {
    public static void main(String[] args) {
        /*
        * 并发线程下,ArrayList集合不安全,怎么解决?
        *   1、使用List接口的古老实现类:Vector--List list = new Vector<>();
        *   2、使用Collections工具类:List list = Collections.synchronizedList(new ArrayList<>());
        *   3、CopyOnWrite写入时复制,计算机程序设计领域的一种优化策略:List list = new CopyOnWriteArrayList<>();
        * */
        //就是当多个线程写入数据时,会造成写入的数据“覆盖问题”
//        List list = new CopyOnWriteArrayList<>();
        List list = new ArrayList<>();

        for (int i = 0; i < 10; i++) {
            new Thread(()->{
                list.add(UUID.randomUUID().toString().substring(0,5));
                System.out.println(list);
            },String.valueOf(i)).start();
        }

    }
}

6.2、Set集合不安全

package com.my.list;

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) {
        /*
        * 和ArrayList同样,线程不安全,解决方法?
        *   1、Set set = Collections.synchronizedSet(new HashSet<>());
        *   2、Set set = new CopyOnWriteArraySet<>();
        * */

//        Set set = new HashSet<>();
        Set set = new CopyOnWriteArraySet<>();

        for (int i = 0; i < 50; i++) {
            new Thread(()->{
                set.add(UUID.randomUUID().toString().substring(0,5));
                System.out.println(set);
            },String.valueOf(i)).start();
        }

    }
}

6.3、Map集合不安全

package com.my.list;

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

public class MapTest {
    public static void main(String[] args) {
        /*
        * 工作中是这么用HashMap?--不是这么用的,工作中不要HashMap
        * 它默认等价于什么?--new HashMap<>(16,0.75);
        * */

        //搞清楚什么是加载因子?什么是初始化容量?

        /*
        * map线程不安全怎么解决?
        *   1、Map map = Collections.synchronizedMap(new HashMap<>());
        *   2、Map map = new ConcurrentHashMap<>();
        *   3、研究ConcurrentHashMap原理
        * */

//        Map map = new HashMap<>();
        Map map = new ConcurrentHashMap<>();

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

    }
}

七、走进Callable

基于《狂神说Java》JUC并发编程--学习笔记_第15张图片

 

Callable:

  • 函数式接口(可以使用lombda表达式)
  • 有返回值
  • 可以抛出异常
  • 方法不同,这里的是call方法

在这里插入图片描述

 此图引用爱睡觉哦。

package com.my.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();

        //怎么使用Callable?
        /*
        * 原来Runnable接口的实现方式:new Thread(new MyThread()).start();
        *
        * */
        FutureTask futureTask = new FutureTask(myThread);//适配类,作为中间“驿站”,为了能够使用Callable接口的实现类
        new Thread(futureTask,"A").start();
        new Thread(futureTask,"B").start();//结果会被缓存!--call方法只执行一次!

        //获取Callable方法中的返回值
        Object str = futureTask.get();//这个get方法可能会产生阻塞,一般放在最后!或者用过异步通信来处理
        System.out.println(str);
    }
}

class MyThread implements Callable {

    @Override
    public String call() throws Exception {
        System.out.println("执行了call方法!");
        return "哈哈哈哈";
    }
}

八、常用的辅助类

8.1、CountDownLatch(“减法计数器”)

常用方法:

  • countDown()
  • await()
package com.my.assist;

import java.util.concurrent.CountDownLatch;

//计算器
public class CountDownLatchTest {
    public static void main(String[] args) throws InterruptedException {

        CountDownLatch countDownLatch = new CountDownLatch(6);

        for (int i = 0; i <= 6; i++) {
            new Thread(()->{
                System.out.println(Thread.currentThread().getName()+"go  out!");
                countDownLatch.countDown();//就是“-1”操作
            },String.valueOf(i)).start();
        }

        countDownLatch.await();//阻塞,判断countDownLatch是否归零,归零后再往下执行
        System.out.println("Closed door!");

    }
}

8.2、CyclicBarrier(“加法计数器”)

package com.my.assist;

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

//加法计数器
public class CyclicBarrierTest {
    public static void main(String[] args) {

        CyclicBarrier cyclicBarrier = new CyclicBarrier(7,()->{
            System.out.println("召唤神龙成功!");
        });

        for (int i = 1; i <= 7; i++) {
            final int temp = i;
            new Thread(()->{
                System.out.println(Thread.currentThread().getName()+"收集了"+temp+"颗,龙珠!");

                try {
                    cyclicBarrier.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } catch (BrokenBarrierException e) {
                    e.printStackTrace();
                }
            }).start();
        }

    }
}

8.3、Semaphore(“信号量”)

常用方法:

  • semaphore.acquire():获得
  • semaphore.release():释放

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

package com.my.assist;

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

//信号量
public class SemaphoreTest {
    public static void main(String[] args) {
        //以抢车位为例

        Semaphore semaphore = new Semaphore(3);

        for (int i = 1; i <= 6; i++) {
            new Thread(()->{
                try {
                    semaphore.acquire();
                    System.out.println(Thread.currentThread().getName()+"获得停车位");
                    TimeUnit.SECONDS.sleep(2);
                    System.out.println(Thread.currentThread().getName()+"释放停车位");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }finally {
                    semaphore.release();
                }
            }).start();
        }

    }
}

九、读写锁(ReentrantReadWriteLock)

ReentrantReadWriteLock它是ReadWriteLock接口的实现类:

  • 读-读:可以共享
  • 读-写:不可以共享
  • 写-写:不可以共享
package com.example.demo1.demo02;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.locks.ReentrantReadWriteLock;
/**
 * @author liar
 */
public class TestReadWriteLock {
    public static void main(String[] args) {
        MyCacheLock myCache = new MyCacheLock();
        //写线程
        for (int i = 1; i <= 5; i++) {
            final int temp = i;
            new Thread(()->{
                myCache.put(temp+"",temp+"");
            },String.valueOf(i)).start();
        }
        for (int i = 1; i <= 5; i++) {
            final int temp = i;
            new Thread(()->{
                myCache.get(temp+"");
            },String.valueOf(i)).start();
        }
    }
}
class MyCacheLock{
    //volatile:关键字
    public volatile Map map = new HashMap();
    /*
     * 为了提高效率,只允许有一个线程来写,但允许多个线程来读
     * */
    //读写锁:(并不是普通的Lock锁),是一种更加“细粒度”的锁
    private ReentrantReadWriteLock reentrantReadWriteLock = new ReentrantReadWriteLock();
    //存--》写的过程
    public void put(String key,Object value){
        reentrantReadWriteLock.writeLock().lock();
        try {
            System.out.println(Thread.currentThread().getName()+"写入"+key);
            map.put(key,value);
            System.out.println(Thread.currentThread().getName()+"写入成功");
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            reentrantReadWriteLock.writeLock().unlock();
        }
    }
    //去--》读的过程
    public void get(String key){
        reentrantReadWriteLock.readLock().lock();
        try {
            System.out.println(Thread.currentThread().getName()+"读入"+key);
            map.get(key);
            System.out.println(Thread.currentThread().getName()+"读入成功");
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            reentrantReadWriteLock.readLock().unlock();
        }
    }
}
//自定义缓存
class MyCache{
    //volatile:关键字
    public volatile Map 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()+"写入成功");
    }
    //去--》读的过程
    public void get(String key){
        System.out.println(Thread.currentThread().getName()+"读入"+key);
        map.get(key);
        System.out.println(Thread.currentThread().getName()+"读入成功");
    }
}

其运行结果是:

基于《狂神说Java》JUC并发编程--学习笔记_第16张图片

 十、阻塞队列

基于《狂神说Java》JUC并发编程--学习笔记_第17张图片

         ·基于《狂神说Java》JUC并发编程--学习笔记_第18张图片

 基于《狂神说Java》JUC并发编程--学习笔记_第19张图片

 

什么情况下使用队列:

  • 多线程并发处理
  • 线程池

常用方法:

  • 添加
  • 移除

队列中常用的四组API:

  • 抛出异常
  • 不会抛出异常
  • 阻塞等待
  • 超时等待
方式 抛出异常 不会抛出异常,有返回值 阻塞等待 超时等待
添加 add offer put offer(…)
移除 remove poll take poll(…)
获取队列首 element peak
package com.my.bq;

import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.TimeUnit;

public class BlockingQueueTest {
    public static void main(String[] args) throws InterruptedException {

        test05();

    }

    /*
    * 抛出异常
    * */
    public static void test01(){

        //需要有个参数:表示该队列可以存放的元素的个数
        ArrayBlockingQueue arrayBlockingQueue = new ArrayBlockingQueue<>(3);

        //add方法:返回值为布尔类型
        /*
        * 如果添加的元素数量超过规定的数量,则程序会报异常:Queue full
        * */
        System.out.println(arrayBlockingQueue.add("a"));
        System.out.println(arrayBlockingQueue.add("b"));
        System.out.println(arrayBlockingQueue.add("c"));

        //System.out.println(arrayBlockingQueue.add("d"));

        //remove方法:从队列中“弹出”元素,按照先进先出的顺序
        /*
        * 如果弹出的次数大于已存在队列元素的数量,也会报异常:NoSuchElementException
        * */
        System.out.println(arrayBlockingQueue.remove());
        System.out.println(arrayBlockingQueue.remove());
        System.out.println(arrayBlockingQueue.remove());

        //System.out.println(arrayBlockingQueue.remove());
    }

    /*
    * 不抛出异常
    * */
    public static void test02(){
        ArrayBlockingQueue arrayBlockingQueue = new ArrayBlockingQueue<>(3);

        //offer方法:不抛出异常,当队列满时,后面要添加的语句直接返回--false
        System.out.println(arrayBlockingQueue.offer("a"));
        System.out.println(arrayBlockingQueue.offer("b"));
        System.out.println(arrayBlockingQueue.offer("c"));

        //System.out.println(arrayBlockingQueue.offer("d"));

        //poll方法:弹出队列元素时,如果队列为空,后面的执行语句返回值--null
        System.out.println(arrayBlockingQueue.poll());
        System.out.println(arrayBlockingQueue.poll());
        System.out.println(arrayBlockingQueue.poll());

        //System.out.println(arrayBlockingQueue.poll());
    }

    /*
    * 返回队列首元素
    * */
    public static void test03(){
        ArrayBlockingQueue arrayBlockingQueue = new ArrayBlockingQueue<>(3);

        System.out.println(arrayBlockingQueue.offer("a"));
        System.out.println(arrayBlockingQueue.offer("b"));
        System.out.println(arrayBlockingQueue.offer("c"));

        System.out.println(arrayBlockingQueue.element());
        System.out.println(arrayBlockingQueue.peek());
    }

    /*
    * 阻塞等待--(一直等待)
    * */
    public static void test04() throws InterruptedException {
        ArrayBlockingQueue arrayBlockingQueue = new ArrayBlockingQueue<>(3);

        arrayBlockingQueue.put("a");
        arrayBlockingQueue.put("b");
        arrayBlockingQueue.put("c");

        //arrayBlockingQueue.put("d");

        System.out.println(arrayBlockingQueue.take());
        System.out.println(arrayBlockingQueue.take());
        System.out.println(arrayBlockingQueue.take());
    }

    /*
    * 阻塞等待(超时等待)
    * */
    public static void test05() throws InterruptedException {
        ArrayBlockingQueue arrayBlockingQueue = new ArrayBlockingQueue<>(3);

        arrayBlockingQueue.offer("a");
        arrayBlockingQueue.offer("b");
        arrayBlockingQueue.offer("c");

        //arrayBlockingQueue.offer("d",2, TimeUnit.SECONDS);

        System.out.println(arrayBlockingQueue.poll());
        System.out.println(arrayBlockingQueue.poll());
        System.out.println(arrayBlockingQueue.poll());

        arrayBlockingQueue.poll(2,TimeUnit.SECONDS);
    }
}

10.1、同步队列(SynchronizedQueue)

本质:

  • 阻塞队列:BlockingQueue接口的实现类
  • 和里面的ArrayBlockingQueue等价(它也是BlockingQueue接口的实现类)

原理:

  • “同步队列”,没有容量,进去一个元素,必须要取出来以后才能往里面放
    package com.my.bq;
    
    import java.util.concurrent.SynchronousQueue;
    import java.util.concurrent.TimeUnit;
    
    /*
    * SynchronousQueue  是  BlockingQueue  接口的实现类
    * “同步队列”,没有容量,进去一个元素,必须要取出来以后才能往里面放
     * */
    public class SynchronizedQueueTest {
        public static void main(String[] args) {
    
            SynchronousQueue synchronousQueue = new SynchronousQueue<>();
    
            new Thread(()->{
                try {
                    TimeUnit.SECONDS.sleep(2);
                    synchronousQueue.put("a");
                    System.out.println(Thread.currentThread().getName()+"线程,put了一个值:a");
    
                    TimeUnit.SECONDS.sleep(2);
                    synchronousQueue.put("b");
                    System.out.println(Thread.currentThread().getName()+"线程,put了一个值:b");
    
                    TimeUnit.SECONDS.sleep(2);
                    synchronousQueue.put("c");
                    System.out.println(Thread.currentThread().getName()+"线程,put了一个值:c");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }).start();
    
    
            new Thread(()->{
                try {
                    synchronousQueue.take();
                    System.out.println(Thread.currentThread().getName()+"线程,take了一个值:a");
    
                    synchronousQueue.take();
                    System.out.println(Thread.currentThread().getName()+"线程,take了一个值:b");
    
                    synchronousQueue.take();
                    System.out.println(Thread.currentThread().getName()+"线程,take了一个值:c");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }).start();
    
        }
    }
    

    十一、线程池(重点)

线程池:三大方法、七大参数、四种拒绝策略

11.1、池化技术

  • 程序运行的本质(消耗资源)
  • 池化技术(优化资源的使用)
  • 常见的:线程池、连接池、内存池、对象池…
  • 池化技术:事先准备好一些资源,有人要用就来我这里拿,用完就还回来

11.2、线程池的好处

  • 降低资源的消耗
  • 提高响应速度
  • 方便管理
  • 线程复用,可以控制最大并发数,管理线程

11.3、线程池

1、三大方法

package com.my.pool;

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

/*
* Executors:类似于“Collections”是一种工具类,用它可以来创建线程池
*            用它可以来创建线程池的“三大方法”
*   Executors.newSingleThreadExecutor();//单个线程
    Executors.newFixedThreadPool(5);//创建固定线程数量的线程池
    Executors.newCachedThreadPool();//随机创建一定数量线程的线程池
* */
public class ExecutorsTest {
    public static void main(String[] args) {

        //ExecutorService threadPool = Executors.newSingleThreadExecutor();//单个线程
        //ExecutorService threadPool = Executors.newFixedThreadPool(5);//创建固定线程数量的线程池
        ExecutorService threadPool = Executors.newCachedThreadPool();//随机创建一定数量线程的线程池

        /*
        * 引入线程池后,执行线程的方式也发送了改变
        *   1、执行线程:execute
        *   2、线程池用完需要关闭:shutdown
        * */
        try {
            for (int i = 0; i < 100; i++) {
                threadPool.execute(()->{
                    System.out.println(Thread.currentThread().getName()+"==>OK");
                });
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            threadPool.shutdown();
        }

    }
}

 2、七大参数和四大策略

在这里插入图片描述

 源码分析:

public static ExecutorService newSingleThreadExecutor() {
  return new FinalizableDelegatedExecutorService
    (new ThreadPoolExecutor(1, 1,
                            0L, TimeUnit.MILLISECONDS,
                            new LinkedBlockingQueue()));
}

public static ExecutorService newFixedThreadPool(int nThreads) {
  return new ThreadPoolExecutor(nThreads, nThreads,
                                0L, TimeUnit.MILLISECONDS,
                                new LinkedBlockingQueue());
}

public static ExecutorService newCachedThreadPool() {
  return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                60L, TimeUnit.SECONDS,
                                new SynchronousQueue());
}

//本质:ThreadPoolExecutor
public ThreadPoolExecutor(int corePoolSize,//核心线程池大小
                          int maximumPoolSize,//最大核心线程池大小
                          long keepAliveTime,//超时了,没有人使用就会释放
                          TimeUnit unit,//时间单位
                          BlockingQueue 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;
}
package com.my.pool;

import java.util.concurrent.Executors;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
/*
* 七大参数
*
* 四种拒绝策略
*   AbortPolicy()//拒绝策略:就是当银行满的时候,如果再来顾客,就不处理了,然后抛出异常
*   CallerRunsPolicy()//哪来的回哪去
*   DiscardPolicy()//队列满了,就丢掉任务,不会抛出异常
*
* */
public class ThreadPoolExecutorTest {
    public static void main(String[] args) {

        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
                2,
                5,
                3,
                TimeUnit.SECONDS,
                new LinkedBlockingQueue<>(3),
                Executors.defaultThreadFactory(),
                new ThreadPoolExecutor.DiscardOldestPolicy()//队列满了,刚来的尝试和队首竞争,也不抛出异常
        );

        try {
            //最大承载:max+Queue
            //超过最大承载就抛出异常:RejectedExecutionException
            for (int i = 0; i < 10; i++) {
                threadPoolExecutor.execute(()->{
                    System.out.println(Thread.currentThread().getName()+"==>OK");
                });
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            threadPoolExecutor.shutdown();
        }

    }
}

3、核心线程池最大数量到底定义到多少?

最大核心线程池数量到底应该定义为多少?(调优)

CPU密集型:就是根据电脑中CPU中的核数(逻辑处理器数量)
获取每个电脑中的CPU核数:System.out.println(Runtime.getRuntime().availableProcessors());
IO密集型:对于大型任务,io非常占资源,判断程序类似任务多少,最好分配的线程数量大于这样的任务数量(多的线程可以取执行其他任务,避免阻塞)

十二、四大函数式接口(必须掌握)

新时代的程序员:Lambda表达式、链式编程、函数式接口、Stream流计算

12.1、函数式接口

概念:定义的接口中有且仅含有一个方法

优势:简化编程模型,在新版的框架中大量应用

在这里插入图片描述

 

12.2、详解

1、Function(函数型接口)

基于《狂神说Java》JUC并发编程--学习笔记_第20张图片

 

package com.my.function;

import java.util.function.Function;
//Function:函数型接口(接口中只有一个方法)
//接口实现类-》匿名内部类-》Lambda表达式
public class FunctionTest {
    public static void main(String[] args) {
        //匿名内部类
        Function function = new Function() {
            //该方法的作用:输出输入的值
            @Override
            public String apply(String str) {
                return str;
            }
        };

        //Lambda表达式
        Function function1 = (str)->{return str;};

        System.out.println(function.apply("麻腾飞"));
        System.out.println(function1.apply("Matengfei"));
    }
}

2、Predicate(断定型接口)

基于《狂神说Java》JUC并发编程--学习笔记_第21张图片

 

package com.my.function;

import java.util.function.Predicate;
/*
* 断定型接口
* */
public class PredicateTest {
    public static void main(String[] args) {

        //判断字符串是否为空
        Predicate predicate = new Predicate() {
            @Override
            public boolean test(String str) {
                return str.isEmpty();
            }
        };

        Predicate predicate1 = (str)->{return str.isEmpty();};

        System.out.println(predicate.test(""));
        System.out.println(predicate1.test(""));
    }
}

3、Consumer(消费型接口)

在这里插入图片描述

 

package com.my.function;

import java.util.function.Consumer;
/*
* 消费型接口:只有输入
* */
public class ConsumerTest {
    public static void main(String[] args) {
        //只接收输入的参数
        Consumer consumer = new Consumer() {
            @Override
            public void accept(String  str) {
                System.out.println(str);
            }
        };

        Consumer consumer1 = (str)->{
            System.out.println(str);
        };

        consumer.accept("Matengfei");
        consumer1.accept("Matengfei");
    }
}

4、Supplier(供给型接口)

基于《狂神说Java》JUC并发编程--学习笔记_第22张图片

 

package com.my.function;

import java.util.function.Supplier;

/*
* 供给型接口:只输出,不输入
* */
public class SupplierTest {
    public static void main(String[] args) {

        Supplier supplier = new Supplier() {
            @Override
            public String get() {
                System.out.println("get");
                return "1024";
            }
        };

        Supplier supplier1 = ()->{return "110";};

        System.out.println(supplier.get());
        System.out.println(supplier1.get());

    }
}

十三、Stream流式计算

13.1、什么是Stream流式计算

  • 存储+计算
  • 存储:集合、MySQL、分布式…
  • 计算:交给“流”来做

13.2.具体示例

package com.my.stream;

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

public class StreamTest {
    public static void main(String[] args) {
        /*
        * 现在有五个用户,按条件进行筛选:
        *   1、ID必须是偶数
        *   2、年龄必须大于20岁
        *   3、用户名转为大写字母
        *   4、用户名字母倒着排序
        *   5、只输出一个用户
        * */
        User user1 = new User(1, "a", 18);
        User user2 = new User(2, "b", 12);
        User user3 = new User(4, "d", 22);
        User user4 = new User(5, "f", 30);
        User user5 = new User(6, "z", 40);

        //存储
        List list = Arrays.asList(user1, user2, user3, user4, user5);

        //计算
        //搞明白:System.out::println
        //Lambda表达式、链式编程、函数式接口、Stream流式计算
        list.stream().filter((u)->{return u.getId()%2==0;})
                .filter((u)->{return u.getAge()>20;})
                .map((u)->{return u.getName().toUpperCase();})
                .sorted((uu1,uu2)->{return uu2.compareTo(uu1);})
                .limit(1)
                .forEach(System.out::println);
    }
}

十四、ForkJoin

14.1、什么是ForkJoin(分支合并)

  • ForkJoin在JDk1.7之后出现,并行执行任务!提高效率,数据量大!

在这里插入图片描述

 

14.2、ForkJoin特点

工作窃取:提高工作效率!(“双端队列”)

基于《狂神说Java》JUC并发编程--学习笔记_第23张图片

 14.3、ForkJoin实例

package com.my.forkjoin;

import java.util.concurrent.RecursiveTask;

public class ForkJoinDemo extends RecursiveTask {

    private Long start;
    private Long end;

    //临界值
    private Long temp=10000L;

    public ForkJoinDemo(Long start, Long end) {
        this.start = start;
        this.end = end;
    }

    @Override
    protected Long compute() {
        if ((end-start)
package com.my.forkjoin;

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 {
        //test01();//13999
        //test02();//10833
        test03();//1147
    }

    public static void test01(){
        Long sum = 0L;
        Long start = System.currentTimeMillis();
        for (Long i=1L;i<=10_0000_0000L;i++){
            sum+=i;
        }
        Long end = System.currentTimeMillis();
        System.out.println("sum="+sum+";时间:"+(end-start));
    }

    //使用ForkJoin,可以通过临界值(temp)来进行调优
    public static void test02() throws ExecutionException, InterruptedException {
        Long start = System.currentTimeMillis();

        ForkJoinPool forkJoinPool = new ForkJoinPool();

        ForkJoinDemo task = new ForkJoinDemo(0L, 10_0000_0000L);
        ForkJoinTask submit = forkJoinPool.submit(task);
        Long sum = submit.get();

        Long end = System.currentTimeMillis();
        System.out.println("sum="+sum+";时间:"+(end-start));
    }
    //使用Stream流式计算
    public static void test03(){
        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设计的初衷:对未来的某个事件的结果进行建模

package com.my.future;

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

/*
* 异步回调:ajax(前后端交互)、Future(后端)
*   异步执行
*   成功回调
*   失败回调
* */
public class FutureTest {
    public static void main(String[] args) throws ExecutionException, InterruptedException {

        //发起一个请求(没有返回值的异步回调)
        /*CompletableFuture completableFuture = CompletableFuture.runAsync(()->{//执行一个异步任务
            try {
                TimeUnit.SECONDS.sleep(2);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName()+"==>run");
        });

        System.out.println("主线程...");

        completableFuture.get();//获取阻塞结果*/

        //有返回值的异步回调
        /*
        * 它和ajax一样,如果有返回值的情况:
        *   成功:返回结果
        *   失败:打印错误信息
        * */
        CompletableFuture completableFuture = CompletableFuture.supplyAsync(()->{
            System.out.println(Thread.currentThread().getName()+"run");
            int i=10/0;
            return 1024;
        });
        System.out.println(
            completableFuture.whenComplete((t,u)->{
            System.out.println("t=>"+t);//返回的是正确的结果
            System.out.println("u=>"+u);//返回的是错误的详细信息:u=>java.util.concurrent.CompletionException: java.lang.ArithmeticException: / by zero
        }).exceptionally((e)->{
            e.getMessage();
            return 233;
        }).get());
    }
}

十六、JMM


16.1、Volatile关键字


Volatile是Java虚拟机提供的轻量级同步机制

保证可见性
不保证原子性
禁止指令重排


16.2、JMM


JMM:是一种理论概念(内存模型),实际中不存在,就是一个概念,一个约定

1、关于JMM的一些约定


线程释放锁前,必须将线程自己内存中的数据立刻刷新到主存
线程加锁前,必须先从主存中拷取最新的数据
加锁和释放的锁必须是同一把锁


2、8种操作
在这里插入图片描述

 存在问题:

在这里插入图片描述

 

内存交互操作(规则):

不允许read和load、store和write操作之一单独出现。即使用了read必须load,使用了store必须write
不允许线程丢弃他最近的assign操作,即工作变量的数据改变了之后,必须告知主存
不允许一个线程将没有assign的数据从工作内存同步回主内存
一个新的变量必须在主内存中诞生,不允许工作内存直接使用一个未被初始化的变量。就是怼变量实施use、store操作之前,必须经过assign和load操作
一个变量同一时间只有一个线程能对其进行lock。多次lock后,必须执行相同次数的unlock才能解锁
如果对一个变量进行lock操作,会清空所有工作内存中此变量的值,在执行引擎使用这个变量前,必须重新load或assign操作初始化变量的值
如果一个变量没有被lock,就不能对其进行unlock操作。也不能unlock一个被其他线程锁住的变量
对一个变量进行unlock操作之前,必须把此变量同步回主内存


十七、Volatile

问题引入:

package com.my.volatileTest;
/*
* 模拟8种操作存在的问题
* */
public class VolatileTest {
    private static int num = 0;
    public static void main(String[] args) {//main线程

        new Thread(()->{//线程A
            while (num==0){

            }
        }).start();

        num = 1;
        System.out.println("num=>"+num);
    }
}

在这里插入图片描述

 17.1、保证可见性

package com.my.volatileTest;
/*
* 模拟8种操作存在的问题
* */
public class VolatileTest {
    //加上volatile关键字:保证可见性
    private volatile static int num = 0;
    public static void main(String[] args) {//main线程

        new Thread(()->{
            while (num==0){

            }
        }).start();

        num = 1;
        System.out.println("num=>"+num);
    }
}

17.2、不保证原子性

原子性:不可分割

线程在执行时,不能被干扰,不可分割;要么成功,要么失败

package com.my.volatileTest;
//证明:Volatile关键字不保证原子性
public class VolatileTest02 {

    private volatile static int num = 0;

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

    public static void main(String[] args) {

        for (int i = 0; i < 20; i++) {
            new Thread(()->{
                for (int i1 = 0; i1 < 1000; i1++) {
                    add();
                }
            }).start();
        }

        while (Thread.activeCount()>2){//main线程、gc线程
            Thread.yield();
        }

        System.out.println(Thread.currentThread().getName()+"num=>"+num);
    }
}

问:如果不使用lock和synchronized,怎么保证原子性?

基于《狂神说Java》JUC并发编程--学习笔记_第24张图片

 

package com.my.volatileTest;

import java.util.concurrent.atomic.AtomicInteger;

//证明:Volatile关键字不保证原子性
public class VolatileTest02 {

    //private volatile static int num = 0;
    //原子类的Integer
    private static AtomicInteger num = new AtomicInteger();

    public static void add(){
        //num++;
        num.getAndIncrement();//AtomicInteger中的+1方法,要想更详细了解:CAS
    }

    public static void main(String[] args) {

        for (int i = 0; i < 20; i++) {
            new Thread(()->{
                for (int i1 = 0; i1 < 1000; i1++) {
                    add();
                }
            }).start();
        }

        while (Thread.activeCount()>2){//main线程、gc线程
            Thread.yield();
        }

        System.out.println(Thread.currentThread().getName()+"num=>"+num);
    }
}

注:这些原子类的底层和操作系统有关!在内存中修改值!Unsafe类是一个很特殊的存在

17.3、指令重排

什么是指令重排?

  • 我们写的程序,实际在计算机中并不是按照我们想象的顺序执行的
  • 源代码——》编译器优化的重排——》指令并行也可能重排——》内存系统也可能会重排——》执行
int x=1;//a
int y=2;//b
x=x+y;//c
y=x*x;//d

我们期望的执行顺序:abcd;实际上可能是bacd
那可不可能是:cdab

处理器在进行指令重排的时候:考虑数据之间的依赖性;但是如果前后程序语句没有依赖关系,可能会存在影响结果的指令重排

volatile关键字可以避免指令重排:(内存屏障,cpu指令)

  • 保证特定的操作按顺序执行
  • 可以保证某些变量的内存可见性(volatile保证可见性的底层原理)

十八、单例模式

18.1、单例模式的定义

单例模式是指在内存中只会创建且仅创建一次对象的设计模式
在程序中多次使用同一个对象且作用相同时,为了防止频繁地创建对象使得内存飙升,单例模式可以让程序仅在内存中创建一个对象,让所有需要调用的地方都共享这一单例对象
单例模式分类:

饿汉式单例模式
懒汉式单例模式
存在问题:

反射机制、序列化和反序列化会破坏单例模式!
 

18.2、饿汉式单例模式

饿汉式在类加载时已经创建好该对象,在程序调用时直接返回该单例对象即可,即我们在编码时就已经指明了要马上创建这个对象,不需要等到被调用时再去创建。

package com.my.single;
//饿汉式单例模式:“饿”创建对象更早!

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

public class Hungry {
    //类加载时,对象就已经被创建
    private static final Hungry hungry = new Hungry();

    public Hungry() {
        synchronized (Hungry.class){
            if (hungry!=null){
                throw new RuntimeException("试图通过反射破坏单例模式!");
            }
        }
    }

    public static Hungry getInstance(){
        return hungry;
    }
    //通过反射来破坏单例模式
    public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
        //Hungry hungry = new Hungry();

        Constructor constructor = Hungry.class.getDeclaredConstructor(null);
        //通过反射获取这个类的构造器,然后通过构造器来创建对象
        constructor.setAccessible(true);
        Hungry hungry1 = constructor.newInstance();
        Hungry hungry2 = constructor.newInstance();

        System.out.println(hungry2==hungry1);//输出结果为false


    }
    /*
    * 总结:
    *   1、第一个对象通过构造器正常创建,第二个对象通过反射机制来创建(不加任何修改,第二个对象会创建成功)--解决:为了防止单例模式被破坏,需要对类的私有构造器加锁!
    *   2、加入我创建的第一个对象是通过反射机制创建的,那么第二个通过反射机制创建的对象也会破坏单例模式(执行成功)--解决:通过“信号灯”法,设置一个标志位
    * */

}

18.3、懒汉式单例模式

懒汉式创建对象的方法是在程序使用对象前,先判断该对象是否已经实例化(判空),若已实例化直接返回该类对象。,否则则先执行实例化操作。

 

package com.my.single;
/*
* 懒汉式单例模式
*   “双重检测”懒汉式单例模式(Double Check(双重校验) + Lock(加锁))
* */
public class Lazy {

    private static volatile Lazy lazy;

    public Lazy() {
        System.out.println(Thread.currentThread().getName()+"线程创建了Lazy对象!");
    }

    public static Lazy getInstance(){
        //双重检测模式  DCL--但是还是会存在问题:原因lazy = new Lazy();不是原子性操作(存在指令重排问题!!!)
        //为了解决这个问题:需要使用volatile关键字,来禁止指令重排
        if (lazy==null){
            synchronized (Lazy.class){
                if (lazy==null){
                    lazy = new Lazy();
                    /*
                    * 分配内存空间
                    * 执行构造方法,初始化类对象
                    * 将创建的类对象的地址指向分配的内存空间
                    * */
                }
            }
        }
        return lazy;
    }

    public static void main(String[] args) {
        for (int i = 0; i < 10; i++) {
            new Thread(()->{
                getInstance();

            }).start();
        }
    }
}

18.4、枚举类

枚举类是JDK1.5后,自带单例模式的类(该类定义单例模式,可以防止被反射机制破坏)

package com.my.single;

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

//枚举类默认是单例模式
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;
        //EnumSingle instance2 = EnumSingle.INSTANCE;
        /*
        * 通过反射机制来验证:枚举类可以防止反射破坏单例模式
        *   1、但是结果并不是按照我们想象的那样,结果报了这个异常:
        *   Exception in thread "main" java.lang.NoSuchMethodException: com.my.single.EnumSingle.()
        *
        *   2、原因就是枚举类创建对象是通过“有参构造器”来创建的
        *   按照正常情况结果会报如下异常:Cannot reflectively create enum objects
        * */

        //Constructor constructor = EnumSingle.class.getDeclaredConstructor(String.class,int.class);
        Constructor constructor = EnumSingle.class.getDeclaredConstructor(null);
        constructor.setAccessible(true);

        //EnumSingle enumSingle1 = constructor.newInstance();
        EnumSingle instance2 = constructor.newInstance();

        System.out.println(instance1==instance2);
    }
}

基于《狂神说Java》JUC并发编程--学习笔记_第25张图片

 十九、CAS

什么是CAS?

  • 比较工作内存中的值(期望的值)和主存中的值,如果这个期望的值和主存中的值相等,那么就进行修改

缺点:

  • 循环会耗时(底层自旋锁)
  • 一次性只能保证一个共享变量的原子性
  • ABA问题(狸猫换太子)
package com.my.cas;

import java.util.concurrent.atomic.AtomicInteger;

public class CASDemo {
    public static void main(String[] args) {

        AtomicInteger atomicInteger = new AtomicInteger(2022);
        // public final boolean compareAndSet(int expect, int update)
        //期望,更新
        atomicInteger.compareAndSet(2022,2023);
        System.out.println(atomicInteger.get());

        System.out.println(atomicInteger.compareAndSet(2022, 2000));
        System.out.println(atomicInteger.get());
    }
}

Unsafe类

基于《狂神说Java》JUC并发编程--学习笔记_第26张图片

 19.1、ABA问题

基于《狂神说Java》JUC并发编程--学习笔记_第27张图片

 

package com.my.cas;

import java.util.concurrent.atomic.AtomicInteger;

public class CASDemo {
    public static void main(String[] args) {

        AtomicInteger atomicInteger = new AtomicInteger(2022);
        // public final boolean compareAndSet(int expect, int update)
        //期望,更新

        //捣乱的线程B
        atomicInteger.compareAndSet(2022,2023);
        System.out.println(atomicInteger.get());

        atomicInteger.compareAndSet(2023,2022);
        System.out.println(atomicInteger.get());

        //期望的线程A
        System.out.println(atomicInteger.compareAndSet(2022, 2000));
        System.out.println(atomicInteger.get());
    }
}

二十、原子引用

模拟ABA场景:

package com.my.cas;

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

public class ABATest {
    public static void main(String[] args) {

        //Integer:反是这种包装类(引用数据类型)看看有没有超出返回,如果超出范围,会new一个新的对象
        AtomicStampedReference stampedReference = new AtomicStampedReference<>(1,1);

        new Thread(()->{
            int stamp = stampedReference.getStamp();
            System.out.println("a1=>"+stamp);

            //a、b线程在这里一起休眠两秒
            try {
                TimeUnit.SECONDS.sleep(2);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            stampedReference.compareAndSet(1, 8, stampedReference.getStamp(), stampedReference.getStamp()+1);
            System.out.println("a2=>"+stampedReference.getStamp());

            stampedReference.compareAndSet(8, 1, stampedReference.getStamp(), stampedReference.getStamp()+1);
            System.out.println("a3=>"+stampedReference.getStamp());


        },"a").start();

        new Thread(()->{
            int stamp = stampedReference.getStamp();
            System.out.println("b1=>"+stamp);

            //a、b线程在这里一起休眠两秒
            try {
                TimeUnit.SECONDS.sleep(4);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            stampedReference.compareAndSet(1, 6, stampedReference.getStamp(), stampedReference.getStamp()+1);
            System.out.println("b2=>"+stampedReference.getStamp());

        },"b").start();
    }
}

二十一、各种锁的理解

21.1、公平锁和非公平锁

基于《狂神说Java》JUC并发编程--学习笔记_第28张图片

 

  • 公平锁:非常公平,不允许插队
  • 非公平锁:不公平,可以根据情况来插队;例如,线程A需要3秒,线程B需要1小时

21.2、可重入锁

  • 可重入锁(递归锁)
  • 大部分锁默认的都是非公平锁

基于《狂神说Java》JUC并发编程--学习笔记_第29张图片

 可重入锁(synchronized版):

package com.my.lock;

import java.util.concurrent.TimeUnit;

//可重入锁:synchronized版
public class Test01 {
    public static void main(String[] args) {
        Phone phone = new Phone();

        new Thread(()->{
            try {
                phone.send();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        },"A").start();

        new Thread(()->{
            try {
                phone.send();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        },"B").start();

    }
}

class Phone{

    public synchronized void send() throws InterruptedException {
        System.out.println(Thread.currentThread().getName()+"=>发短信");
        call();
    }

    public synchronized void call() throws InterruptedException {
        TimeUnit.SECONDS.sleep(3);

        System.out.println(Thread.currentThread().getName()+"=>打电话");
    }
}

可重入锁(Lock版):

package com.my.lock;

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

//可重入锁:Lock版
public class Test02 {
    public static void main(String[] args) {
        Phone02 phone = new Phone02();

        new Thread(()->{
                phone.send();
        },"A").start();

        new Thread(()->{
                phone.send();
        },"B").start();

    }
}

class Phone02{

    Lock lock = new ReentrantLock();

    public void send() {
        lock.lock();
        try {
            System.out.println(Thread.currentThread().getName()+"=>发短信");
            call();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

    public void call() {
        lock.lock();
        try {
            System.out.println(Thread.currentThread().getName()+"=>打电话");
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
}


21.3、自旋锁

自旋锁核心:循环+CAS

自定义创建自旋锁:

package com.my.lock;

import java.util.concurrent.atomic.AtomicReference;

//定义自旋锁:循环+CAS
public class Test03 {

    AtomicReference atomicReference = new AtomicReference();

    //加锁
    public void myLock(){
        Thread thread = Thread.currentThread();
        System.out.println(Thread.currentThread().getName()+"线程:进入了我的自旋锁!");

        //就是如果当前线程不是“null”,就会一直循环
        while (!atomicReference.compareAndSet(null,thread)){}
    }

    //解锁
    public void myUnLock(){
        Thread thread = Thread.currentThread();
        System.out.println(Thread.currentThread().getName()+"线程:拿到了锁,开始释放锁!");

        atomicReference.compareAndSet(thread,null);
    }
}

调用自定义的自旋锁:

package com.my.lock;
import java.util.concurrent.TimeUnit;

//调用自己创建的自旋锁
public class Test04 {
    public static void main(String[] args) {

        Test03 lock = new Test03();

        new Thread(()->{
            lock.myLock();

            try {
                TimeUnit.SECONDS.sleep(3);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            lock.myUnLock();
        },"A").start();

        new Thread(()->{
            System.out.println(Thread.currentThread().getName()+"线程:正在尝试获取锁!");
            lock.myLock();

            lock.myUnLock();
        },"B").start();
    }
}

21.4、死锁

基于《狂神说Java》JUC并发编程--学习笔记_第30张图片

 

package com.my.lock;

import java.util.concurrent.TimeUnit;

//搭建“死锁”现场
public class Test05 {
    public static void main(String[] args) {

        String LockA = "LockA";
        String LockB = "LockB";

        new Thread(new MyThread(LockA,LockB),"A线程").start();
        new Thread(new MyThread(LockB,LockA),"B线程").start();

    }
}

class MyThread implements Runnable{

    private String LockA;
    private String LockB;

    public MyThread(String lockA, String lockB) {
        LockA = lockA;
        LockB = lockB;
    }

    @Override
    public void run() {
        synchronized (LockA){
            System.out.println(Thread.currentThread().getName()+":拿着LockA,试图想要LockB!");

            //休眠一会,保证线程B,那到B锁
            try {
                TimeUnit.SECONDS.sleep(3);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            synchronized (LockB){
                System.out.println(Thread.currentThread().getName()+":拿着LockB,试图想要LockA!");
            }
        }
    }
}

怎么排查死锁

步骤:

  • 使用jps -l命令定位进程号

基于《狂神说Java》JUC并发编程--学习笔记_第31张图片

 再使用jstack 进程号,发现死锁问题

基于《狂神说Java》JUC并发编程--学习笔记_第32张图片

 基于《狂神说Java》JUC并发编程--学习笔记_第33张图片

 

面试或者工作中,解决问题:

  • 日志(常用)
  • 堆栈信息

到此就结束了,再次声明,此文,只作本人复习使用。如有侵权,请联系我,将妥善处理。

邮箱:[email protected]

你可能感兴趣的:(学习)