R-JUC(java并发包)入门文档 2021年10月13日 至 2021年11月01日

java内力内力篇总纲

JDK1.8 官网在线文档,学习跟着看

目录

1、何为JUC
2、Lock锁(接口)
3、生产者和消费者
4、线程八锁(八个问题,四组)
5、集合类(安全与不安全)
6、Callable
7、CountDownLatch、CyclicBarrier、Semaphore
8、读写锁(ReadWriteLock)
9、阻塞队列(FIFO 先进先出)
10、线程池()
11、四大函数式接口
12、Stream流式计算
13、分之合并(ForkJoin)
14、异步回调
15、JMM
16、volatile
17、深入单例模式
18、CAS(比较并交换)
19、原子引用(解决ABA问题)
20、可重入锁、自旋锁、死锁、公平锁、非公平锁....

一、何为JUC

JDK并发工具类是JDK1.5引入的一大重要的功能,集中在Java.util.concurrent包下。java.util.concurrent包主要包含了并发集合类以及线程池和信号量三组重要工具类。

二、Lock 接口

1、实现类:


image.png

2、官网提供的使用方式:


image.png

3、ReentrantLock


image.png
  • 公平锁:十分公平,可以先来后到
  • 非公平锁:十分不公平,可以插队(根据CPU调度优化算法)

4、使用 demo

// lock 三部曲
// 1 new ReentantLock
// 2 lock.lock() 加锁
// 3 finally -> lock.unlock() 解锁
class TicketTest {
    // 属性、方法
    private int number = 30;

    private Lock lock = new ReentrantLock ( );

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

        try {
            // 业务代码------------
            if (number > 0) {
                System.out.println (Thread.currentThread ( ).getName ( ) + ":" + (++number));
            }
        } catch (Exception e) {
            e.printStackTrace ( );
        } finally {
            lock.unlock ( );
        }
    }

    // 测试
    public static void main(String[] args) {
        TicketTest ticket = new TicketTest ( );

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

5、Synchronized 与 Lock 区别

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

三、生产者和消费者

1、Synchronized 版本(1.0,导致虚假唤醒)

package com.kk.pc;

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) {
                    e.printStackTrace ( );
                }
            }
        },"A").start ();

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


// 判断等待、业务、通知
class Data{// 数字 资源类
    private int number = 0;

    // +1
    public synchronized void increment() throws InterruptedException{
        if (number!=0){
            // 等待
            this.wait ();
        }
        number++;
        System.out.println (Thread.currentThread ().getName ()+"=>"+number );
        // 通知其他线程,我 +1 完毕
        this.notifyAll ();
    }
    // -1
    public synchronized void decrement() throws InterruptedException{
        if (number==0){
            // 等待
            this.wait ();
        }
        number--;
        System.out.println (Thread.currentThread ().getName ()+"=>"+number );
        // 通知其他线程,我 -1 完毕
        this.notifyAll ();
    }
}

2、虚假唤醒(跑两个线程,一个生产一个消费没问题;但是两个以上,IF 就会出现问题,必须使用 white 循环防止中断)


image.png

只需要把 以上代码的 IF 改为 while

image.png

3、JUC版本

通过 lock 找到 Condition

image.png
package com.kk.pc;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class A {
    public static void main(String[] args) {

        Data data = new Data ( );
        new Thread (() -> {
            for (int i = 0; i < 10; i++) {
                data.increment ( );
            }
        }, "A").start ( );

        new Thread (() -> {
            for (int i = 0; i < 10; i++) {
                data.decrement ( );
            }
        }, "B").start ( );
        new Thread (() -> {
            for (int i = 0; i < 10; i++) {
                data.increment ( );
            }
        }, "C").start ( );

        new Thread (() -> {
            for (int i = 0; i < 10; i++) {
                data.decrement ( );
            }
        }, "D").start ( );
    }
}


// 判断等待、业务、通知
class Data {// 数字 资源类
    private int number = 0;

    private Lock lock = new ReentrantLock ( );
    Condition condition = lock.newCondition ( );// 监视器

    //condition.await ();      等待
    //condition.signalAll ();  唤醒全部

    // +1
    public void increment() {
        lock.lock ( );// 上锁
        try {
            while (number != 0) {
                // 等待
                condition.await ( );
            }
            number++;
            System.out.println (Thread.currentThread ( ).getName ( ) + "=>" + number);
            // 通知其他线程,我 +1 完毕
            condition.signalAll ( );
        } catch (InterruptedException e) {
            e.printStackTrace ( );
        } finally {
            lock.unlock ( );// 解锁
        }
    }

    // -1
    public void decrement() {
        lock.lock ( );// 上锁
        try {
            while (number == 0) {
                // 等待
                condition.await ( );
            }
            number--;
            System.out.println (Thread.currentThread ( ).getName ( ) + "=>" + number);
            // 通知其他线程,我 -1 完毕
            condition.signalAll ( );
        } catch (InterruptedException e) {
            e.printStackTrace ( );
        } finally {
            lock.unlock ( );// 解锁
        }
    }
}



4、两个版本的使用区别


image.png

5、 Condition 精准的通知和唤醒线程(既 有序执行线程)

有序执行: A->B->C

package com.kk.pc;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class A {
    public static void main(String[] args) {

        Data data = new Data ( );
        new Thread (() -> {
            for (int i = 0; i < 10; i++) {
                data.printA ( );
            }
        }, "A").start ( );

        new Thread (() -> {
            for (int i = 0; i < 10; i++) {
                data.printB ( );
            }
        }, "B").start ( );
        new Thread (() -> {
            for (int i = 0; i < 10; i++) {
                data.printC ( );
            }
        }, "C").start ( );

    }
}


// 判断等待、业务、通知
class Data {// 数字 资源类
    private int number = 1;// 1A  2B 3C

    private Lock lock = new ReentrantLock ( );
    Condition condition1 = lock.newCondition ( );// 监视器1
    Condition condition2 = lock.newCondition ( );// 监视器2
    Condition condition3 = lock.newCondition ( );// 监视器3

    public void printA () {
        lock.lock ( );// 上锁
        try {
            // 业务,判断 -> 执行 -> 通知
            while (number != 1) {
                // 等待
                condition1.await ( );
            }
            System.out.println (Thread.currentThread ( ).getName ( ) + "=>" + "AAAAAA");
            // 唤醒指定线程 B
            number=2;
            condition2.signal ();
        } catch (InterruptedException e) {
            e.printStackTrace ( );
        } finally {
            lock.unlock ( );// 解锁
        }
    }

