JUC快速入门

JUC

文章目录

  • JUC
    • 1、什么是JUC
    • 2、线程和进程
    • 3、并发和并行
    • 4、线程有几个状态
    • 5、Lock锁(重点)
    • 6、生产者和消费者问题
    • 7、8锁现象
      • 7.1、前两个问题
      • 7.2、第三个问题
      • 7.3、第四个问题
      • 7.4、第五个问题
      • 7.5、第六个问题
      • 7.6、第七个问题
      • 7.7、第八个问题
    • 8、集合类不安全
      • 8.1、List不安全
      • 8.2、Set不安全
      • 8.3、HashMap不安全
    • 9、Callable
    • 10、常用的辅助类
      • 10.1、CountDownLatch
      • 10.2、CyclicBarrier
      • 10.3、Semaphore
    • 11、读写锁ReadWriteLock
    • 12、阻塞队列BlockingQueue
    • 13、同步队列SynchronousQueue
    • 14、线程池(重点)
      • 14.1、三大方法
      • 14.2、七大参数
      • 14.3、四种拒绝策略
    • 15、CPU密集型和IO密集型
    • 16、四大函数式接口(必须掌握)
      • 16.1、Function函数式接口
      • 16.2、Predicate断定型接口
      • 16.3、Consumer消费型接口
      • 16.4、Supplier供给型接口
    • 17、Stream流式计算
    • 18、ForkJoin
    • 19、异步回调
    • 20、JMM
    • 21、Volatile
    • 22、单例模式
      • 22.1、第一次破坏和恢复
      • 22.2、第二次破坏和恢复
      • 22.3、第三次破坏
      • 22.4、反射无法破坏枚举的单例
    • 23、深入理解CAS
    • 24、各种锁的理解
      • 24.1、公平锁、非公平锁
      • 24.2、可重入锁
      • 24.3、自旋锁
    • 25、死锁

1、什么是JUC

JUC快速入门_第1张图片

java.util.concurrent
java.util.concurrent.atomic
java.util.concurrent.locks

业务:普通的线程代码Thread、Runnable:没有返回值,效率没有Callable高

JUC快速入门_第2张图片
JUC快速入门_第3张图片

2、线程和进程

进程:一个程序、例如qq.exe、music.exe

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

java默认有几个线程?2个 main和GC

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

对于java而言:Thread、Runnable、Callable

java真的能开启线程吗?开不了线程

public synchronized void start() {
  /**
     * This method is not invoked for the main method thread or "system"
     * group threads created/set up by the VM. Any new functionality added
     * to this method in the future may have to also be added to the VM.
     *
     * A zero status value corresponds to state "NEW".
     */
    if (threadStatus != 0)
        throw new IllegalThreadStateException();

    /* Notify the group that this thread is about to be started
     * so that it can be added to the group's list of threads
     * and the group's unstarted count can be decremented. */
    group.add(this);

    boolean started = false;
    try {
        start0();
        started = true;
    } finally {
        try {
            if (!started) {
                group.threadStartFailed(this);
            }
        } catch (Throwable ignore) {
            /* do nothing. If start0 threw a Throwable then
              it will be passed up the call stack */
        }
    }
}

// 本地方法,调用底层的c++,无法直接操作硬件
private native void start0();

3、并发和并行

并发编程:并发、并行


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

  • 比如一个食堂打饭口,一会给这人打饭、一会给那人打饭
  • CPU一核,模拟出来多条线程,天下武功,唯快不破,快速交替

并行(多个人一起行走):线程池

  • 一个食堂打饭口,就给一个人打饭
  • CPU多核,多个线程可以同时执行

JUC快速入门_第4张图片

查看电脑是几核的

public class Main {
    public static void main(String[] args) {
        // 获取cpu的核数
        // CPU密集型、IO密集型
        System.out.println(Runtime.getRuntime().availableProcessors());
    }
}

并发编程的本质:充分利用CPU的资源,公司特别看重的东西!

4、线程有几个状态

public enum State {
  	// 新生
    NEW,
    // 运行
    RUNNABLE,
	// 阻塞
    BLOCKED,
    // 等待
    WAITING,
    // 超时等待
    TIMED_WAITING,
	// 终止
    TERMINATED;
}

wait/sleep区别

1、来自不同的类

wait =》Object

sleep =》Thread

2、关于锁的释放

wait:会释放锁

sleep:就是睡觉了,抱着锁睡觉了!

3、使用的范围是不同的

wait:必须在同步代码块中

sleep:可以在任何地方睡

4、是否需要捕获异常

wait:不需要捕获异常

sleep:必须要捕获异常

5、Lock锁(重点)

传统Synchronized

// 一个卖票的例子

/**
 * 真正的多线程开发,公司中的开发,降低耦合性
 * 线程就是一个单独的资源类,没有任何附属的操作!
 * 1、属性、方法
 */
public class Demo1 {
    public static void main(String[] args) {
        // 并发:多线程操作同一个资源类,把资源丢入线程
        Ticket ticket = new Ticket();
        new Thread(() -> {
            for (int i = 0; i < 60; i++) {
                ticket.sale();
            }
            }, "A").start();
        new Thread(() -> {
            for (int i = 0; i < 60; i++) {
                ticket.sale();
            }
            }, "B").start();
        new Thread(() -> {
            for (int i = 0; i < 60; i++) {
                ticket.sale();
            }
            }, "C").start();
    }
}

// OOP解耦,
class Ticket {
    // 属性和方法
    private int number = 50;

    // 卖票的方式
    public synchronized void sale(){
        int count = 0;
        while(number > 0){
            System.out.println(Thread.currentThread().getName() + "卖出了" + number-- + "票,剩余" + number);
        }
    }
}

JUC快速入门_第5张图片

写线程的方式,Lamada表达式

Lock锁

JUC快速入门_第6张图片

JUC快速入门_第7张图片

JUC快速入门_第8张图片
公平锁:十分公平,先来后到

非公平锁:不公平,可以插队,默认

package com.lzy;

/**
 * @author li
 * @data 2023/3/19
 * @time 18:01
 */
// 一个卖票的例子

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

/**
 * 真正的多线程开发,公司中的开发,降低耦合性
 * 线程就是一个单独的资源类,没有任何附属的操作!
 * 1、属性、方法
 */
public class Demo2 {
    static final ReentrantLock lock = new ReentrantLock();
    public static void main(String[] args) {
        Ticket2 ticket2 = new Ticket2();
        new Thread(() -> {for (int i = 0; i < 60; i++) ticket2.sale();}, "A").start();
        new Thread(() -> {for (int i = 0; i < 60; i++) ticket2.sale();}, "B").start();
        new Thread(() -> {for (int i = 0; i < 60; i++) ticket2.sale();}, "C").start();
    }
}