    public void printB () {
        lock.lock ( );// 上锁
        try {
            // 业务,判断 -> 执行 -> 通知
            while (number != 2) {
                // 等待
                condition2.await ( );
            }
            System.out.println (Thread.currentThread ( ).getName ( ) + "=>" + "BBBBBB");
            // 唤醒指定线程 B
            number=3;
            condition3.signal ();
        } catch (InterruptedException e) {
            e.printStackTrace ( );
        } finally {
            lock.unlock ( );// 解锁
        }
    }

    public void printC () {
        lock.lock ( );// 上锁
        try {
            // 业务,判断 -> 执行 -> 通知
            while (number != 3) {
                // 等待
                condition3.await ( );
            }
            System.out.println (Thread.currentThread ( ).getName ( ) + "=>" + "CCCCCC");
            // 唤醒指定线程 B
            number=1;
            condition1.signal ();
        } catch (InterruptedException e) {
            e.printStackTrace ( );
        } finally {
            lock.unlock ( );// 解锁
        }
    }
}



image.png

四、八锁的现象( 八个问题,四组)

问题一:标准情况下:两个线程(先发短信/先打电话)? 先发短信,后打电话

package com.kk.Lock8;
import java.util.concurrent.TimeUnit;
// 问题一:标准情况下:两个线程(先发短信/先打电话)? 先发短信,后打电话
public class Test {
    public static void main(String[] args) {
        Phone phone = new Phone ( );

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

        // 睡眠一秒
        try {
            TimeUnit.SECONDS.sleep (1);
        } catch (InterruptedException e) {
            e.printStackTrace ( );
        }

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

    public synchronized void sendSms(){
        System.out.println ("发短信" );
    }

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

问题二 sendSms延迟3秒 :标准情况下:两个线程(先发短信/先打电话)?

package com.kk.Lock8;
import java.util.concurrent.TimeUnit;
// 问题一:标准情况下:两个线程(先发短信/先打电话)? 先发短信,后打电话
// 问题二 sendSms延迟3秒 :标准情况下:两个线程(先发短信/先打电话)? 先发短信,后打电话
// 两个问题的分析:首先要明确,不是因为 sendSms先调用而先运行,是 sendSms先拿到锁才线先运行
//而两线程持有的是通一把锁,所以 call 等待,两个是异步,如果加 main是三个 
public class Test {
    public static void main(String[] args) {
        Phone phone = new Phone ( );

        new Thread(()->{phone.sendSms ();},"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 sendSms(){
        // 睡眠三秒
        try {
            TimeUnit.SECONDS.sleep (3);
        } catch (InterruptedException e) {
            e.printStackTrace ( );
        }
        System.out.println ("发短信" );
    }

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

// 问题三:标准情况下:两个线程(先发短信/先打电话)?

package com.kk.Lock8;
import java.util.concurrent.TimeUnit;
// 问题三:标准情况下:两个线程(先发短信/先打电话)? 先打印 hello,后打电话
public class Test {
    public static void main(String[] args) {
        Phone phone = new Phone ( );

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

        // 睡眠 1 秒(main 调用顺序睡眠 1 秒)
        try {
            TimeUnit.SECONDS.sleep (1);
        } catch (InterruptedException e) {
            e.printStackTrace ( );
        }

        new Thread(()->{phone.hello ();},"B").start ();
    }
}
class Phone{
    // synchronized 锁的对象是方法的调用者 phone 实例对象
    public synchronized void sendSms(){
        // 睡眠 2 秒(sendSms 前睡眠 2 秒)
        try {
            TimeUnit.SECONDS.sleep (2);
        } catch (InterruptedException e) {
            e.printStackTrace ( );
        }
        System.out.println ("发短信" );
    }

    public synchronized void call(){
        System.out.println ("打电话" );
    }
    // 没有锁,不受锁的影响
    public  void hello(){
        System.out.println ("hello" );
    }
}

问题四:两个对象锁,两个同步方法(先发短信/先打电话)?

package com.kk.Lock8;
import java.util.concurrent.TimeUnit;
// 问题三:标准情况下:两个线程(先发短信/先打电话)? 先打印 hello,后打电话
// 问题四:两个对象锁,两个同步方法(先发短信/先打电话)? 打电话(看设的延迟,但两个线程不相互影响)
public class Test {
    public static void main(String[] args) {
        Phone phone = new Phone ( );
        Phone phone2 = new Phone ( );

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

        // 睡眠 1 秒(main 调用顺序睡眠 1 秒)
        try {
            TimeUnit.SECONDS.sleep (1);
        } catch (InterruptedException e) {
            e.printStackTrace ( );
        }

        new Thread(()->{phone2.call ();},"B").start ();
    }
}
class Phone{
    // synchronized 锁的对象是方法的调用者 phone 实例对象
    public synchronized void sendSms(){
        // 睡眠 2 秒(sendSms 前睡眠 2 秒)
        try {
            TimeUnit.SECONDS.sleep (2);
        } catch (InterruptedException e) {
            e.printStackTrace ( );
        }
        System.out.println ("发短信" );
    }
  // synchronized 锁的对象是方法的调用者 phone2 实例对象
    public synchronized void call(){
        System.out.println ("打电话" );
    }
    // 没有锁,不受锁的影响
    public  void hello(){
        System.out.println ("hello" );
    }
}

问题五:增加两个静态方法,只有一个对象(先发短信/先打电话)?

package com.kk.Lock8;
import java.util.concurrent.TimeUnit;
// 问题五:增加两个静态方法,只有一个对象(先发短信/先打电话)?
//问题五分析:先发短信,后打电话,两个线程持有的是同一把锁
public class Test {
    public static void main(String[] args) {
        Phone phone = new Phone ( );

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

        // 睡眠 1 秒(main 调用顺序睡眠 1 秒)
        try {
            TimeUnit.SECONDS.sleep (1);
        } catch (InterruptedException e) {
            e.printStackTrace ( );
        }

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

// 类是唯一的,对象是N个
class Phone{
    // synchronized 锁的对象是方法的调用者
    // stati 类一加载,既有 Class 模板(类)
    // 锁的是 Class 类
    public static synchronized void sendSms(){
        // 睡眠 2 秒(sendSms 前睡眠 2 秒)
        try {
            TimeUnit.SECONDS.sleep (2);
        } catch (InterruptedException e) {
            e.printStackTrace ( );
        }
        System.out.println ("发短信" );
    }

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

问题六:两个对象,两个静态的同步方法(先发短信/先打电话)?

package com.kk.Lock8;
import java.util.concurrent.TimeUnit;
// 问题五:增加两个静态方法,只有一个对象(先发短信/先打电话)?
//问题五分析:先发短信,后打电话,两个线程持有的是同一把锁

// 问题六:两个对象,两个静态的同步方法(先发短信/先打电话)?
//问题五分析:其实就是同问题五的结果
public class Test {
    public static void main(String[] args) {
        // 两个对象的 Class 都是同一个,static 锁也是同一个 Class
        Phone phone = new Phone ( );
        Phone phone2 = new Phone ( );

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

        // 睡眠 1 秒(main 调用顺序睡眠 1 秒)
        try {
            TimeUnit.SECONDS.sleep (1);
        } catch (InterruptedException e) {
            e.printStackTrace ( );
        }

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

// 类是唯一的,对象是N个
class Phone{
    // synchronized 锁的对象是方法的调用者
    // stati 类一加载,既有 Class 模板(类)
    // 锁的是 Class 类
    public static synchronized void sendSms(){
        // 睡眠 2 秒(sendSms 前睡眠 2 秒)
        try {
            TimeUnit.SECONDS.sleep (2);
        } catch (InterruptedException e) {
            e.printStackTrace ( );
        }
        System.out.println ("发短信" );
    }

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

问题七:1 个静态同步方法, 1 个普通同步方法(先发短信/先打电话)?

package com.kk.Lock8;
import java.util.concurrent.TimeUnit;
// 问题七:1 个静态同步方法, 1 个普通同步方法(先发短信/先打电话)?
//问题七分析:先打电话,后发短信,因为两个持有的是两个锁,一个Class,一个 phone实例

public class Test {
    public static void main(String[] args) {
        // 两个对象的 Class 都是同一个,static 锁也是同一个 Class
        Phone phone = new Phone ( );

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

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

// 类是唯一的,对象是N个
class Phone{
    // synchronized 锁的对象是方法的调用者
    // stati 类一加载,既有 Class 模板(类)
    // 锁的是 Class 类
    public static synchronized void sendSms(){
        // 睡眠 2 秒(sendSms 前睡眠 2 秒)
        try {
            TimeUnit.SECONDS.sleep (2);
        } catch (InterruptedException e) {
            e.printStackTrace ( );
        }
        System.out.println ("发短信" );
    }

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


问题八:1 个静态同步方法, 1 个普通同步方法,两个对象(先发短信/先打电话)?

package com.kk.Lock8;
import java.util.concurrent.TimeUnit;
// 问题七:1 个静态同步方法, 1 个普通同步方法,一个对象(先发短信/先打电话)?
//问题七分析:先打电话,后发短信,因为两个持有的是两个锁,一个Class,一个 phone实例

//问题八:1 个静态同步方法, 1 个普通同步方法,两个对象(先发短信/先打电话)?
//同问题七
public class Test {
    public static void main(String[] args) {
        // 两个对象的 Class 都是同一个,static 锁也是同一个 Class
        Phone phone = new Phone ( );
        Phone phone2 = new Phone ( );

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

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

// 类是唯一的,对象是N个
class Phone{
    // synchronized 锁的对象是方法的调用者
    // stati 类一加载,既有 Class 模板(类)
    // 锁的是 Class 类
    public static synchronized void sendSms(){
        // 睡眠 2 秒(sendSms 前睡眠 2 秒)
        try {
            TimeUnit.SECONDS.sleep (2);
        } catch (InterruptedException e) {
            e.printStackTrace ( );
        }
        System.out.println ("发短信" );
    }

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


五、集合类(安全与不安全)

1、ArrayList 解决线程安全问题

package com.kk.unsafe;

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

public class ListTest {

    // java.util.ConcurrentModificationException 并发修改异常
    public static void main(String[] args) {
        // 解决 ArrayList 并发修改问题
        // 1、List list = new Vector<> ( );
        // 2、List list = Collections.synchronizedList (new ArrayList<> ());
        // 3、List list = new CopyOnWriteArrayList();

        // CopyOnWrite 顾名思义,写入时复制;采用 COW,计算机领域的一种优化策略
        // CopyOnWrite 在多线程调用时的处理方式:
        // (1)读:原来固定的 list
        // (2)写:将新数据放入 copy数组中(避免覆盖),此时有人读取的还是原来的数组
        // 读写分离

        List list = new CopyOnWriteArrayList();
        //
        for (int i = 1; i <= 10; i++) {

            new Thread (() ->
            {
                list.add (
                        UUID.randomUUID ( ).toString ( ).substring (0,6));
                System.out.println ("线程名:"+Thread.currentThread ().getName ()+"\t"+list );
            }, "" + i).start ( );
        }
    }
}

2、HashSet 同上

package com.kk.unsafe;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.CopyOnWriteArraySet;

public class SetList {
    public static void main(String[] args) {
        // 解决 ArrayList 并发修改问题
        // 1、Set set = Collections.synchronizedSet (new HashSet<> ());
        // 2、Set set = new CopyOnWriteArraySet();

        // CopyOnWrite 顾名思义,写入时复制;采用 COW,计算机领域的一种优化策略
        // CopyOnWrite 在多线程调用时的处理方式:
        // (1)读:原来固定的 list
        // (2)写:将新数据放入 copy数组中,此时有人读取的还是原来的数组
        // 读写分离

        Set set = new CopyOnWriteArraySet();
        //
        for (int i = 1; i <= 10; i++) {
            new Thread (() ->
            {
                set.add (
                        UUID.randomUUID ( ).toString ( ).substring (0,6));
                System.out.println ("线程名:"+Thread.currentThread ().getName ()+"\t"+set );
            }, "" + i).start ( );
        }
    }
}

3、hashMap 并发问题

public class MapTest {
    public static void main(String[] args) {
        // 解决 ArrayList 并发修改问题

        Map map = new ConcurrentHashMap<> ( );
        //
        for (int i = 1; i <= 10; i++) {
            new Thread (() ->
            {
                map.put (
                        UUID.randomUUID ( ).toString ( ).substring (0, 6),UUID.randomUUID ( ).toString ( ).substring (0, 6));
                System.out.println ("线程名:" + Thread.currentThread ( ).getName ( ) + "\t" + map);
            }, "" + i).start ( );
        }
    }
}

六、Callable

1、和 Runnable 的区别

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

2、关系图


image.png

3、demo

package com.kk.call;
import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;

public class CallableTest {
    public static void main(String[] args)throws Exception  {
        MyThread thread = new MyThread ( );

        // 适配类
        FutureTask futureTask = new FutureTask (thread);
        // 启动
        new Thread (futureTask,"A").start ();
        new Thread (futureTask,"B").start ();// 结果只会打印一个 call.因为第二个被缓存了

        // 获取返回值, get() 可能会产生阻塞
        // 所以将这句代码放最后,或者使用异步处理
        Integer result = (Integer) futureTask.get ( );
        System.out.println (result );
    }
}
class MyThread implements Callable{
    @Override
    public Integer call() throws Exception {
        System.out.println ("call..." );
        // ---耗时操作---
        return 1024;
    }
}

七、CountDownLatch、CyclicBarrier、Semaphore

1、CountDownLatch(减法计数器)
image.png
package com.kk.count;
import java.util.concurrent.CountDownLatch;
// 计数器
public class CountDownLatchDemo{
    public static void main(String[] args) throws Exception{
        // 初始化设置总数
        CountDownLatch countDownLatch = new CountDownLatch (6);
        for (int i = 1; i <= 6; i++) {
            new Thread (()->
            {
                System.out.println (Thread.currentThread ().getName () );
                countDownLatch.countDown ();// 计数 -1
            },""+i).start ();
        }

        // 等待计时器归零,再往下执行
        countDownLatch.await ();

        System.out.println ( "结束,关闭资源...");
    }
}

须知:
countDownLatch.countDown ();// 计数 -1
countDownLatch.await ();// 等待计时器归零,再往下执行
每次有线程执行完后,countDown ()数量 -1,countDownLatch.await ()就会被唤醒,继续执行。

2、CyclicBarrier(加法计数器)
package com.kk.count;
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;

public class CyclicBarrierDemo {
    public static void main(String[] args) {
        // 赚 10 亿财富自由
        CyclicBarrier cyclicBarrier = new CyclicBarrier (10, () -> {
            System.out.println ("实现财富自由!");
        });

        for (int i = 1; i <= 10; i++) {
            // 1.8 以后线程外的变量,要在线程内使用,若是每加 final 默认隐式加 final
            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 ( );
                }

            },temp+"").start ();
        }
    }
}

3、Semaphore(信号量:限流可以用到)
image.png
package com.kk.count;

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

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

        // 信号量:初始化限定线程数量,可用于限流
        Semaphore semaphore = new Semaphore (3);

        for (int i = 1; i <= 6; i++) {
            new Thread (()->{
                try {
                    // 获取:厕所拉屎的权利
                    semaphore.acquire ();
                    System.out.println (Thread.currentThread ().getName ()+":开始拉屎!" );
                    TimeUnit.SECONDS.sleep (1);
                    System.out.println (Thread.currentThread ().getName ()+":拉屎完毕!" );
                } catch (InterruptedException e) {
                    e.printStackTrace ( );
                }finally {
                    // 释放:厕所关闭
                    semaphore.release ();
                }
            }).start ();
        }
    }
}

原理:

(1)semaphore.acquire () 获得,假设如果已经满了,等待释放为止。
(2)semaphore.release () 释放,会将当前的信号量释放 +1,然后唤醒等待的线程!
(3)作用:多个共享资源互斥的使用,并发限流,控制最大并发数

八、读写锁(ReadWriteLock)

image.png

1、没有加锁的缓存demo

package com.kk.rw;
import java.util.HashMap;
import java.util.Map;

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

        MyCache myCache = new MyCache ( );
        
        // 并发写
        for (int i = 1; i <= 100; i++) {
            final int temp = i;
            new Thread (()->{
                myCache.put (temp+"",temp+"");
            },""+i).start ();
        }
        // 并发读
        for (int i = 1; i <= 100; i++) {
            final int temp = i;
            new Thread (()->{
                myCache.get (temp+"");
            },""+i).start ();
        }
    }
}

// 自定义缓存
class MyCache{

    private Map map = new HashMap<> ();
    // 写
    public void put(String key,Object val){
        System.out.println (Thread.currentThread ().getName ()+" 写入:"+key );
        map.put (key,val);
        System.out.println (Thread.currentThread ().getName ()+" 写入成功!");
    }
    // 读
    public void get(String key){
        System.out.println (Thread.currentThread ().getName ()+" 读取:"+key );
        Object o = map.get (key);
        System.out.println (Thread.currentThread ().getName ()+" 读取成功,读到:"+o);
    }
}

插队,无序写,会出问题,读无所谓


image.png

2、加入读写锁

package com.kk.rw;

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

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

        MyCacheLock myCache = new MyCacheLock ( );

        // 并发写
        for (int i = 1; i <= 100; i++) {
            final int temp = i;
            new Thread (()->{
                myCache.put (temp+"",temp+"");
            },""+i).start ();
        }
        // 并发读
        for (int i = 1; i <= 100; i++) {
            final int temp = i;
            new Thread (()->{
                myCache.get (temp+"");
            },""+i).start ();
        }
    }
}

// 自定义缓存(读写锁)
class MyCacheLock{

    private Map map = new HashMap<> ();
    // 读写锁:更细粒度控制
    private ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
    // 写:写入的时候,同一时间只允许一个线程写
    public void put(String key,Object val){
        // 加锁(写锁)
        readWriteLock.writeLock ().lock ();
        try {
            System.out.println (Thread.currentThread ().getName ()+" 写入:"+key );
            map.put (key,val);
            System.out.println (Thread.currentThread ().getName ()+" 写入成功!");
        } catch (Exception e) {
            e.printStackTrace ( );
        }finally {
            readWriteLock.writeLock ().unlock ();// 释放
        }
    }
    // 读:读取的时候,同一时间允许多个线程读取
    public void get(String key){
        // 加锁(读锁)
        readWriteLock.readLock ().lock ();
        try {
            System.out.println (Thread.currentThread ().getName ()+" 读取:"+key );
            Object o = map.get (key);
            System.out.println (Thread.currentThread ().getName ()+" 读取成功,读到:"+o);
        } catch (Exception e) {
            e.printStackTrace ( );
        }finally {
            readWriteLock.readLock ().unlock ();// 释放
        }
    }
}


image.png

3、读写锁小结:
(1)读写锁分为:独占锁(写锁)又称排它锁,共享锁(读锁)
(2)读——读 可以共存
(3)读——写 不可共存
(4)写——写 不可共存

九、阻塞队列(FIFO-先进先出)

1、概念

(1)写入:如果队列满,就必须阻塞等待
(2)取出:如果队列空,就必须阻塞生产

(3)文档


image.png

(4)树结构关系


image.png
2、四组 API

抛出异常,不会抛出异常,阻塞等待,阻塞超时


image.png
    /**
     * 抛出异常
     */
    @Test
    public void test1(){
        // 队列的大小
        ArrayBlockingQueue blockingQueue = new ArrayBlockingQueue<> (3);

        System.out.println (blockingQueue.add ("A") );
        System.out.println (blockingQueue.add ("B") );
        System.out.println (blockingQueue.add ("C") );

        // 抛出异常:java.lang.IllegalStateException: Queue full
        // System.out.println (blockingQueue.add ("D") );

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

        // 抛出异常:java.util.NoSuchElementException
        // System.out.println (blockingQueue.remove () );
    }
    /**
     * 有返回值,无异常
     */
    @Test
    public void test2(){
        // 队列的大小
        ArrayBlockingQueue blockingQueue = new ArrayBlockingQueue<> (3);

        System.out.println (blockingQueue.offer ("A") );
        System.out.println (blockingQueue.offer ("B") );

        //System.out.println (blockingQueue.element () );
        System.out.println (blockingQueue.peek () );
        System.out.println (blockingQueue.offer ("C") );

        //System.out.println (blockingQueue.offer ("D") );// false 不抛出异常

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

        // System.out.println (blockingQueue.poll () );// null 不抛出异常
    }


    /**
     * 等待,阻塞(一直阻塞)
     */
    @Test
    public void test3() throws Exception{
        // 队列的大小
        ArrayBlockingQueue blockingQueue = new ArrayBlockingQueue<> (3);

        // 一直阻塞
        blockingQueue.put ("A");
        blockingQueue.put ("B");
        blockingQueue.put ("C");
        // 队列没有位置了,一直阻塞
        //blockingQueue.put ("D");

        blockingQueue.take ();
        blockingQueue.take ();
        blockingQueue.take ();
        //blockingQueue.take ();// 队列内没有元素,一直阻塞
    }

    /**
     * 等待,阻塞(超时等待)
     */
    @Test
    public void test4() throws Exception{
        // 队列的大小
        ArrayBlockingQueue blockingQueue = new ArrayBlockingQueue<> (3);

        blockingQueue.offer ("A");
        blockingQueue.offer ("B");
        blockingQueue.offer ("C");
        // 设置超时超过 2 s,则不等待
        blockingQueue.offer ("D",2, TimeUnit.SECONDS);

        blockingQueue.poll ();
        blockingQueue.poll ();
        blockingQueue.poll ();
        // 设置超时超过 2 s,则不等待
        blockingQueue.poll (2,TimeUnit.SECONDS);
    }
3、SynchronousQueue (无容量)

(1)进去一个元素,必须等待取出来之后,才能再往里面放一个元素。
(2)put 进去一个元素,必须从 take 中取出,才能再 put 第二个元素
(3)这里可以先put; 然后再打印语句,然后把T2线程注释掉。就会发现put 之后的输出语佝是没办法执行的,也就是说put 之后的输出宇语句是没办法执行的;也就是说 put 操作时就进入到阻塞了。只有调用了take 方法后, 原有的put才会继续执行下一个 put 操作

package com.kk.queue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.SynchronousQueue;
public class SynchronousQueueDemo {

    public static void main(String[] args) {
        // 同步队列
        BlockingQueue blockingQueue = new SynchronousQueue<> ( );

        new Thread (() -> {
            try {
                System.out.println (Thread.currentThread ( ).getName ( ) + "添加:a");
                blockingQueue.put ("a");
                System.out.println (Thread.currentThread ( ).getName ( ) + "添加:b");
                blockingQueue.put ("b");
                System.out.println (Thread.currentThread ( ).getName ( ) + "添加:c");
                blockingQueue.put ("c");

            } catch (InterruptedException e) {
                e.printStackTrace ( );
            }
        }, "T1").start ( );


        new Thread (() -> {
            try {
                System.out.println (Thread.currentThread ( ).getName ( ) + "\t取出:" + blockingQueue.take ( ));
                System.out.println (Thread.currentThread ( ).getName ( ) + "\t取出:" + blockingQueue.take ( ));
                System.out.println (Thread.currentThread ( ).getName ( ) + "\t取出:" + blockingQueue.take ( ));
            } catch (InterruptedException e) {
                e.printStackTrace ( );
            }
        }, "T2").start ( );
    }
}

(4)解释打印的连续 put现象,看 3 的解释


image.png

十、线程池()

核心内容

(1)三大构造方法
(2)七大构造参数
(3)四种拒绝策略

1、池化技术

(1)为何使用池化技术:程序的运行,本质:占用系统的资源,池化技术则可以优化资源的使用。
(2)池化技术概念:事先准备好一些资源,有人要用,就来池中取,用完了归还到池子中取。
(3)池化技术应用:线程池,连接池,内存池,对象池

2、线程池的好处:

(1)降低资源的消耗(线程复用)
(2)提高响应的速度
(3)方便管理
(4)可控最大并发数

3、三个创建线程的构造
package com.kk.executors;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

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

        // 单个线程,只有一个线程执行运行处理
        //ExecutorService threadPool = Executors.newSingleThreadExecutor ( );

        // 创建一个固定大小的线程池
        //ExecutorService threadPool = Executors.newFixedThreadPool (5 );

        // 可伸缩:遇强则强,遇弱则弱
        ExecutorService threadPool = Executors.newCachedThreadPool ( );

        // 使用线程池来创建函数
        try {
            for (int i = 1; i <= 10; i++) {
                threadPool.execute (() -> {
                    System.out.println (Thread.currentThread ( ).getName ( ) + ":ok");
                });
            }
        } catch (Exception e) {
            e.printStackTrace ( );
        }finally {
            // 使用完毕,关闭线程池
            threadPool.shutdown ();
        }
    }
}

4、七个构造参数

(1)先看三个构造的源码,发现底层都是调用同一个构造方法(ThreadPoolExecutor)


image.png
image.png
image.png

(2)然后追溯到 ThreadPoolExecutor 源码


1635177181(1).jpg

(3)自定义线程池

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

        // 七大参数,自定义线程
        ThreadPoolExecutor threadPool = new ThreadPoolExecutor (
                2,
                5,
                3,
                TimeUnit.SECONDS,
                new LinkedBlockingDeque<> (3),
                Executors.defaultThreadFactory ( ),// 默认工厂
                new ThreadPoolExecutor.AbortPolicy ( )
        );

        // 使用线程池来创建函数
        try {
            for (int i = 1; i <= 10; i++) {
                threadPool.execute (() -> {
                    System.out.println (Thread.currentThread ( ).getName ( ) + ":ok");
                });
            }
        } catch (Exception e) {
            e.printStackTrace ( );
        }finally {
            // 使用完毕,关闭线程池
            threadPool.shutdown ();
        }
    }
}

(4)结论:首选固定线程池,newFixedThreadPool,因可伸缩的 最大可达到 21亿,必然会有OOM的可能性

5、四个拒绝策略

RejectedExecutionHandler 接口底下的四个实现类


image.png

(1)默认:ThreadPoolExecutor.AbortPolicy ( ),阻塞队列满了,还有线程进来,不处理并且抛出异常。(当线程数达到 Max ,的线程进入阻塞队列)

    public static void main(String[] args) {

        // 七大参数,自定义线程
        ThreadPoolExecutor threadPool = new ThreadPoolExecutor (
                2,
                5,
                3,
                TimeUnit.SECONDS,
                new LinkedBlockingDeque<> (3 ),
                Executors.defaultThreadFactory ( ),// 默认工厂
                new ThreadPoolExecutor.AbortPolicy ( )
        );

        // 使用线程池来创建函数
        try {
            for (int i = 1; i <= 9; i++) {
                threadPool.execute (() -> {
                    System.out.println (Thread.currentThread ( ).getName ( ) + ":ok");
                });
            }
        } catch (Exception e) {
            e.printStackTrace ( );
        }finally {
            // 使用完毕,关闭线程池
            threadPool.shutdown ();
        }
    }

(2)ThreadPoolExecutor.CallerRunsPolicy ( ),哪里来的去哪里(这个线程池由主线程 main创建,则拒绝策略让 main处理 超出来的部门)

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

        // 七大参数,自定义线程
        ThreadPoolExecutor threadPool = new ThreadPoolExecutor (
                2,
                5,
                3,
                TimeUnit.SECONDS,
                new LinkedBlockingDeque<> (3 ),
                Executors.defaultThreadFactory ( ),// 默认工厂
                new ThreadPoolExecutor.CallerRunsPolicy ( )
        );

        // 使用线程池来创建函数
        try {
            for (int i = 1; i <= 11; i++) {
                threadPool.execute (() -> {
                    System.out.println (Thread.currentThread ( ).getName ( ) + ":ok");
                });
            }
        } catch (Exception e) {
            e.printStackTrace ( );
        }finally {
            // 使用完毕,关闭线程池
            threadPool.shutdown ();
        }
    }
}

(3)ThreadPoolExecutor.DiscardPolicy( ),队列满了,丢弃任务,不会抛出异常
(4)ThreadPoolExecutor.DiscardOldestPolicy ( ),队列满了,抢掉最早的那个线程来使用,不会抛出异常

6、CPU密集型 and IO密集型

线程池的最大线程数如何定义,取决于两者
CPU:几核就是几,可以保持CPU效率最高的使用
IO:判断程序中十分消耗IO的线程(写和读消耗大的多分配)

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

        System.out.println (Runtime.getRuntime ().availableProcessors () );
        // 七大参数,自定义线程
        ThreadPoolExecutor threadPool = new ThreadPoolExecutor (
                2,
                Runtime.getRuntime ().availableProcessors () ,// 最大线程数=CPU核数
                3,
                TimeUnit.SECONDS,
                new LinkedBlockingDeque<> (3 ),
                Executors.defaultThreadFactory ( ),// 默认工厂
                new ThreadPoolExecutor.DiscardPolicy ( )
        );

        // 使用线程池来创建函数
        try {
            for (int i = 1; i <= 11; i++) {
                final int  temp = i;
                threadPool.execute (() -> {
                    System.out.println (Thread.currentThread ( ).getName ( ) + ":ok"+temp);
                });
            }
        } catch (Exception e) {
            e.printStackTrace ( );
        }finally {
            // 使用完毕,关闭线程池
            threadPool.shutdown ();
        }
    }
}

十一、四大函数式接口

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

(1)函数型接口:只有一个传入,只有一个输出


image.png
// 自定义方法
// Function:只有一个传入,只有一个输出
public class Demo {
    public static void main(String[] args) {

//        Function function = new Function ( ) {
//            @Override
//            public String apply(String str) {
//                return str;
//            }
//        };

        // 优化写法
        Function function = str -> str;

        // 使用
        System.out.println (function.apply ("str"));
    }
}