// OOP解耦,
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);
            }
        }finally {
            lock.unlock();
        }
    }
}

Synchronized和Lock的区别

可以把Synchronized当做是自动挡,Lock当做是手动挡,一个是好开适合上手,一个比较难上手,但是在跑赛道的时候,都是手动挡的车,没有自动挡的,你明白吧!

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

锁是什么

6、生产者和消费者问题

Synchronized wait notify

juc lock

面试:单例、排序、生产者和消费者问题

A,B两个线程

/**
 * 线程之间的通信问题:生产者和消费者问题!  等待唤醒  通知唤醒
 * 线程交替执行,A B 操作同一个 变量 num = 0
 */
public class A {
    public static void main(String[] args) {
        Data data = new Data();
        new Thread(() -> { for(int i = 0; i < 10; i++) {
            try {
                data.increment();
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }}, "A").start();
        new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                try {
                    data.decremnt();
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        }, "B").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 decremnt() throws InterruptedException {
        if(number == 0){
            this.wait();
        }
        number--;
        System.out.println(Thread.currentThread().getName() + " =>" + number) ;
        this.notifyAll();
    }
}

JUC快速入门_第9张图片

A,B,C,D,四个线程

/**
 * 线程之间的通信问题:生产者和消费者问题!  等待唤醒  通知唤醒
 * 线程交替执行,A B 操作同一个 变量 num = 0
 */
public class A {
    public static void main(String[] args) {
        Data data = new Data();
        new Thread(() -> { for(int i = 0; i < 10; i++) {
            try {
                data.increment();
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }}, "A").start();
        new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                try {
                    data.decremnt();
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        }, "B").start();
        new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                try {
                    data.increment();
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        }, "C").start();
        new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                try {
                    data.decremnt();
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        }, "D").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 decremnt() throws InterruptedException {
        if(number == 0){
            this.wait();
        }
        number--;
        System.out.println(Thread.currentThread().getName() + " =>" + number) ;
        this.notifyAll();
    }
}

JUC快速入门_第10张图片

很明显出现了问题

JUC快速入门_第11张图片

这个问题在于使用的if,应该写成while

// 判断等待、业务、通知
class Data{
        private int number = 0;
     public synchronized  void increment() throws InterruptedException {
         while(number != 0){
            this.wait();
         }
         number++;
         System.out.println(Thread.currentThread().getName() + " =>" + number) ;
         this.notifyAll();
     }

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

JUC版本的生产者消费者

JUC快速入门_第12张图片

JUC快速入门_第13张图片

JUC快速入门_第14张图片

static int count = 0;
Lock lock = new ReentrantLock();
Condition condition = lock.newCondition();

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

public void jian(){
    lock.lock();
    try {
        while(count == 0){
            condition.await();
        }
        count--;
        System.out.println(Thread.currentThread().getName() + "=>" + count);
        condition.signalAll();
    }catch (Exception e){
        e.printStackTrace();
    }finally {
        lock.unlock();
    }
}

JUC快速入门_第15张图片

这里可以发现是随机执行的

Condition实现精准通知唤醒

class Data1{
    private Lock lock = new ReentrantLock();
    Condition condition1 = lock.newCondition();
    Condition condition2 = lock.newCondition();
    Condition condition3 = lock.newCondition();
    int number = 1;