(2)断定型接口:只有一个输入参数,返回值只能是 布尔值

// 断定型接口:只有一个输入参数,返回值只能是 布尔值
public class Demo01 {
    public static void main(String[] args) {
        // 判断字符串是否为空
//        Predicate predicate = new Predicate ( ) {
//            @Override
//            public boolean test(String str) {
//                return str.isEmpty ();
//            }
//        };

        // 优化写法 一
//        Predicate predicate = (str) -> {
//            return str.isEmpty ( );
//        };

        // 优化写法 二
        Predicate predicate = str -> str.isEmpty ();

        // 使用
        System.out.println (predicate.test (""));
    }
}

(3)消费型接口:只有输入,没有返回值

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

        Consumer consumer = str -> System.out.println ( str);

        consumer.accept ("st");
    }
}

(4)供给型接口:没有输入,只有返回

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

        Supplier supplier = () -> {
            return "来了!";
        };

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

十二、Stream流式计算

一道题,概括
(1)ID必须为偶数
(2)年龄必须大于23岁
(3)用户名转换为大写
(4)用户名字母倒着排序
(5)分页出数据范围

package com.kk.function;
import lombok.AllArgsConstructor;
import lombok.Data;
import java.util.Arrays;
import java.util.List;

public class Demo4 {
    public static void main(String[] args) {
        /**
         * (1)ID必须为偶数
         * (2)年龄必须大于23岁
         * (3)用户名转换为大写
         * (4)用户名字母倒着排序
         * (5)输出一个用户
         */

        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 (5, "e", 25);

        // 集合用于存储
        List list = Arrays.asList (u1, u2, u3, u4, u5);

        // Stream 用于计算
        list.stream ()
                .filter (u->{return u.getId ()%2==0;})
                .filter (u-> u.getAge ()>23)
                .map (u->u.getName ().toUpperCase ())
                .sorted ((c1,c2)->c1.compareTo (c2))
                .limit (1)
                .forEach (System.out::println);
    }
}

@AllArgsConstructor
@Data
class User{
    private int id;
    private String name;
    private int age;
}

十三、分之合并(ForkJoin)

1、原理

(1)将一个大任务,分解为小任务分别计算,最后再合并


image.png

(2)底层实现的是双端队列,可以从上面或者下面同时取


image.png
2、ForkJoin实现求和计算
image.png
// 计算一个从 start 每次累加到 end 的案例
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) < temp) {// 小于临界值则常规计算
            long sum = 0;
            for (long i = start; i <= end; i++) {
                sum += i;
            }
            return sum;
        } else {// forkJoin(递归
            // 中间值
            long mid = (start + end) / 2;
            ForkJoinDemo fork1 = new ForkJoinDemo (start, mid);
            fork1.fork ( );// 拆分任务,将任务压入到线程队列
            ForkJoinDemo fork2 = new ForkJoinDemo (mid + 1, end);
            fork2.fork ( );// 拆分任务,将任务压入到线程队列
            // 合并返回
            return fork1.join ( ) + fork2.join ( );
        }
    }
}
3、性能比较(常规、forkJoin、Stream)
package com.kk.forkjoin;

import java.util.concurrent.RecursiveTask;
import java.util.stream.LongStream;

// 计算一个从 start 每次累加到 end 的案例
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) < temp) {// 小于临界值则常规计算
            long sum = 0;
            for (long i = start; i <= end; i++) {
                sum += i;
            }
            return sum;
        } else {// forkJoin(递归
            // 中间值
            long mid = (start + end) / 2;
            ForkJoinDemo fork1 = new ForkJoinDemo (start, mid);
            fork1.fork ( );// 拆分任务,将任务压入到线程队列
            ForkJoinDemo fork2 = new ForkJoinDemo (mid + 1, end);
            fork2.fork ( );// 拆分任务,将任务压入到线程队列
            // 合并返回
            return fork1.join ( ) + fork2.join ( );
        }
    }
}