    public void printA(){
        lock.lock();
        try {
            // 1A 2B 3C
            while(number != 1){
                condition1.await();
            }
            System.out.println(Thread.currentThread().getName() + "=>" + number);
            number = 2;
            condition2.signal();
        }catch (Exception e){
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

    public void printB(){
        lock.lock();
        try {
            // 1A 2B 3C
            while(number != 2){
                condition2.await();
            }
            System.out.println(Thread.currentThread().getName() + "=>" + number);
            number = 3;
            condition3.signal();
        }catch (Exception e){
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

    public void printC(){
        lock.lock();
        try {
            // 1A 2B 3C
            while(number != 3){
                condition3.await();
            }
            System.out.println(Thread.currentThread().getName() + "=>" + number);
            number = 1;
            condition1.signal();
        }catch (Exception e){
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
}

7、8锁现象

如何判断锁的是谁,知道什么锁,锁到底锁的是谁

7.1、前两个问题

public class test1 {
    public static void main(String[] args) {
        Phone phone = new Phone();
        new Thread(() ->{
            phone.sendSms();
        }, "A").start();

        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }

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

class Phone{
    public synchronized void sendSms(){
        System.out.println("发短信");
    }
    public synchronized void call(){
        System.out.println("打电话");
    }
}

问题1:正常情况下,两个线程先打印,发短信还是打电话?

public class test1 {
    public static void main(String[] args) {
        Phone phone = new Phone();
        new Thread(() ->{
            phone.sendSms();
        }, "A").start();

        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }

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

class Phone{
    public synchronized void sendSms(){
    	 try {
            TimeUnit.SECONDS.sleep(4);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        System.out.println("发短信");
    }
    public synchronized void call(){
        System.out.println("打电话");
    }
}

问题2:如果在sendSms中模拟操作(用延迟来替代),这次先打印哪个?

结果都是1、发短信;2、打电话,如果是按我的理解是,先调用的发短信,所以发短信在前,但是这个解释有点不专业

解释:

  1. synchronized 锁的对象是方法的调用者
  2. 两个方法用的是同一个锁,谁先拿到谁执行

7.2、第三个问题

public class test1 {
    public static void main(String[] args) {
        Phone phone = new Phone();
        new Thread(() ->{
            phone.sendSms();
        }, "A").start();

        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }

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

class Phone{
    public synchronized void sendSms(){
        try {
            TimeUnit.SECONDS.sleep(3);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        System.out.println("发短信");
    }
    public synchronized void call(){
        System.out.println("打电话");
    }
    public void hello(){
        System.out.println("Hello World!");
    }
}

问题3:增加了一个普通方法后!两个线程先是执行带锁的方法,还是普通的方法?

答案是先执行Hello World,在执行发短信

解释:

  • 这个普通方法没有锁,不是同步方法,不受锁的影响

7.3、第四个问题

public class test1 {
    public static void main(String[] args) {
        Phone phone1 = new Phone();
        Phone phone2 = new Phone();
        new Thread(() ->{
            phone1.sendSms();
        }, "A").start();

        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }

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

class Phone{
    public synchronized void sendSms(){
        try {
            TimeUnit.SECONDS.sleep(3);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        System.out.println("发短信");
    }
    public synchronized void call(){
        System.out.println("打电话");
    }
    public void hello(){
        System.out.println("Hello World!");
    }
}

问题:两个对象,两个同步方法,先执行哪个?

答案是调用打电话,后调用发短信

解释:

  • 还是因为锁的对象是方法的调用者,这里有两个对象,也就是有两个调用者,两把锁,两个线程不互相的干扰,又因为发短信里面有一个延迟所以就是谁等待的时间少就先出现

7.4、第五个问题

public class test1 {
    public static void main(String[] args) {
        Phone phone = new Phone();
        new Thread(() ->{
            phone.sendSms();
        }, "A").start();

        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }

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

class Phone{
    public static synchronized void sendSms(){
        try {
            TimeUnit.SECONDS.sleep(3);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        System.out.println("发短信");
    }
    public static synchronized void call(){
        System.out.println("打电话");
    }
}

问题:增加两个静态的同步方法,先执行哪个?

答案是先执行发短信,后是打电话

解释:

  1. synchronize 锁的对象是方法的调用者
  2. static是静态的,在类加载的时候就有了,也就是在Class模板的时候就是是同步方法了,锁的是Class模板

7.5、第六个问题

public class test1 {
    public static void main(String[] args) {
        Phone phone1 = new Phone();
        Phone phone2 = new Phone();
        new Thread(() ->{
            phone1.sendSms();
        }, "A").start();

        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }

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

class Phone{
    public static synchronized void sendSms(){
        try {
            TimeUnit.SECONDS.sleep(3);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        System.out.println("发短信");
    }
    public static synchronized void call(){
        System.out.println("打电话");
    }
}

问题:两个对象,两个静态同步方法,先执行哪个?

还是先执行发短信,再执行打电话

解释:

  • 和上面那个问题差不多,把Class锁上了,所以无论你创建几个对象,几个调用者,用的都是同一把锁,还是谁先获得锁,谁就先执行

7.6、第七个问题

public class test1 {
    public static void main(String[] args) {
        Phone phone = new Phone();
        new Thread(() ->{
            phone.sendSms();
        }, "A").start();

        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }

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

class Phone{
    public static synchronized void sendSms(){
        try {
            TimeUnit.SECONDS.sleep(3);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        System.out.println("发短信");
    }
    public synchronized void call(){
        System.out.println("打电话");
    }
}

问题:一个静态同步方法,一个普通同步方法,一个对象,先执行哪个?

答案是先执行打电话,后执行发短信

解释:

  • 静态的同步方法,锁的就是Class模板
  • 普通的同步方法,锁的就是调用者

7.7、第八个问题

public class test1 {
    public static void main(String[] args) {
        Phone phone1 = new Phone();
        Phone phone2 = new Phone();
        new Thread(() ->{
            phone1.sendSms();
        }, "A").start();

        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }

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

class Phone{
    public static synchronized void sendSms(){
        try {
            TimeUnit.SECONDS.sleep(3);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        System.out.println("发短信");
    }
    public synchronized void call(){
        System.out.println("打电话");
    }
}

问题:一个静态同步方法,一个普通同步方法,两个对象,先执行哪个?

答案是先执行打电话,后执行发短信

解释同上

小结:

  • new this 具体的一个手机
  • static Class 唯一的一个模板

8、集合类不安全

8.1、List不安全

public class test1 {
    public static void main(String[] args) {
        List<String> list = new ArrayList<>();
        for(int i = 1; i <= 10; i++){
            new Thread(() -> {
                list.add(UUID.randomUUID().toString().substring(0,5));
                System.out.println(list);
            }, String.valueOf(i)).start();
        }
    }
}

JUC快速入门_第16张图片

并发下的List是不安全的,这个报错之前在面经里面见到过

快速失败(fail—fast):快速失败是Java集合的一种错误检测机制

  • 在用迭代器遍历一个集合对象时,如果线程A遍历过程中,线程B对集合对象的内容进行了修改(增加、删除、修改),则会抛出Concurrent Modification Exception。

  • 原理:迭代器在遍历时直接访问集合中的内容,并且在遍历过程中使用一个 modCount 变量。集合在被遍历期间如果内容发生变化,就会改变modCount的值。每当迭代器使用hashNext()/next()遍历下一个元素之前,都会检测modCount变量是否为expectedmodCount值,是的话就返回遍历;否则抛出异常,终止遍历。

  • 注意:这里异常的抛出条件是检测到 modCount!=expectedmodCount 这个条件。如果集合发生变化时修改modCount值刚好又设置为了expectedmodCount值,则异常不会抛出。因此,不能依赖于这个异常是否抛出而进行并发操作的编程,这个异常只建议用于检测并发修改的bug。

  • 场景:java.util包下的集合类都是快速失败的,不能在多线程下发生并发修改(迭代过程中被修改),比如ArrayList 类

解决方案:

  1. 使用Vector(不推荐,底层就是用了synchronized)
  2. 使用Collections.synchronizedList(new ArrayList<>())

我大致了解了一下底层,好像是创建了一个线程安全的list,但是在迭代的时候,要用户手动去做同步处理

// 迭代集合元素
synchronized (lists) {
	//获取迭代器
	Iterator<String> iterator = synlist.iterator();
	//遍历
	while (iterator.hasNext()) {	
		System.out.println(iterator.next());
	}
}
  1. CopyOnWriteArrayList:写时复制 COW 计算机程序设计领域的一种优化策略

JUC快速入门_第17张图片

CopyOnWrite比Vector牛在没有使用synchronized
JUC快速入门_第18张图片

8.2、Set不安全

JUC快速入门_第19张图片

public class test2 {
    public static void main(String[] args) {
        Set<Object> set = new HashSet<>();

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

JUC快速入门_第20张图片

解决方法

public class test2 {
    public static void main(String[] args) {
        Set<Object> set = new CopyOnWriteArraySet<>();

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

HashSet的底层是什么?
JUC快速入门_第21张图片

额。。。底层是hashmap

JUC快速入门_第22张图片

add方法也是map的put方法,这里利用了hashmap的key不重复来防止重复

8.3、HashMap不安全

JUC快速入门_第23张图片

public class test3 {
    public static void main(String[] args) {
        // 1、map是这样用的吗?不是,工作中不用HashMap
        // 2、默认等价于什么? new HashMap<>(16, 0.75f);
        // 加载因子和初始化容量
        Map<String, String> map = new HashMap<>();

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

JUC快速入门_第24张图片

解决方法

  1. 使用Collections.synchronizedMap(new HashMap<>())
  2. 使用ConcurrentHashMap

这个在面经里面也见过,但是我觉得我看不懂。。。

9、Callable

开线程的另一种方式

JUC快速入门_第25张图片

  1. 可以有返回值
  2. 可以抛出异常
  3. 方法不同,run()/call()

JUC快速入门_第26张图片

public class test1 {
    public static void main(String[] args) {
        FutureTask<String> stringFutureTask = new FutureTask<>(new MyThread());
        new Thread(stringFutureTask).start();
        try {
            String s = stringFutureTask.get();
            System.out.println(s);

        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        } catch (ExecutionException e) {
            throw new RuntimeException(e);
        }
    }
}

class MyThread implements Callable<String>{
    @Override
    public String call() throws Exception {
        System.out.println("call()");
        return "1024";
    }
}

细节:

  1. 有缓存
  2. 结果可能需要等待,会阻塞!

JUC快速入门_第27张图片

最后就打出来一个

10、常用的辅助类

10.1、CountDownLatch

JUC快速入门_第28张图片

减法计数器

public class Test1 {
    public static void main(String[] args) throws InterruptedException {
        CountDownLatch countDownLatch = new CountDownLatch(6);
        // 总数是6,必须要执行的任务的时候,再使用!
        for (int i = 1; i <= 6; i++) {
            new Thread(() -> {
                System.out.println(Thread.currentThread().getName() + "走了");
                countDownLatch.countDown();

            }, String.valueOf(i)).start();
        }
        countDownLatch.await();
        System.out.println("关门了");
    }
}

原理:

  1. countDownLatch.countDown(); // 数量减一
  2. countDownLathch.await(); // 等待计数器归零,然后再向下执行

每次有线程调用的时候就减一,假设计数器归零,countDownLatch.await()就会被唤醒,继续执行

10.2、CyclicBarrier

JUC快速入门_第29张图片

public class Test1 {
    public static void main(String[] args) throws InterruptedException {
        CountDownLatch countDownLatch = new CountDownLatch(6);
        // 总数是6,必须要执行的任务的时候,再使用!
        for (int i = 1; i <= 6; i++) {
            new Thread(() -> {
                System.out.println(Thread.currentThread().getName() + "走了");
                countDownLatch.countDown();
            }, String.valueOf(i)).start();
        }
        countDownLatch.await();
        System.out.println("关门了");
    }
}

差不多一个意思和CountDownLatch

10.3、Semaphore

JUC快速入门_第30张图片

public class SemaphoretTest {
    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) {
                    throw new RuntimeException(e);
                }finally {
                    semaphore.release();
                }
            }, String.valueOf(i)).start();
        }
    }
}

我一直以为Condition是信号量,原来这个才是。。。

熔断和限流????狂神提到了,spring cloud看来要补一下了

作用:

  1. semaphore.acquire() 获得,假设如果已经满了,等待,等待到被释放为止
  2. semaphore.release() 释放,会将当前的信号量释放+1,然后唤醒等待的线程

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

11、读写锁ReadWriteLock

ReadWriteLock

JUC快速入门_第31张图片

public class ReadWriteLockDemo {
    public static void main(String[] args) {
        MyCache2 myCache = new MyCache2();
        for (int i = 1; i <= 10; i++) {
            final String temp = String.valueOf(i);
            new Thread(() -> {
                myCache.put(temp, temp);
            }, String.valueOf(i)).start();
        }

        for (int i = 1; i <= 10; i++) {
            final String temp = String.valueOf(i);
            new Thread(() -> {
                myCache.get(temp);
            }, String.valueOf(i)).start();
        }
    }
}

class MyCache2{
    private volatile Map<String, Object> map = new HashMap<>();
    // 更加细粒度的控制
    ReadWriteLock lock = new ReentrantReadWriteLock();

    // 存,写入的时候,只希望同时只有一个线程写
    public void put(String key, Object value){
        lock.writeLock().lock();
        try {
            System.out.println(Thread.currentThread().getName() + "写入" + key);
            map.put(key, value);
            System.out.println(Thread.currentThread().getName() + "写入完毕");
        }finally {
            lock.writeLock().unlock();
        }
    }
    // 存的时候,可以多个线程去读
    public void get(String key){
        lock.readLock().lock();
        try {
            System.out.println(Thread.currentThread().getName() + "读取" + key);
            Object o = map.get(key);
        }finally {
            lock.readLock().unlock();
        }
    }
}
  • 独占锁(写锁):一次只能被一个线程占有
  • 共享锁(读锁):一次可以被多个线程占有
  • 读—读 可以共存!
  • 读—写 不可以共存!
  • 写—写 不能共存!

12、阻塞队列BlockingQueue

BlockingQueue

JUC快速入门_第32张图片
JUC快速入门_第33张图片

JUC快速入门_第34张图片
JUC快速入门_第35张图片
JUC快速入门_第36张图片

什么情况下要使用阻塞队列呢?

  1. 多线程并发处理
  2. 线程池

学会使用队列
添加、移除

四组API

方式 抛出异常 不抛出异常,返回值 阻塞等待 超时等待
添加 add offer put offer
移除 remove poll take poll
检测队首元素 element peek - -

抛出异常

public static void test1(){
    // 队列的大小
    ArrayBlockingQueue<String> arrayBlockingQueue = new ArrayBlockingQueue<>(3);

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

    // Exception in thread "main" java.lang.IllegalStateException: Queue full
    // System.out.println(arrayBlockingQueue.add("d"));

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

    // Exception in thread "main" java.util.NoSuchElementException
    System.out.println(arrayBlockingQueue.remove());
}

不抛出异常

public static void test2(){
 ArrayBlockingQueue<String> arrayBlockingQueue = new ArrayBlockingQueue<>(3);

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

    // false
    // System.out.println(arrayBlockingQueue.offer("d"));
    System.out.println(arrayBlockingQueue.poll());
    System.out.println(arrayBlockingQueue.poll());
    System.out.println(arrayBlockingQueue.poll());
    
    // null
    System.out.println(arrayBlockingQueue.poll());
}

等待阻塞(一直等待)

 // 等待 阻塞(一直阻塞)
public static void test3() throws InterruptedException {
    BlockingQueue<String> objects = new ArrayBlockingQueue<>(3);
    objects.put("a");
    objects.put("b");
    objects.put("c");
    System.out.println(objects.take());
    System.out.println(objects.take());
    System.out.println(objects.take());
    System.out.println(objects.take());
}

等待阻塞(等待一会儿)

// 等待 阻塞(就阻塞一会儿)
public static void test4() throws InterruptedException {
    ArrayBlockingQueue<String> strings = new ArrayBlockingQueue<String>(3);

    strings.offer("a");
    strings.offer("b");
    strings.offer("c");
    System.out.println(strings.poll());
    System.out.println(strings.poll());
    System.out.println(strings.poll());
    System.out.println(strings.poll(2, TimeUnit.SECONDS));
}

13、同步队列SynchronousQueue

SynchronousQueue同步队列

没有容量,进去一个元素,必须等待取出来之后,才能再往里面放一个元素

public class sbqtest {
    public static void main(String[] args) throws InterruptedException {
        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) {
                throw new RuntimeException(e);
            }
        }, "T1").start();
        new Thread(() -> {
            try {
                TimeUnit.SECONDS.sleep(3);
                System.out.println(Thread.currentThread().getName() + " " + blockingQueue.take());
                TimeUnit.SECONDS.sleep(3);
                System.out.println(Thread.currentThread().getName() + " " + blockingQueue.take());
                TimeUnit.SECONDS.sleep(3);
                System.out.println(Thread.currentThread().getName() + " " + blockingQueue.take());
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }, "T2").start();
    }
}

14、线程池(重点)

池化技术

程序的运行,本质:占用系统的资源!优化资源的使用 =》池化技术

线程池、连接池、内存池、对象池 //

池化技术:事先准备好一些资源,有人要用,就来我这里拿,用完之后还给我

线程池的好处:

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

线程复用,可以控制最大的并发数,管理线程

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

14.1、三大方法

JUC快速入门_第37张图片

public class Demo01 {
    public static void main(String[] args) {
//        ExecutorService executorService = Executors.newSingleThreadExecutor();// 单个线程
//        ExecutorService executorService = Executors.newFixedThreadPool(5);// 创建一个固定的线程池的大小
        ExecutorService executorService = Executors.newCachedThreadPool();// 遇强则强,遇弱则弱
        try{

            for (int i = 1; i <= 10; i++) {
                executorService.execute(()->{
                    System.out.println(Thread.currentThread().getName() + "ok");
                });
            }
        }finally {
            // 线程池用完,要及时的关闭
            executorService.shutdown();
        }
    }
}
  • ExecutorService executorService = Executors.newSingleThreadExecutor();// 单个线程
  • ExecutorService executorService = Executors.newFixedThreadPool(5);// 创建一个固定的线程池的大小
  • ExecutorService executorService = Executors.newCachedThreadPool();// 遇强则强,遇弱则弱

14.2、七大参数

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

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

public static ExecutorService newCachedThreadPool() {
  return new ThreadPoolExecutor(0, Integer.MAX_VALUE // 这个小21亿,
                                  60L, TimeUnit.SECONDS,
                                  new SynchronousQueue<Runnable>());
}

ThreadPoolExecutor:七大参数就在这里

public ThreadPoolExecutor(int corePoolSize, // 核心线程大小
                          int maximumPoolSize, // 最大核心线程池大小
                          long keepAliveTime, // 超时了,没有人调用就会释放
                          TimeUnit unit, // 超时的单位
                          BlockingQueue<Runnable> workQueue, // 阻塞队列
                          ThreadFactory threadFactory, // 创建线程的工厂
                          RejectedExecutionHandler handler // 拒绝策略) {
    if (corePoolSize < 0 ||
        maximumPoolSize <= 0 ||
        maximumPoolSize < corePoolSize ||
        keepAliveTime < 0)
        throw new IllegalArgumentException();
    if (workQueue == null || threadFactory == null || handler == null)
        throw new NullPointerException();
    this.corePoolSize = corePoolSize;
    this.maximumPoolSize = maximumPoolSize;
    this.workQueue = workQueue;
    this.keepAliveTime = unit.toNanos(keepAliveTime);
    this.threadFactory = threadFactory;
    this.handler = handler;
}

JUC快速入门_第38张图片> 所以这里的阿里巴巴手册才建议你不要使用Executors去创建线程池

JUC快速入门_第39张图片

类比银行排队图

手动实现一个线程池

public class Demo01 {
    public static void main(String[] args) {
        ExecutorService executorService = new ThreadPoolExecutor(
                2,
                5,
                3,
                TimeUnit.SECONDS,
                new LinkedBlockingQueue<>(3),
                Executors.defaultThreadFactory(),
                //new ThreadPoolExecutor.AbortPolicy() // 银行满了,还有人进来,不处理这个人的,抛出异常
                // new ThreadPoolExecutor.CallerRunsPolicy() // 那里来的回哪里去
                /// new ThreadPoolExecutor.DiscardPolicy() // 队列满了,丢掉任务,不会抛出异常
                new ThreadPoolExecutor.DiscardOldestPolicy() // 队列满了,尝试和最早的去竞争,也不会抛出异常
        );
        try{
            // 最大承载: 队列长度 + max
            for (int i = 1; i <= 9; i++) {
                executorService.execute(()->{
                    System.out.println(Thread.currentThread().getName() + "ok");
                });
            }
        }finally {
            // 线程池用完,要及时的关闭
            executorService.shutdown();
        }
    }
}
参数 作用
corePoolSize 核心线程大小
maximumPoolSize 最大核心线程大小
keepAliveTime 超时了,没有人调用就会释放
第四个 超时时间单位
第五个 阻塞队列
第六个 创建线程的工厂
第七个 拒绝策略(详见下方)

14.3、四种拒绝策略

JUC快速入门_第40张图片

new ThreadPoolExecutor.AbortPolicy() // 银行满了,还有人进来,不处理这个人的,抛出异常
 
new ThreadPoolExecutor.CallerRunsPolicy() // 那里来的回哪里去

new ThreadPoolExecutor.DiscardPolicy() // 队列满了,丢掉任务,不会抛出异常

new ThreadPoolExecutor.DiscardOldestPolicy() // 队列满了,尝试和最早的去竞争,也不会抛出异常

15、CPU密集型和IO密集型

  • CPU密集型:几核,就是几,可以保持CPU的效率最高
  • IO密集型:判断你程序中十分耗IO的线程(可以设置大于两倍)
public class Demo01 {
    public static void main(String[] args) {
        ExecutorService executorService = new ThreadPoolExecutor(
                2,
                Runtime.getRuntime().availableProcessors(),
                3,
                TimeUnit.SECONDS,
                new LinkedBlockingQueue<>(3),
                Executors.defaultThreadFactory(),
                new ThreadPoolExecutor.DiscardOldestPolicy() 
        );
        try{
            // 最大承载: 队列长度 + max
            for (int i = 1; i <= 9; i++) {
                executorService.execute(()->{
                    System.out.println(Thread.currentThread().getName() + "ok");
                });
            }
        }finally {
            // 线程池用完,要及时的关闭
            executorService.shutdown();
        }
    }
}

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

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

@FunctionalInterface
public interface Runnable {
    public abstract void run();
}

// 超级多的FunctionInterface
// 简化编程模型,在新版本的框架底层大量应用
// foreach(消费者的函数式接口)

JUC快速入门_第41张图片

16.1、Function函数式接口

JUC快速入门_第42张图片

/**
 * Function 函数式接口,只有一个输入参数,有一个输出
 * 只要是 函数式接口 可以 用lambda表达式简化
 */
public class Demo01 {
    public static void main(String[] args) {

//        Function function = new Function() {
//            @Override
//            public String apply(String s) {
//                return s;
//            }
//        };
        Function function = (str) -> {return  str;};
        System.out.println(function.apply("afds"));
    }
}

16.2、Predicate断定型接口

JUC快速入门_第43张图片

public class Demo02 {
    public static void main(String[] args) {
        // 判断传入字符串是否为空
//        Predicate predicate = new Predicate() {
//            @Override
//            public boolean test(String s) {
//                return "".equals(s);
//            }
//        };
        // Predicate predicate = ""::equals;
        Predicate predicate = (str)->{return "".equals(str)};
        System.out.println(predicate.test(""));
    }
}

16.3、Consumer消费型接口

JUC快速入门_第44张图片

public class Demo033 {
    /// Consumer 消费性接口: 只有输入,没有返回值
    public static void main(String[] args) {
//        Consumer consumer = new Consumer() {
//            @Override
//            public void accept(String s) {
//                System.out.println(s);
//            }
//        };
        // Consumer consumer = System.out::println;
        Consumer consumer = (str) -> {
            System.out.println(str);
        };
        consumer.accept("afdsf ");
    }
}

16.4、Supplier供给型接口

JUC快速入门_第45张图片

public class Demo04 {
    public static void main(String[] args) {
//        Supplier supplier = new Supplier() {
//            @Override
//            public String get() {
//                return "get到了";
//            }
//        };
        Supplier supplier = () -> {return "get";};
        System.out.println(supplier.get());
    }
}

17、Stream流式计算

JUC快速入门_第46张图片

public class Demo01 {
    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> users = Arrays.asList(u1, u2, u3, u4, u5);
        users.stream()
                .filter(user ->{return user.getId() % 2 == 0;})
                .filter(user -> {return user.getAge() > 23;})
                .map(user -> {return user.getName().toUpperCase();})
                .sorted((o1, o2) -> {return o2.compareTo(o1);})
                .limit(1)
                .forEach(System.out::println);
    }
}

18、ForkJoin

什么是ForkJon

ForkJoin在JDK1.7,并行执行任务,提高效率,大数据量。

在这里插入图片描述

ForkJoin工作窃取

这里面维护的都是双端队列
JUC快速入门_第47张图片

比如第二个线程跑完任务了,第一个还没有跑完,第二个就把第一个的任务偷过来帮他执行

在这里插入图片描述

JUC快速入门_第48张图片

public class test {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        // test1(); // 5617
        // test2(); // 4139
        test3(); // 152
    }

    // 普通程序员 3000
    public static void test1(){
        long start = System.currentTimeMillis();
        Long sum = 0L;
        for (Long i = 1L; i <= 10_0000_0000L; i++) {
            sum += i;
        }
        long end = System.currentTimeMillis();
        System.out.println("sum = " + sum + "  时间:" + (end - start));
    }

    // 使用forkjoin 6000 
    public static void test2() throws ExecutionException, InterruptedException {
        long start = System.currentTimeMillis();
        ForkJoinPool forkJoinPool = new ForkJoinPool();
        ForkJoinTask<Long> demo01 = new Demo01(0L, 10_0000_0000L);
        ForkJoinTask<Long> submit = forkJoinPool.submit(demo01);

        Long aLong = submit.get();

        long end = System.currentTimeMillis();
        System.out.println("sum = " + aLong + "  时间:" + (end - start));
    }

    // Stream流计算 9000
    public static void test3(){
        long start = System.currentTimeMillis();
        long reduce = LongStream.rangeClosed(0L, 10_0000_0000L).parallel().reduce(0, Long::sum);
        long end = System.currentTimeMillis();
        System.out.println("sum = " + reduce + "  时间:" + (end - start));
    }
}

19、异步回调

Future设计的初衷:对将来的某个事务的结果进行建模

JUC快速入门_第49张图片

public class Demo01 {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        // 没有返回值的runAsync 异步回调
//        CompletableFuture completableFuture = CompletableFuture.runAsync(()->{
//            try {
//                TimeUnit.SECONDS.sleep(2);
//            } catch (InterruptedException e) {
//                throw new RuntimeException(e);
//            }
//            System.out.println(Thread.currentThread().getName()  + "runAsync");
//        });
//
//        System.out.println("1111");
//        completableFuture.get();

        // 有返回值的supplyAsync 异步回调
        CompletableFuture<Integer> completableFuture = CompletableFuture.supplyAsync(() -> {
            System.out.println(Thread.currentThread().getName() + "   ");
            int i = 10 / 0;
            return 1024;
        });
        System.out.println(completableFuture.whenComplete((t, u) -> {
            System.out.println("t => " + t);
            System.out.println("u => " + u);
        }).exceptionally((e) -> {
            System.out.println(e.getMessage());
            return 233;
        }).get());
    }
}

20、JMM

请你谈谈你对Volatile的理解

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

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

什么是JMM

JMM:Java内存模型,不存在的东西,概念!约定

关于JMM的一些同步的约定

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

线程中 工作内存、主内存

8种操作:

JUC快速入门_第50张图片

JUC快速入门_第51张图片

问题:程序不知道主内存中的值已经别修改过了

public class Demo01 {
    static int num = 0;
    public static void main(String[] args) {
        // 线程A中工作内存中的num是0
        new Thread(() -> {
            while (num == 0) {
            }
        }, "A").start();
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }

        // main线程修改了主内存中的num,但是此时A线程中还有的旧的num,不知道num已经发生了变化
        num = 1;
        System.out.println(num);
    }
}

JUC快速入门_第52张图片

21、Volatile

  • 保证可见性
public class Demo01 {
    static volatile int num = 0; // 加了volatile 可见性了
    public static void main(String[] args) {
        new Thread(() -> {
            while (num == 0) {
            }
        }, "A").start();
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        num = 1;
        System.out.println(num);
    }
}
  • 不保证原子性

原子性:不可分割

线程A在执行任务的时候,不能被打扰的,也不能被分割。要么同时成功,要么同时失败

public class Demo02 {
    static volatile int num = 0;
    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){
            Thread.yield();
        }

        System.out.println(Thread.currentThread().getName() + " " + num);
    }
    public static void add(){
        num++;
    }
}

在这里插入图片描述

如果不加Lock和synchronized,怎么保证原子性

JUC快速入门_第53张图片
使用原子类

JUC快速入门_第54张图片

public class Demo02 {
    static volatile AtomicInteger num = new AtomicInteger();
    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){
            Thread.yield();
        }

        System.out.println(Thread.currentThread().getName() + " " + num);
    }
    public static void add(){
        num.getAndIncrement();
    }
}

这些类的底层都是直接和操作系统挂钩。在内存中修改值!

  • 指令重排

什么是 指令重排 :你写的程序,计算机并不是按照你写的那样去执行的

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

处理器在进行指令重排的时候,考虑:数据的依赖性!

x = 0, y = 0, a = 0, b = 0

线程A 线程B
x = a y = b
b = 1 a = 2

正常结果:x = 0, y = 0,但是可能由于指令重排

线程A 线程B
b = 1 a = 2
x = a y = b

x = 2, y = 1

加了volatile可以避免指令重排:
内存屏障。CPU指令。作用

1、保证特定的操作的执行顺序!
2、可以保证某些变量的内存可见性(利用了这些特性的volatile实现了可见性)

JUC快速入门_第55张图片
Volatile是可以保持可见性。不能保证原子性,由于内存屏障,可以保证避免指令重排的现象产生!

22、单例模式

饿汉式、DCL懒汉式、深究!

懒汉式!

public class LazyMan {
    private LazyMan(){
        System.out.println(Thread.currentThread().getName() + "ok");
    }
    private volatile static LazyMan lazyMan;
    public static LazyMan getInstance(){
        // 双重检测锁模式的 懒汉式单例 DCL懒汉式
        if(lazyMan == null){
            synchronized (LazyMan.class){
                if(lazyMan == null){
                    lazyMan = new LazyMan(); // 不是一个原子性操作
                    /**
                     * 1、分配内存空间
                     * 2、执行构造方法,初始化对象
                     * 3、把这个对象指向这个空间
                     */
                }
            }
        }
        return lazyMan;
    }
    public static void main(String[] args) {
        for (int i = 0; i < 10; i++) {
            new Thread(()->{
                LazyMan.getInstance();
            }).start();
        }
    }
}

22.1、第一次破坏和恢复

使用反射破坏双重检测锁的模式的单例

public class LazyMan {
    private LazyMan(){
        System.out.println(Thread.currentThread().getName() + "ok");
    }
    private volatile static LazyMan lazyMan;
    public static LazyMan getInstance(){
        // 双重检测锁模式的 懒汉式单例 DCL懒汉式
        if(lazyMan == null){
            synchronized (LazyMan.class){
                if(lazyMan == null){
                    lazyMan = new LazyMan(); // 不是一个原子性操作
                    /**
                     * 1、分配内存空间
                     * 2、执行构造方法,初始化对象
                     * 3、把这个对象指向这个空间
                     */
                }
            }
        }
        return lazyMan;
    }
    public static void main(String[] args) throws Exception {
        LazyMan instance = LazyMan.getInstance();
        Constructor<LazyMan> constructor = LazyMan.class.getDeclaredConstructor(null);
        constructor.setAccessible(true);
        LazyMan lazyMan1 = constructor.newInstance();
        System.out.println(instance);
        System.out.println(lazyMan1);
    }
}

JUC快速入门_第56张图片

使用三重检验

public class LazyMan {
    private LazyMan(){
        // 这里在加一个
        synchronized (LazyMan.class){
            if (lazyMan != null){
                throw new RuntimeException("不要试图使用反射破坏异常");
            }
        }
        System.out.println(Thread.currentThread().getName() + "ok");
    }
    // 双重检测锁加原子性操作	
    private volatile static LazyMan lazyMan;
    public static LazyMan getInstance(){
        // 双重检测锁模式的 懒汉式单例 DCL懒汉式
        if(lazyMan == null){
            synchronized (LazyMan.class){
                if(lazyMan == null){
                    lazyMan = new LazyMan(); // 不是一个原子性操作
                    /**
                     * 1、分配内存空间
                     * 2、执行构造方法,初始化对象
                     * 3、把这个对象指向这个空间
                     */
                }
            }
        }
        return lazyMan;
    }
    public static void main(String[] args) throws Exception {
        LazyMan instance = LazyMan.getInstance();
        Constructor<LazyMan> constructor = LazyMan.class.getDeclaredConstructor(null);
        constructor.setAccessible(true);
        LazyMan lazyMan1 = constructor.newInstance();

        System.out.println(instance);
        System.out.println(lazyMan1);
    }
}

JUC快速入门_第57张图片

22.2、第二次破坏和恢复

但是如果用两个反射的话,还是破坏了单例结构

JUC快速入门_第58张图片

新加一个红绿灯检测

public class LazyMan {
    // 新增一个红绿灯判断
    private static boolean qinjiang = false;

    private LazyMan(){
        // 这里在加一个
        synchronized (LazyMan.class){
            if(qinjiang == false){
                qinjiang = true;
            }else {
                throw new RuntimeException("不要试图使用反射破坏异常");
            }
        }
        System.out.println(Thread.currentThread().getName() + "ok");
    }
    private volatile static LazyMan lazyMan;
    public static LazyMan getInstance(){
        // 双重检测锁模式的 懒汉式单例 DCL懒汉式
        if(lazyMan == null){
            synchronized (LazyMan.class){
                if(lazyMan == null){
                    lazyMan = new LazyMan(); // 不是一个原子性操作
                    /**
                     * 1、分配内存空间
                     * 2、执行构造方法,初始化对象
                     * 3、把这个对象指向这个空间
                     */
                }
            }
        }
        return lazyMan;
    }
    public static void main(String[] args) throws Exception {
        // LazyMan instance = LazyMan.getInstance();
        Constructor<LazyMan> constructor = LazyMan.class.getDeclaredConstructor(null);
        constructor.setAccessible(true);
        LazyMan lazyMan1 = constructor.newInstance();
        LazyMan instance = constructor.newInstance();
        System.out.println(instance);
        System.out.println(lazyMan1);
    }
}

这样的话就又好了

22.3、第三次破坏

如果得到了红绿灯变量名的话

public class LazyMan {
    // 新增一个红绿灯判断
    private static boolean qinjiang = false;

    private LazyMan(){
        // 这里在加一个
        synchronized (LazyMan.class){
            if(qinjiang == false){
                qinjiang = true;
            }else {
                throw new RuntimeException("不要试图使用反射破坏异常");
            }
        }
        System.out.println(Thread.currentThread().getName() + "ok");
    }
    private volatile static LazyMan lazyMan;
    public static LazyMan getInstance(){
        // 双重检测锁模式的 懒汉式单例 DCL懒汉式
        if(lazyMan == null){
            synchronized (LazyMan.class){
                if(lazyMan == null){
                    lazyMan = new LazyMan(); // 不是一个原子性操作
                    /**
                     * 1、分配内存空间
                     * 2、执行构造方法,初始化对象
                     * 3、把这个对象指向这个空间
                     */
                }
            }
        }
        return lazyMan;
    }
    public static void main(String[] args) throws Exception {
        Field qinjiang1 = LazyMan.class.getDeclaredField("qinjiang");
        qinjiang1.setAccessible(true);
        
        // LazyMan instance = LazyMan.getInstance();
        Constructor<LazyMan> constructor = LazyMan.class.getDeclaredConstructor(null);
        constructor.setAccessible(true);
        
        LazyMan instance = constructor.newInstance();
        qinjiang1.set(instance, false);
        
        LazyMan lazyMan1 = constructor.newInstance();
        System.out.println(instance);
        System.out.println(lazyMan1);
    }
}

22.4、反射无法破坏枚举的单例

public enum Enumtest {
    INSTANCE;

    public Enumtest getInstance(){
        return INSTANCE;
    }
}

class Test{
    public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
        Enumtest instance = Enumtest.INSTANCE;
        Constructor<Enumtest> declaredConstructor = Enumtest.class.getDeclaredConstructor(String.class, int.class);
        declaredConstructor.setAccessible(true);
        Enumtest enumtest = declaredConstructor.newInstance();

        System.out.println(enumtest);
        System.out.println(instance);
    }
}

JUC快速入门_第59张图片

枚举类型的最终反编译源码

23、深入理解CAS

什么是CAS

大厂你必须要深入研究底层!有所突破!

public class CASDemo {
    // CAS compareAndSet:比较并交换
    public static void main(String[] args) {
        AtomicInteger atomicInteger = new AtomicInteger(2020);

        // 期望、更新
        // public final boolean compareAndSet(int expert, int update)
        // 如果我期望的值达到了,那么就更新,否则,就不更新,CAS是CPU的并发原理
        System.out.println(atomicInteger.compareAndSet(2020, 2021));
        System.out.println(atomicInteger.get());

        System.out.println(atomicInteger.compareAndSet(2020, 2021));
        System.out.println(atomicInteger.get());
    }
}

Unsafe类

JUC快速入门_第60张图片

原子类加一操作的底层是调用了unsafe的方法,偏向于底层
JUC快速入门_第61张图片

JUC快速入门_第62张图片

  • 看到了这里,从视频中就会发现这个原子类的compareAndSet底层调用这个方法

在这里插入图片描述
在这里插入图片描述

  • 后来看原子类getAndIncrement的方法

JUC快速入门_第63张图片
JUC快速入门_第64张图片
在这里插入图片描述

反正这个也就是证明了这个原子类的底层用的就是CAS,就是效率高,然后带锁,很牛b的样子,

JUC快速入门_第65张图片

CAS:比较当前工作中的值和主内存中的值,如果这个值是期望的,那么执行操作,如果不是就一直循环

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

CAS:ABA问题(狸猫换太子)

带版本的号的原子操作
JUC快速入门_第66张图片

JUC快速入门_第67张图片

public class CASDemo {
    // AtomicStampedReference注意,如果泛型是包装类,注意对象的引用问题
    static AtomicStampedReference<Integer> atomicStampedReference = new AtomicStampedReference<>(1, 1);
    public static void main(String[] args) {
        new Thread(()->{
            int stamp = atomicStampedReference.getStamp();
            System.out.println("a1 => " + stamp);

            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }

            System.out.println(atomicStampedReference.compareAndSet(1, 2, atomicStampedReference.getStamp(), atomicStampedReference.getStamp() + 1));

            System.out.println("a2 => " + atomicStampedReference.getStamp());

            System.out.println(atomicStampedReference.compareAndSet(2, 1, atomicStampedReference.getStamp(), atomicStampedReference.getStamp() + 1));

            System.out.println("a3 => " + atomicStampedReference.getStamp());
        }, "a").start();

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

            try {
                TimeUnit.SECONDS.sleep(2);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }

            System.out.println(atomicStampedReference.compareAndSet(1, 3, stamp, stamp + 1));
            System.out.println("b2 => " + atomicStampedReference.getStamp());

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

JUC快速入门_第68张图片

线程a和b执行,a中等待一秒,b中等待两秒,所以a中先将时间戳给修改了,然后b中要执行ACS的时候,发现期待的时间戳不一致,就会发生CAS失败了

JUC快速入门_第69张图片

24、各种锁的理解

24.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();
}

24.2、可重入锁

可重入锁(递归锁)

public class Demo01 {
    public static void main(String[] args) {
        Phone phone = new Phone();
        new Thread(()-> {
            phone.sms();
        }, "A").start();

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

class Phone{
    public synchronized void sms(){
        System.out.println(Thread.currentThread().getName() + "sms");
        call();
    }

    public synchronized void call(){
        System.out.println(Thread.currentThread().getName() + "call");
    }
}

JUC快速入门_第70张图片

结果总是这样的,就是谁先拿到锁,然后会拿到这个锁里面锁的东西,直到嘴里面的执行完后,才依次解锁,这样才轮到了B

Lock版本(注意lock配对

public class Demo02 {

    public static void main(String[] args) {
        Phone1 phone = new Phone1();
        new Thread(()-> {
            phone.sms();
        }, "A").start();

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

}

class Phone1{
    Lock lock = new ReentrantLock();
    public void sms(){
        // 锁必须要配对,否则就会死在里面
        lock.lock();
        try {
            System.out.println(Thread.currentThread().getName() + "sms");
            call();
        }finally {
            lock.unlock();
        }
    }

    public void call(){
        lock.lock();
        try {
            System.out.println(Thread.currentThread().getName() + "call");
        }finally {
            lock.unlock();
        }
    }
}

24.3、自旋锁

SpinLock

JUC快速入门_第71张图片

public class SpinlockDemo {
    static 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);
    }
}

public class TestSpinLock {
    public static void main(String[] args) throws InterruptedException {
        SpinlockDemo lock = new SpinlockDemo();

        new Thread(()->{
            lock.myLock();
            try {
                try {
                    TimeUnit.SECONDS.sleep(3);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }finally {
                lock.myUnLock();
            }
        }, "A").start();

        TimeUnit.SECONDS.sleep(1);

        new Thread(()->{
            lock.myLock();
            try {
                try {
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }finally {
                lock.myUnLock();
            }
        }, "B").start();
    }
}

自旋锁例子,实现了一个自旋锁,测试中,搞两个线程去获取这个锁,一个等待3秒,一个等待1秒,A线程先拿到了锁,然后将null变成了当前的线程,然后B来获取锁的时候,那个CAS操作就会一直返回false,就会陷入一个死循环,当线程A执行完后,释放了锁,就是将thread变成了null,线程B才会将null变成当前B线程,才可以真正的拿到锁,用完了释放即可

25、死锁

什么是死锁

JUC快速入门_第72张图片

public class DeadLockTest {
    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) {
                throw new RuntimeException(e);
            }
            synchronized (lockB){
                System.out.println(Thread.currentThread().getName() + "lock:" + lockB + "=>get" + lockA);
            }
        }
    }
}

解决办法:
1、使用jps -l,定位进程号

JUC快速入门_第73张图片
2、使用jstack 进行号找到死锁问题
JUC快速入门_第74张图片

你可能感兴趣的:(JUC,jvm,java,开发语言)