// 测试
class Test {
    public static void main(String[] args) throws Exception {
        long startTime = System.currentTimeMillis ( );
        long start = 1;
        long end = 100_0000_0000L;// 100亿

        // 常规,4221 ms
        long sum = 0;
//        for (long i = start; i <= end; i++) {
//            sum+=i;
//        }

        // forkJoin 3522 ms
//        ForkJoinPool forkJoinPool = new ForkJoinPool ( );
//        ForkJoinDemo task = new ForkJoinDemo (start, end);
//        // 提交任务
//        ForkJoinTask submit = forkJoinPool.submit (task);
//        sum = submit.get ( );

        // stream 并行流 2488 ms
        sum = LongStream.rangeClosed (start,end).parallel ().reduce (0,Long::sum);

        long endTime = System.currentTimeMillis ( );
        System.out.println ("结果为:" + sum + ",花费时间为:" + (endTime - startTime) + "毫秒");
    }
}

十四、异步回调

分为两类:有返回值 和 无返回值

1、无返回值
public class CompleableFutureDemo {
    public static void main(String[] args)throws Exception {
        // 一个异步任务(没有返回值)
        CompletableFuture completableFuture = CompletableFuture.runAsync (() -> {
            try {
                TimeUnit.SECONDS.sleep (2);
                System.out.println (Thread.currentThread ().getName ()+"=>async" );
            } catch (InterruptedException e) {
                e.printStackTrace ( );
            }
        });
        System.out.println ("main" );

        // 获取阻塞执行结果
        completableFuture.get ();
    }
}
2、有返回值
public class CompleableFutureDemo {
    public static void main(String[] args) throws Exception {
        // 一个异步任务(有返回值)
        CompletableFuture completableFuture = CompletableFuture.supplyAsync (() -> {
            try {
                TimeUnit.SECONDS.sleep (2);
                System.out.println (Thread.currentThread ( ).getName ( ) + "=>async");
            } catch (InterruptedException e) {
                e.printStackTrace ( );
            }

            // 异常情况
            int i = 1/0;
            return 1024;
        });

        System.out.println ("main");

        // 获取阻塞执行结果
        System.out.println (completableFuture.whenCompleteAsync ((t, u) -> {
            // 正常返回结果
            System.out.println ("t  --> " + t);
            // 错误信息
            System.out.println ("u  --> " + u);
        }).exceptionally ((e) -> {
            e.printStackTrace ( );
            // 返回异常的结果信息
            return 500;
        }).get ());
    }
}

十五、JMM

1、谈谈你对 Volatile 的理解

Volatile 是 java 虚拟机提供的 轻量级的同步机制
(1)保证可见性
(2)不保证原子性
(3)禁止指令重排

2、什么是 JMM

java 内存模型,不存在的东西,一个概念(约定 )。

关于 JMM 的一些同步约定

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

线程、主存、工作内存关系图

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

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

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

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

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

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

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

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

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

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

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

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

不允许一个线程将没有assign的数据从工作内存同步回主内存
一个新的变量必须在主内存中诞生,不允许工作内存直接使用一个未被初始化的变量。就是怼变量实施use、store操作之前,必须经过assign和load操作

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

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

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

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

public class JMMDemo {

    private static int num = 0;
    public static void main(String[] args) {
        new Thread (()->{
            while (num==0){
                // 一直循环,若 num被改变不再是 0则停止
            }
        }).start ();

        // 睡 2s 再改变值
        try {
            TimeUnit.SECONDS.sleep (2);
        } catch (InterruptedException e) {
            e.printStackTrace ( );
        }
        num =1;
        System.out.println (num );
    }
}

问题:运行后发现,程序没有停下来,因为线程A 无法知道,main 线程改变的num 为 1 。-- 此刻需要加入 volatile 关键字

十六、volatile

一、三个特性

(1)保证可见性

public class JMMDemo {

    private volatile static int num = 0;
    public static void main(String[] args) {
        new Thread (()->{
            while (num==0){
                // 一直循环,若 num被改变不再是 0则停止
            }
        }).start ();

        // 睡 2s 再改变值
        try {
            TimeUnit.SECONDS.sleep (2);
        } catch (InterruptedException e) {
            e.printStackTrace ( );
        }
        num =1;
        System.out.println (num );
    }
}

(2)不保证原子性(原子性:可分割)

// volatile 不保证原子性
public class JMMDemo {

    private volatile static int num = 0;

    private static void add(){
        num++;// 不是一个原子性操作
    }

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

        // JVM默认存在有 两个线程  main  GC
        // 当存活数为 2++ 说明以上操作执行完毕 则让步,输出以下代码
        while (Thread.activeCount ()>2){
            Thread.yield ();
        }

        System.out.println (num );
    }
}
二、问:如果不加 lock 或 synchronize ,怎么保证原子性

查看字节码文件:javap -c JMMDemo.class,进行分析

image.png

采用原子类
image.png

这些类的底层都直接和操作系统挂钩。。。(CAS...)

// volatile 不保证原子性
public class JMMDemo {

    private  static AtomicInteger num = new AtomicInteger ();

    public static void add(){
        num.addAndGet (1);
        num.getAndDecrement ();
    }

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

        // JVM默认存在有 两个线程  main  GC
        // 当存活数为 2++ 说明以上操作执行完毕 则让步,输出以下代码
        while (Thread.activeCount ()>2){
            Thread.yield ();
        }

        System.out.println (num );
    }
}
三、禁止指令重排

(1)何为指令重排:你写的程序,计算机并不是按照你写的顺序去执行。

(2)代码的执行流程:源代码 =》 编译器优化的重排 =》 指令并行也可能会重排 =》 内存系统也会重排 =》 执行

(3)处理器在进行指令重排的时候,考虑:数据之间的依赖性

int x = 1 ; // 1
int y = 2 ; // 2
x = x+ 5 ; // 3
y = x + x; // 4

我们写的顺序是 1234,但系统执行的时候可能是, 2134,1324,但绝不会是 4123,因为 4 的时候 x 还没被定义。

(4)底层采用内存屏障,CPU级别的指令:
保证操作的执行顺序,保证变量内存可见性


image.png

总结:Volatile,可以保证内存可见性,禁止指令重排,但是无法保证原子性(可以用原子类),内存屏障在单例模式应用比较多。

十七、深入单例模式

https://www.jianshu.com/p/b120020e7ea8

十八、CAS(比较并交换)

1、使用demo
public class CASDemo {
    public static void main(String[] args) {

        AtomicInteger atomicInteger = new AtomicInteger (2021);

        // 参数一:期望值,更新值
        // public final boolean compareAndSet(int expect, int update)
        // 如果我期望值是 2021(现在拿到的值),那么就更新成功(变成 2022),否则不更新
        System.out.println (atomicInteger.compareAndSet (2021, 2022));
        System.out.println (atomicInteger.get ( ));
        //

        // 期望值已经是 2022 ,则不更新
        System.out.println (atomicInteger.compareAndSet (2021, 2022));
        System.out.println (atomicInteger.get ( ));

    }
}
2、Unsafe类

valueiffset 不是对象内存中的值,而是对象在内存中的偏移量


image.png
image.png
3、总结

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

4、缺点

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

十九、原子引用(解决ABA问题)

1、什么是ABA问题

你和王少分别是线程A,B,你女朋友是共享变量。今天你去见她,她还是她(还是一个人 1),后来你出差了。王少当你不在的时间去操作了你女朋友,于是她不在是她(变成了两个人 2),当你出差回来,你以为她还是她,其实她已经变了,这就是ABA问题,A搞完,B搞,B搞完 A搞。


image.png
2、解决ABA问题
package com.kk.cas.aba;
import com.kk.jingdongelactichserchdemo.pojo.Content;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicStampedReference;
public class ABADemo {

    // AtomicStampedReference 如果是个包装类(需要注意原子引用问题),
    // 因为 Integer (cache [-128~127])
    // 若 Integer赋值超过了这个范围则会 new 出一个新的对象,就不是原来的,所以无法避免

    // 正常业务下,泛型不能是包装类(原子引用可能会和底层缓存机制冲突)
    // Content 自定义pojo

    public static void main(String[] args) {

        Content content1 = new Content ("1", "18", "aa");
        Content content2 = new Content ("2", "18", "aa");
        Content content3 = new Content ("3", "18", "aa");

        AtomicStampedReference at = new AtomicStampedReference (content1, 1);
        
        // 乐观锁原理
        new Thread (() -> {
            // 获得版本号
            int stamp = at.getStamp ( );
            System.out.println ("al =>" + stamp);

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

            at.compareAndSet (content1, content2, at.getStamp ( ), at.getStamp ( ) + 1);
            System.out.println ("a2 =>" + at.getStamp ( ));

        }, "A").start ( );


        new Thread (() -> {
            // 获得版本号
            System.out.println ("bl =>" + at.getStamp ( ));

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

            System.out.println (at.compareAndSet (content1, content3, at.getStamp ( ), at.getStamp ( ) + 1));
            System.out.println ("b2 =>" + at.getStamp ( ));
        }, "B").start ( );
    }
}


二十、可重入锁、自旋锁、死锁、公平锁、非公平锁....

六张面试题
https://www.jianshu.com/p/909a74926e55

个人理解

狂神说,有部分讲的不太准确,CAS,自旋锁....可以参考以上面试题。

文章参考B站:狂神说,B站中 弹屏们 ,尚硅谷以及一些博客

https://www.cnblogs.com/xiaoxi/p/9140541.html
https://www.cnblogs.com/KingIceMou/p/7239668.html
https://blog.csdn.net/weixin_41832850/article/details/100095677
https://blog.csdn.net/zxl1148377834/article/details/90079882

你可能感兴趣的:(R-JUC(java并发包)入门文档 2021年10月13日 至 2021年11月01日)