JUC并发编程

什么是JUC


java.util 工具包

业务:普通的线程代码:Thread
Runnable:没有返回值,效率相比于Callable相对较低

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

线程和进程

进程和线程(使用一句话描述)

进程:一个程序 静态的指令集合 QQ.exe Music.exe
一个进程可以包含多个线程(至少包含一个)

java默认几个线程? 2(main GC(垃圾回收))

线程:程序被加载到内存中动态执行的实例(例如开了多个窗口)

java无法真正开启线程
它是采用调用本地方法,底层的c++,Java无法直接操作硬件

并发和并行

并发编程:并发和并行

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

  • CPU 单核,模拟出来多个线程,快速交替

并行:多个人同时行走

  • CPU 多核,多个线程可以同时执行 使用线程池
package javaSEStudy.JUC.JUCDemo;

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

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

线程有几个状态

public enum State{
        // 新生
        NEW,
        
        // 运行
        RUNNABLE,

        // 阻塞
        BLOCKED,

        // 等待(死等)
        WAITING,

        // 超时等待(等待一定时间)
        TIMED_WAITING,

        // 终止
        TERMINATED;
}

wait 和 sleep 区别

1.来自不同的类
wait ---> Object类
sleep ---> Thread类

2.释放锁
wait 会释放锁
sleep 睡觉了,抱着锁睡觉,不会释放

3.使用范围不同
wait 必须在同步代码块(synchronized)
sleep 可以在任何地方睡

4.是否需要捕获异常
wait 不需要捕获异常
sleep 必须要捕获异常(可能会发生超时等待异常)

Lock锁(重点)

传统的 Synchronized Lock

package javaSEStudy.JUC.JUCDemo;

// 基本卖票例子

/**
 * 线程就是一个单独的资源类,没有任何附属的操作
 * 1.属性 2.方法
 */
public class Basics2 {
    public static void main(String[] args) {
        // 并发: 多线程操作同一个资源类

        Ticket ticket = new Ticket();

       // @FunctionalInterface 函数式接口
        new Thread( ()-> {
            for (int i = 0; i < 60; i++) {
                ticket.sale();
            }
        },"A" ).start();
        new Thread( ()-> {
            for (int i = 0; i < 60; i++) {
                ticket.sale();
            }
        },"B" ).start();
        new Thread( ()-> {
            for (int i = 0; i < 60; i++) {
                ticket.sale();
            }
        },"C" ).start();
    }
}

// 资源类
class Ticket{
    // 属性 ,方法
    private int number = 50;

    // 卖票的方式
    //synchronized 本质:排队,锁
    //锁对象,锁class
    public synchronized void sale(){
        if (number > 0){
            System.out.println(Thread.currentThread().getName() + "卖出了:"+(number--)+"票,剩余:"+number);
        }
    }
}

Lock接口

ReentrantLock()

公平锁:十分公平,可以先来后到
非公平锁:十分不公平,允许插队(默认)

package javaSEStudy.JUC.JUCDemo.basics;

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

// 基本卖票例子 使用Lock
public class Basics3 {
   public static void main(String[] args) {
       // 并发: 多线程操作同一个资源类

       Ticket1 ticket1 = new Ticket1();

       new Thread( ()-> {for (int i = 0; i < 60; i++) ticket1.sale();},"A").start();
       new Thread( ()-> {for (int i = 0; i < 60; i++) ticket1.sale();},"B").start();
       new Thread( ()-> {for (int i = 0; i < 60; i++) ticket1.sale();},"C").start();
       
   }
}
// Lock
class Ticket1{
   // 属性 ,方法
   private int number = 50;

   Lock lock = new ReentrantLock();

   // 卖票的方式
   public void sale() {
       //加锁
       lock.lock();

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

Synchronized 和 Lock区别

Synchronized :

  • java内置的关键字
  • 无法判断获取锁的状态
  • 会自动释放锁
  • 线程1获得锁,但是发生阻塞,线程2会死等
  • 可重入锁,不可以中断,非公平的
  • 适合锁少量的代码同步问题

Lock :

  • 是一个类
  • 可以判断是否获取到了锁
  • 必须要手动释放锁,如果未释放,会死锁
  • 线程1获得锁,但是发生阻塞,线程2就不会死等
  • 可重入锁,可以判断锁,是否公平自己设置
  • 适合锁大量的同步代码

生产者和消费者问题

Synchronized版

package javaSEStudy.JUC.JUCDemo.prodecersAndConsumers;

/**
 * 线程之间的通信问题 : 等待唤醒,通知唤醒
 * 线程交替执行: A B 操作同一个变量 num = 0
 * A num+1
 * B num-1
 */
public class Synchronized_ {
    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();
    }
}

/**
 * 多线程处理逻辑
 * 判断等待 , 业务 , 通知
 */

//资源类
class Data{
    private int number = 0;

    //增加资源方法
    public synchronized void increment() {
        if (number != 0){
            //等待
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        number++;
        System.out.println(Thread.currentThread().getName() + "--> " + number);
        //通知其他线程,+1完毕
        this.notifyAll();
    }

    //消耗资源方法
    public synchronized void decrement() {
        if (number == 0){
            //等待
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        number--;
        System.out.println(Thread.currentThread().getName() + "--> " + number);
        //通知其他线程,-1完毕
        this.notifyAll();
    }
}

存在问题
只有两个线程是安全的,但是当线程数增加到3,4的时候,就发生问题

我们这里使用的是if判断,所以会发生虚假唤醒,应使用while循环

package javaSEStudy.JUC.JUCDemo.prodecersAndConsumers;

/**
 * 线程之间的通信问题 : 等待唤醒,通知唤醒
 * 线程交替执行: A B 操作同一个变量 num = 0
 * A num+1
 * B num-1
 */
public class Synchronized_ {
    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;

    //增加资源方法
    public synchronized void increment() {
//        if (number != 0){
//            //等待
//            try {
//                this.wait();
//            } catch (InterruptedException e) {
//                e.printStackTrace();
//            }
//        }
        while (number != 0){
            //等待
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        number++;
        System.out.println(Thread.currentThread().getName() + "--> " + number);
        //通知其他线程,+1完毕
        this.notifyAll();
    }

    //消耗资源方法
    public synchronized void decrement() {
//        if (number == 0){
//            //等待
//            try {
//                this.wait();
//            } catch (InterruptedException e) {
//                e.printStackTrace();
//            }
//        }
        while (number == 0){
            //等待
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        number--;
        System.out.println(Thread.currentThread().getName() + "--> " + number);
        //通知其他线程,-1完毕
        this.notifyAll();
    }
}

JUC版

在这里使用的是await signal
通过Lock可以找到Condition

代码实现

package javaSEStudy.JUC.JUCDemo.prodecersAndConsumers;

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

//JUC版的生产者与消费者问题
public class Lock_ {
    public static void main(String[] args) {
        Data1 data = new Data1();

        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 Data1{
    private int number = 0;

    Lock lock = new ReentrantLock();
    //这里condition取代了对象监视器的用法
    Condition condition = lock.newCondition();

    //增加资源方法
    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();
            }
    }

    //消耗资源方法
    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();
        }
    }
}

Condition
作用:精准的通知和唤醒线程,有序执行

package javaSEStudy.JUC.JUCDemo.prodecersAndConsumers;

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

/**
 * 详解Condition
 */
public class Condition_ {
    public static void main(String[] args) {
        Data2 data = new Data2();

        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 Data2{
    //1A 2B 3C
    private int number = 1;
    private Lock lock = new ReentrantLock();
    private Condition condition1 = lock.newCondition();
    private Condition condition2 = lock.newCondition();
    private Condition condition3 = lock.newCondition();

    public void  printA(){
        lock.lock();
        try {
            // 业务,判断 -> 执行 -> 通知
            while (number != 1){
                //等待
                condition1.await();
            }
            System.out.println(Thread.currentThread().getName()+"---> AAAAAAA");
            //唤醒 唤醒指定的人
            number = 2;
            //精准唤醒Condition2
            condition2.signal();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
    public void  printB(){
        lock.lock();
        try {
            // 业务,判断 -> 执行 -> 通知
            while (number != 2){
                condition2.await();
            }
            System.out.println(Thread.currentThread().getName()+"---> BBBBBBB");
            number = 3;
            condition3.signal();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
    public void  printC(){
        lock.lock();
        try {
            // 业务,判断 -> 执行 -> 通知
            while (number != 3){
                condition3.await();
            }
            System.out.println(Thread.currentThread().getName()+"---> CCCCCCC");
            number = 1;
            condition1.signal();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
}

8锁现象

如何判断锁的是谁?什么是锁,锁的是谁
锁对象(多个),Class模板(1个)

第一二问

package javaSEStudy.JUC.JUCDemo.lock_8;

import java.util.concurrent.TimeUnit;

/**
 * 8锁就是关于锁的8个问题
 * 1.标准情况下,两个线程先输出谁 先输出A
 * 2.将A延时2秒,先输出谁,两个线程先输出谁 先输出A
 */
public class Test1 {
    public static void main(String[] args) {

        // 这里只有一个对象
        Phone phone = new Phone();

        // 为什么A永远第一个打印,不是因为A先调用
        //是因为锁的存在
        new Thread(()->{
            phone.send();
        },"A").start();

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

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

class Phone{

    // Synchronized锁的对象是方法的调用者
    // 两个方法用的同一个锁,谁先拿到谁执行
    public synchronized void send(){
        try {
            TimeUnit.SECONDS.sleep(2);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("发送消息");
    }
    public synchronized void call(){
        System.out.println("打电话");
    }
}

第三四问

package javaSEStudy.JUC.JUCDemo.lock_8;

import java.util.concurrent.TimeUnit;

/**
 * 8锁就是关于锁的8个问题
 * 3.增加了一个普通方法,先输出发短信还是玩手机
 *      倘若未将A休眠,则先输出发短信,休眠,因为它调用
 *      倘若A休眠,则先输出玩手机,他没有锁,不受锁的影响
 *
 * 4.两个对象,两个调用者,两把锁
 *      倘若A未休眠2秒,A和B线程随机先启动,但是大概率先启动A,因为CPU是单核,且启动顺序为A,B,大概率先执行A
 *      倘若A休眠2秒,则一定先启动B,他们锁的是两个不同对象,则先执行B
 */
public class Test2 {
    public static void main(String[] args) {

        Phone1 phone1 = new Phone1();
        Phone1 phone2 = new Phone1();

        // 为什么A永远第一个打印,不是因为A先调用
        //是因为锁的存在
        new Thread(()->{
            phone1.send();
        },"A").start();

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

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

class Phone1{

    // Synchronized锁的对象是方法的调用者
    // 两个方法用的同一个锁,谁先拿到谁执行
    public synchronized void send(){
//        try {
//            TimeUnit.SECONDS.sleep(2);
//        } catch (InterruptedException e) {
//            e.printStackTrace();
//        }
        System.out.println("发送消息");
    }
    public synchronized void call(){
        System.out.println("打电话");
    }
    //这里没有锁,不受锁的影响
    public void play(){
        System.out.println("玩手机");
    }
}

第五六问

package javaSEStudy.JUC.JUCDemo.lock_8;

import java.util.concurrent.TimeUnit;

/**
 * 8锁问题
 * 5.增加两个静态的同步方法,只有一个对象,先打印谁?  先打印A
 *      在这里锁的是Class模版,和类一起锁着的
 * 6.两个对象,增加两个静态的同步方法,只有一个对象,先打印谁?
 *      由于锁的是Class模版,所以即使是两个对象,也还是先执行A
 */
public class Test3 {
    public static void main(String[] args) {

        // 两个对象的类模版只有一个,static,锁的是Class
        Phone3 phone1 = new Phone3();
        Phone3 phone3 = new Phone3();

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

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

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

// Phone2只有唯一一个Class对象
class Phone2{

    // Synchronized锁的对象是方法的调用者
    // static 静态方法
    // 和类一起加载,锁的是Class模版
    public static synchronized void send(){
        try {
            TimeUnit.SECONDS.sleep(2);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("发送消息");
    }
    public static synchronized void call(){
        System.out.println("打电话");
    }
}

第七八问

package javaSEStudy.JUC.JUCDemo.lock_8;

import java.util.concurrent.TimeUnit;

/**
 * 8锁问题
 * 7. 一个对象,一个普通的同步方法,一个静态的同步方法,先打印谁
 *      先打印B,因为这是两把不同的锁,不用等待
 * 8. 两个对象,一个普通的同步方法,一个静态的同步方法,先打印谁
 *      先打印B,两个对象,两把不同的锁,互不干扰
 */
public class Test4 {
    public static void main(String[] args) {

        Phone3 phone1 = new Phone3();
        Phone3 phone2 = new Phone3();


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

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

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

class Phone3 {

    // 静态同步方法 锁的是Class类模版
    public static synchronized void send(){
        try {
            TimeUnit.SECONDS.sleep(2);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("发送消息");
    }
    // 普通同步方法 锁的调用者
    public synchronized void call(){
        System.out.println("打电话");
    }
}

小结
new -->是this,这个具体的对象
static -->是Class这个唯一模版

集合类,不安全

List不安全

package javaSEStudy.JUC.JUCDemo.unsafe;

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

//ConcurrentModificationException 并发修改异常
public class List_{
    public static void main(String[] args) {
//        List<String> list = Arrays.asList("1", "2");
//        list.forEach(System.out::println);

        //并发下ArrayList不安全
        /**
         * 解决方案
         * 1.将new ArrayList 改为 new Vector
         *      因为Vector本身就是绝对安全
         * 2.List<String> list = Collections.synchronizedList(new ArrayList<>());
         * 3.List<String> list = new CopyOnWriteArrayList<>();
         */
//        List<String> list = new ArrayList<>();
//        List<String> list = new Vector<>();
//        List<String> list = Collections.synchronizedList(new ArrayList<>());
        // CopyOnWrite 写入时复制 COW 计算机程序设计领域的一种优化策略
        // 多个线程调用的时候,list这一个资源,读取的时候是固定的,但是写的时候可能会发生覆盖
        // 在写入的时候,避免覆盖,造成数据问题
        // CopyOnWriteArrayList 比 Vector好处: 读写分离,Vector调用的是Synchronized,COW调用Lock,效率更高

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

        }
    }
}

Set不安全

package javaSEStudy.JUC.JUCDemo.unsafe;

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

/**
 * 同理: ConcurrentModificationException 并发修改异常
 * 解决方法
 *  1.Set<String> set = Collections.synchronizedSet(new HashSet<>());
 *  2.Set<String> set = new CopyOnWriteArraySet<>();
 */
public class Set_ {
    public static void main(String[] args) {
//        Set<String> set = new HashSet<>();
//        Set<String> set = Collections.synchronizedSet(new HashSet<>());
        Set<String> set = new CopyOnWriteArraySet<>();

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

        }
    }
}

hashSet底层是什么?

public HashSet() {
        map = new HashMap<>();
    }

底层就是hashMap,add set方法本质就是map,他们的key无法重复

map不安全

package javaSEStudy.JUC.JUCDemo.unsafe;

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

// Map不安全

/**
 * ConcurrentModificationException
 * 并发修改异常
 */
public class Map_ {
    public static void main(String[] args) {
        // map不是这么使用的
        // 默认等价于 new HashMap<>(16,0.75)
        Map<String, String> map = new HashMap<>();

        // 两种解决方法
//        Collections.synchronizedMap(map);
//        new ConcurrentHashMap<>();

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

Callable

特点

  1. 可以有返回值
  2. 可以抛出异常
  3. 方法不同,Runnable是实现run()方法,但是Callable是实现call方法

直接上代码

package javaSEStudy.JUC.JUCDemo.callable_;

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

public class Callable_ {
    public static void main(String[] args) throws ExecutionException, InterruptedException {

//        new Thread(new MyThread()).start();
        // 最好别用上述方式,实现功能有限
        // 在这里,Thread只能接收Runnable,所以Callable想要调用Thread,需要通过Runnable接口

        MyThread thread = new MyThread();

        // 适配类
        FutureTask futureTask = new FutureTask(thread);

        // 从而成功将Callable启动
        // 这里只会打印一个你好,因为结果被缓存,所以效率更高
        new Thread(futureTask, "A").start();
        new Thread(futureTask, "B").start();

        // 获取返回值
        // 注意,这个get可能会产生阻塞,一般把他放到最后
        // 或者使用异步通信来处理
        System.out.println(futureTask.get());

        // 链式调用
//        new Thread(new FutureTask(new MyThread())).start();

    }
}

class MyThread implements Callable<String> {

    @Override
    public String call() {
        System.out.println("你好");
        return "hello";
    }
}

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

常用辅助类

CountDownLatch

package javaSEStudy.JUC.JUCDemo.assistant;

import java.util.concurrent.CountDownLatch;

// CountDownLatch 详解  计数器
public class CountDownLatch_ {
    public static void main(String[] args) throws InterruptedException {

        // 必须要执行任务的时候再去使用
        // 总数是10,这是个倒计时
        CountDownLatch cdl = new CountDownLatch(10);

        for (int i = 1; i <= 10 ; i++) {
            new Thread(()->{
                System.out.println("第 "+Thread.currentThread().getName()+" 个 GO GO GO ,出发喽");
                // 数量-1
                cdl.countDown();
            },String.valueOf(i)).start();
        }

        // 等待计数器归零,然后再向下执行
        // 如果没有这个等待,直接将门关了,后面的也就出不去了
        // 这里关闭的顺序不是按照次序的,但是都是保证全部出去后再关闭
        // cdl.await();
        System.out.println("关门了");

        // 倘若我需要将大的进程关闭,得等线程全部关闭才能关闭,可以使用该倒计时
        // -1操作
//        cdl.countDown();

    }
}

原理
cdl.countDown(); 数量-1
cdl.await(); 等待计数器归零
每次都有线程调用countDown()方法,数量-1,假设计数器归零,await()方法就会被唤醒,继续执行

CyclicBarrier

加法计算器

package javaSEStudy.JUC.JUCDemo.assistant;

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

// 加法计算器
public class CyclicBarrier_ {
    public static void main(String[] args) {
        /**
         * 集齐7颗龙珠召唤神龙
         */

        // 召唤龙珠的线程
        CyclicBarrier cb = new CyclicBarrier(7, () -> {
            System.out.println("召唤神龙成功");
        });

        for (int i = 1; i <= 7; i++) {
            final int temp = i;
            // lambda 能操作到 i 吗,无法直接拿到
            new Thread(() -> {
                System.out.println(Thread.currentThread().getName() + "收集了" + temp + "颗龙珠");

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

Semaphore

话不多说,直接上代码

package javaSEStudy.JUC.JUCDemo.assistant;

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

// 信号量详解
public class Semaphore_ {
    public static void main(String[] args) {
        // 需要参数 线程数量
        // 限流的时候可以使用
        Semaphore semaphore = new Semaphore(3);

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

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

读写锁

ReadWriteLock

package javaSEStudy.JUC.JUCDemo.readWriteLock;

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

/**
 * 独占锁(写锁)  一次只能被一个线程占有
 * 共享锁(读锁)  多个线程可以同时占有
 * 读写锁详解 写入只允许一个线程写入,读的时候可以多线程读
 * 读 - 读 可以共存!
 * 读 - 写 不能共存
 * 写 - 写 不能共存
 */
public class ReadWriteLock_ {
    public static void main(String[] args) {
        // 发生并发异常
//        MyCache1 myCache2 = new MyCache1();
//
//        // 进行写入操作
//        for (int i = 1; i <= 5; i++) {
//            final int temp = i;
//            new Thread(()->{
//                myCache2.put(temp+"",temp+"");
//            },String.valueOf(i)).start();
//        }
//        // 进行读取操作
//        for (int i = 1; i <= 5; i++) {
//            final int temp = i;
//            new Thread(()->{
//                myCache2.get(temp+"");
//            },String.valueOf(i)).start();
//        }

        MyCache2 myCache2 = new MyCache2();
        // 进行写入操作
        for (int i = 1; i <= 5; i++) {
            final int temp = i;
            new Thread(() -> {
                myCache2.put(temp + "", temp + "");
            }, String.valueOf(i)).start();
        }
        // 进行读取操作
        for (int i = 1; i <= 5; i++) {
            final int temp = i;
            new Thread(() -> {
                myCache2.get(temp + "");
            }, String.valueOf(i)).start();
        }
    }
}

/**
 * 自定义缓存
 */
class MyCache1 {
    // volatile 保证原子性
    private volatile Map<String, Object> map = new HashMap<>();

    // 存入数据  写入
    public void put(String key, Object value) {
        // 多线程一定要注意是那条线程进入
        System.out.println(Thread.currentThread().getName() + "写入" + key);
        map.put(key, value);
        System.out.println(Thread.currentThread().getName() + "写入完毕");
    }

    // 取出数据  读取
    public void get(String key) {
        System.out.println(Thread.currentThread().getName() + "读取" + key);
        Object o = map.get(key);
        System.out.println(Thread.currentThread().getName() + "读取完毕");
    }
}

/**
 * 自定义缓存
 * 加了锁版本
 */
class MyCache2 {
    // volatile 保证原子性
    private volatile Map<String, Object> map = new HashMap<>();
    // 读写锁,更加细粒度控制
    private ReadWriteLock lock = new ReentrantReadWriteLock();

    // 存入数据  写入
    public void put(String key, Object value) {
        // 多线程一定要注意是那条线程进入
        lock.writeLock().lock();
        try {
            System.out.println(Thread.currentThread().getName() + "写入" + key);
            map.put(key, value);
            System.out.println(Thread.currentThread().getName() + "写入完毕");
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.writeLock().unlock();
        }
    }

    // 取出数据  读取
    public void get(String key) {
        lock.readLock().lock();
        try {
            System.out.println(Thread.currentThread().getName() + "读取" + key);
            Object o = map.get(key);
            System.out.println(Thread.currentThread().getName() + "读取完毕");
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.readLock().unlock();
        }
    }
}

阻塞队列

先进先出

阻塞队列

BlockingQueue属于Collection集合框架,与List和Set同级,实现子类也是ArrayBlockingQueue和LinkedBlockingQueue

阻塞队列不是新的东西
什么情况下会使用阻塞队列
多线程并发处理,线程池

学会使用队列: 添加,移除,判断
四组API

  • 抛出异常
  • 不会抛出异常
  • 等待阻塞
  • 超时等待
方式 抛出异常 不会抛出异常,有返回值 等待,阻塞 超时等待
添加 add() offer() put() offer( , , )
移除 remove() poll() take() poll( , )
检查队首元素 element peek - -
package javaSEStudy.JUC.JUCDemo.blockingQueue_;

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

// 阻塞队列
@SuppressWarnings({"all"})
public class BlockingQueue_ {
    public static void main(String[] args) throws InterruptedException {

        /**
         * 父类: Collection
         * List,Set,BlockingQueue   这三个同级
         */
//        test1();
//        test2();
//        test3();
        test4();

    }

    // 四种API
    /**
     * 抛出异常
     */
    public static void test1() {
        // 括号里面是队列大小
        ArrayBlockingQueue abq = new ArrayBlockingQueue<>(3);

        // add()返回值是 布尔值
        System.out.println(abq.add("A"));
        System.out.println(abq.add("B"));
        System.out.println(abq.add("C"));

        // 此时再添加一个元素
        // 抛出异常:IllegalStateException: Queue full  队列已满
//        System.out.println(abq.add("D"));

        // 查看队首元素
        System.out.println(abq.element());

        System.out.println("----------------------------");

        // remove() 会弹出当前这个值,FIFO 先进先出
        System.out.println(abq.remove());
        System.out.println(abq.remove());
        System.out.println(abq.remove());

        // 现在队列是空的,再去取值,会抛出异常 NoSuchElementException 没有可搜索元素异常
//        System.out.println(abq.remove());

    }

    /**
     * 不抛出异常,但是有返回值
     */
    public static void test2() {
        ArrayBlockingQueue abq = new ArrayBlockingQueue<>(3);
        System.out.println(abq.offer("A"));
        System.out.println(abq.offer("B"));
        System.out.println(abq.offer("C"));

        // 容量只有3个,前三个返回true,但是当无法加入时,则返回false,不会发生异常
//        System.out.println(abq.offer("D"));

        // 检查队首元素
        System.out.println(abq.peek());

        System.out.println("--------------------");

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

        // 只有三个,当三个全部取出,会返回null
        System.out.println(abq.poll());
    }

    /**
     * 等待,阻塞
     */
    public static void test3() throws InterruptedException {
        ArrayBlockingQueue abq = new ArrayBlockingQueue<>(3);

        // 加入,没有返回值,也就无法sout
        // 会一直阻塞
        abq.put("A");
        abq.put("B");
        abq.put("C");

        // 在添加一个元素,会一直等待
//        abq.put("D");

        System.out.println("--------------------");

        // 取出操作,有返回值,为当前元素
        System.out.println(abq.take());
        System.out.println(abq.take());
        System.out.println(abq.take());

        // 此时已经没有元素可取出,所以一直阻塞
        System.out.println(abq.take());
    }

    /**
     * 等待超时,自动退出
     */
    public static void test4() throws InterruptedException {
        ArrayBlockingQueue abq = new ArrayBlockingQueue<>(3);

        // 加入,也是offer,但是需要添加时间参数
        // 第一个参数为添加内容,第二个参数为等待时间,第三个为等待时间单位
        System.out.println(abq.offer("A"));
        System.out.println(abq.offer("B"));
        System.out.println(abq.offer("C"));

        // 此时再添加一个元素,但是让他进行超时等待
        // 加入等待两秒,倘若无法加入,则自动退出
        System.out.println(abq.offer("D", 2, TimeUnit.SECONDS));

        System.out.println("----------------------");

        // 取出
        System.out.println(abq.poll());
        System.out.println(abq.poll());
        System.out.println(abq.poll());

        // 此时只有三个参数,无法再取出
        // 两个参数,第一个参数为等待时间,第二个参数为等待时间单位
        System.out.println(abq.poll(2, TimeUnit.SECONDS));
    }
}

SynchronizedQueue 同步队列

没有容量,进去一个元素必须等待取出后才能再放入
put, take

直接上个栗子

package javaSEStudy.JUC.JUCDemo.blockingQueue_;

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

// 同步队列详解

/**
 * 与其他的BlockingQueue不一样
 * SynchronizedQueue不存储元素
 * put了一个元素,必须从里面先take出来,才能继续put    
 */
public class SynchronizedQueue_ {
    public static void main(String[] args) {
        SynchronousQueue<String> soq = new SynchronousQueue<>();

        new Thread(() -> {
            try {
                soq.put("1");
                System.out.println(Thread.currentThread().getName() + " put 1");
                soq.put("2");
                System.out.println(Thread.currentThread().getName() + " put 2");
                soq.put("3");
                System.out.println(Thread.currentThread().getName() + " put 3");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }, "A").start();

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

线程池

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

池化技术:程序的运行,本质:占用系统的资源,优化资源的使用. --> 池化技术
线程池,连接池,内存池,对象池... // 创建和销毁十分浪费资源
池化技术:事先准备好一些资源,有人要用,就来该地方来拿,用完之后再还到该地方

默认大小为 2

线程池的好处:

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

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

三大方法

package javaSEStudy.JUC.JUCDemo.pool;

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

// 线程池的三大方法
public class PoolMethod {
   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 <= 100; i++) {
               // 使用了线程池之后,用线程池来创建线程
               threadPool.execute(()->{
                   System.out.println(Thread.currentThread().getName() + "执行");
               });
           }
       } catch (Exception e) {
           e.printStackTrace();
       } finally {
           // 线程池使用完毕需要将其关闭
           threadPool.shutdown();
       }
   }
}

七大参数

package javaSEStudy.JUC.JUCDemo.pool;

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

// 七大参数详解
public class PoolParameter {
    public static void main(String[] args) {

        /**
         * 使用 Executors 去创建一个线程池
         * 本质: new ThreadPoolExecutor
         */

        // 单例模式,创建只有一个线程的线程池
        ExecutorService threadPool1 = Executors.newSingleThreadExecutor();

        // 创建固定大小的线程池,需要给定参数,参数为创建线程池的大小
        ExecutorService threadPool2 = Executors.newFixedThreadPool(5);

        // 创建一个可伸缩,具有弹性的线程池,线程池大小随任务改变而改变
        ExecutorService threadPool3 = Executors.newCachedThreadPool();

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

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

        /**
         * newCachedThreadPool() 源码
         *
         * Integer.MAX_VALUE 约等于 21亿 OOM溢出
         *
         * public static ExecutorService newCachedThreadPool() {
         *         return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
         *                                       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;
         *     }
         */
    }
}

四种拒绝策略

package javaSEStudy.JUC.JUCDemo.pool;

import java.util.concurrent.*;

// 线程池的拒绝策略
public class PoolRefuse {
    public static void main(String[] args) {

        // 手动创建一个线程池
        ThreadPoolExecutor threadPool = new ThreadPoolExecutor(2,
                5,
                3,
                TimeUnit.SECONDS,
                // 阻塞队列最多存放三条数据
                new LinkedBlockingQueue<>(3),
                // 调用Executors 默认的线程工厂
                Executors.defaultThreadFactory(),

                // 默认的拒绝策略(办理业务线程满了,现在还有线程想进来,不处理该请求)
                // 抛出异常 RejectedExecutionException
//                new ThreadPoolExecutor.AbortPolicy()

                // 哪儿来的去哪里,这里多出来的两个线程来自main线程,这里由main线程处理
//                new ThreadPoolExecutor.CallerRunsPolicy()

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

                // 队列满了,尝试和最早的竞争,也不会抛出异常
                // 倘若竞争失败就抛出任务,竞争成功就执行该任务
                new ThreadPoolExecutor.DiscardOldestPolicy()


        );

        try {
            // 最大承载数: 最大线程数+阻塞队列
            for (int i = 1; i <= 10; i++) {
                threadPool.execute(() -> {
                    System.out.println(Thread.currentThread().getName()+" 完成 ");
                });
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            // 关闭线程池
            threadPool.shutdown();
        }
    }
}

小结和拓展

最大线程到底该如何定义? (调优)

  1. CPU 密集型 电脑几个核心,最大线程就是几,CPU效率最高(电脑是12核心,12条线程同时运行)
  2. IO 密集型 判断程序中十分耗费IO的线程有多少个,其最大线程数就会大于该线程数
package javaSEStudy.JUC.JUCDemo.pool;

import java.util.concurrent.*;

// 自定义线程
public class PoolRefuse {
    public static void main(String[] args) {

        // 自定义线程池

        // 最大线程池如何定义
        // 1. CPU 密集型   电脑几个核心,最大线程就是几,CPU效率最高(电脑是12核心,12条线程同时运行)
        // 2. IO  密集型   判断程序中十分耗费IO的线程有多少个,其最大线程数就会大于该线程数

        // 获取CPU核数
        System.out.println(Runtime.getRuntime().availableProcessors());

        ThreadPoolExecutor threadPool = new ThreadPoolExecutor(2,
                Runtime.getRuntime().availableProcessors(),
                3,
                TimeUnit.SECONDS,
                // 阻塞队列最多存放三条数据
                new LinkedBlockingQueue<>(3),
                // 调用Executors 默认的线程工厂
                Executors.defaultThreadFactory(),

                // 默认的拒绝策略(办理业务线程满了,现在还有线程想进来,不处理该请求)
                // 抛出异常 RejectedExecutionException
//                new ThreadPoolExecutor.AbortPolicy()

                // 哪儿来的去哪里,这里多出来的两个线程来自main线程,这里由main线程处理
//                new ThreadPoolExecutor.CallerRunsPolicy()

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

                // 队列满了,尝试和最早的竞争,也不会抛出异常
                // 倘若竞争失败就抛出任务,竞争成功就执行该任务
                new ThreadPoolExecutor.DiscardOldestPolicy()


        );

        try {
            // 最大承载数: 最大线程数+阻塞队列
            for (int i = 1; i <= 100; i++) {
                threadPool.execute(() -> {
                    System.out.println(Thread.currentThread().getName()+" 完成 ");
                });
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            // 关闭线程池
            threadPool.shutdown();
        }
    }
}

四大函数式接口(重点)

必会技能:Lambda表达式,链式编程,函数式接口,Stream流式计算

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

@FunctionalInterface
public interface Runnable {

    public abstract void run();
}

作用:简化编程模型,在新版本的框架底层大量应用
foreach(消费者类的函数式接口)

Function(函数式接口)

package javaSEStudy.JUC.JUCDemo.functionalInterface_;

import java.util.function.Function;

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

        /**
         * Function 源码
         * 传入参数T,返回类型R
         *
         * @FunctionalInterface
         * public interface Function<T, R> {
         *  R apply (T t);
         * }
         */
        new Function<String, String>() {
            @Override
            public String apply(String str) {
                return str;
            }
        };

        // 对上述代码进行lambda表达式简化
        Function<String, String> function = (str) -> {
            return str;
        };

    }
}

Predicate(断定型接口)

package javaSEStudy.JUC.JUCDemo.functionalInterface_;

import java.util.function.Predicate;

// Predicate详解

/**
 * Predicate 断定型接口:有一个输入参数,返回值只能为Boolean
 */
public class Predicate_ {
    public static void main(String[] args) {
        /**
         * Predicate 源码
         *只有一个输入参数,返回值为Boolean
         *
         * @FunctionalInterface
         * public interface Predicate<T> {
         *
         *  boolean test (T t);
         * }
         */
        // 判断字符串是否为空
        Predicate<String> op1 = new Predicate<>() {
            @Override
            public boolean test(String str) {
                return str.isEmpty();
            }
        };

        // 使用Lambda表达式简化
        Predicate<String> op2 = str -> {
            return str.isEmpty();
        };

        // 输出 true,未放入参数
        System.out.println(op1.test(""));
    }
}

Consumer(消费型接口)

package javaSEStudy.JUC.JUCDemo.functionalInterface_;

import java.util.function.Consumer;

/**
 * Consumer 消费型接口,只有输入参数,没有返回值
 */
public class Consumer_ {
    public static void main(String[] args) {

        /**
         * Consumer 源码
         * 只有输入参数T,没有返回值
         *
         * @FunctionalInterface
         * public interface Consumer<T> {
         *
         *    void accept (T t);
         * }
         */
        Consumer<String> consumer1 = new Consumer<>() {
            @Override
            public void accept(String str) {
                System.out.println(str);
            }
        };

        // 使用Lambda表达式简化
        Consumer<String> consumer2 = (str) -> {
         System.out.println(str);
        };
        // 打印字符串
        consumer1.accept("Hello World");
    }
}

Supplier(供给型接口)

package javaSEStudy.JUC.JUCDemo.functionalInterface_;

import java.util.function.Supplier;

/**
 * Supplier 供给型接口 只有返回值,没有输入参数
 */
public class Supplier_ {
    public static void main(String[] args) {

        /**
         * Supplier 源码
         * 没有参数,只有返回值
         *
         * @FunctionalInterface
         * public interface Supplier<T> {
         *   T get ();
         * }
         */
        Supplier<String> supplier1 = new Supplier<>() {
            @Override
            public String get() {
                return "你好,世界";
            }
        };

        // 使用Lambda表达式简化
        Supplier<String> supplier2 = ()->{
          return "你好,世界";
        };

        // 创建输出
        System.out.println(supplier1.get());
    }
}

Stream流式计算

什么是Stream流式计算
大数据 : 存储 + 计算

获得流

怎么得到一条Stream流

获取方式 方法名 说明
单列集合 default Stream stream() Colleation中的默认方法
双列集合 无法直接使用Stream(使用需转为keySet() entrySet())
数组 public static Stream stream(T[] array) Arrays工具类中的静态方法
零散数据 public static Stream of(T...value) Stream接口中的静态方法,需要同种数据类型

直接上代码展示

package javaSEStudy.JUC.JUCDemo.stream;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.stream.Stream;

// 获得Stream流
public class FetchStream {
    public static void main(String[] args) {

        /**
         * 1.单列获得Stream流
         */
        ArrayList<String> list = new ArrayList<>();
        Collections.addAll(list, "a", "b", "c", "d", "e");
        // 获取一条流水线,并将集合中的数据放到流水线上
        // 因为ArrayList是Collection的实现子类,可以直接使用
        Stream<String> stream = list.stream();
        // 使用终结方法打印流水线上的所有数据
        stream.forEach(System.out::println);
        // 将上述两条代码使用链式调用
        list.stream().forEach(System.out::println);

        /**
         * 2.双列获得Stream流
         */
        HashMap<String, Integer> sihm = new HashMap<>();
        sihm.put("a", 1);
        sihm.put("b", 2);
        sihm.put("c", 3);
        sihm.put("d", 4);
        // 使用keySet获取Stream流
        sihm.keySet().stream().forEach(System.out::println);
        // 使用entrySet获取Stream流
        sihm.entrySet().stream().forEach(System.out::println);
        System.out.println("---------------");
        sihm.entrySet().stream().forEach(e -> System.out.println(e.getKey() + " " + e.getValue()));

        /**
         * 3.数组获得Stream流
         */
        int[] arr1 = {1, 2, 3, 4, 5};
        String[] arr2 = {"a", "b", "c", "d", "e"};
        Arrays.stream(arr1).forEach(System.out::println);
        Arrays.stream(arr2).forEach(System.out::println);

        /**
         * 4.一堆零散数据,但是数据类型需要相同,才能获得Stream流
         *
         * Stream.of() 使用细节
         * 方法的形参是一个可变参数,可以传递一堆零散的数据,也可以传递数组
         * 但是数组必须是引用数据类型(String),不能是基本数据类型(int),否则将整个数组当做一个元素返回一个地址值
         */
        Stream.of(1,2,3,4,5).forEach(System.out::println);
        System.out.println("---------------");
        Stream.of("a","b","c").forEach(System.out::println);

    }
}

流的中间方法

常见方法

  • filter 过滤
  • limit 获取前几个元素
  • skip 跳过前几个元素(只获取后面的元素)
  • distinct 元素去重(依赖HashCode和equals方法)
  • concat 将两个流合并为一个流
  • map 转换流中的元素类型

注意:
1.中间方法,返回新的Stream流,原来的Stream流只能使用一次,建议使用链式编程
2.修改Stream流中的数据,不会影响原来集合或者数组中的数据

代码展示

package javaSEStudy.JUC.JUCDemo.stream;

import java.util.ArrayList;
import java.util.Collections;
import java.util.function.Function;
import java.util.stream.Stream;

// Stream流的中间方法
public class StreamMiddleMethod {
    public static void main(String[] args) {

        ArrayList<String> list1 = new ArrayList<>();
        Collections.addAll(list1, "张无忌", "周芷若", "赵敏", "张强", "张三丰",
                "张翠山", "张亮", "张亮", "蔡徐坤", "蔡徐坤", "蔡徐坤", "蔡徐坤");

        ArrayList<String> list2 = new ArrayList<>();
        Collections.addAll(list2, "基尼太美", "哎哟你干嘛");

        // filter 过滤  张开头留下,其余不要
        list1.stream()
                .filter((s)-> s.startsWith("张"))
                .filter(s -> s.length() > 2)
                .forEach(System.out::println);

        System.out.println("--------------------");

        // limit 获取前几个元素
        // skip  跳过前几个元素,获取后面的元素
        list1.stream()
                .limit(6)
                .skip(2)
                .forEach(System.out::println);

        System.out.println("---------------------");

        // distinct   元素去重
        list1.stream()
                .distinct()
                .forEach(System.out::println);

        System.out.println("----------------------");

        // concat     合并两个流为一个流
        Stream.concat(list1.stream(), list2.stream())
                .distinct()
                .forEach(System.out::println);

        System.out.println("----------------------");

        // map   转换流中的数据类型
        // 需求:只获取年龄进行打印 String -> int
        ArrayList<String> list3 = new ArrayList<>();
        Collections.addAll(list3, "张无忌-15", "周芷若-18", "赵敏-18", "张强-2", "张三丰-99", "蔡徐坤-2.5");

        // 当map方法执行完毕后,流上的数据就变成了整数,forEach中的数据现在就是整数了
        list3.stream().map(new Function<String, Integer>() {
            @Override
            public Integer apply(String s) {
                String[] arr = s.split("-");
                String ageString = arr[1];
                int age = Integer.parseInt(ageString);
                return age;
            }
        }).forEach(System.out::println);

        // 让我们将上述代码使用lambda表达式简化
        list3.stream()
                .map(s ->{return Integer.parseInt(s.split("-")[1]);})
                .forEach(System.out::println);

    }
}

流的终结方法

常用方法

  • foreach 遍历
  • count 统计
  • toArray 收集流中的数据,放到数组中
  • collect(Collector collecor) 收集流中的数据放到集合中
package javaSEStudy.JUC.JUCDemo.stream;

import java.util.*;
import java.util.function.Function;
import java.util.function.IntFunction;
import java.util.stream.Collectors;

// Stream流的终结方法
@SuppressWarnings({"all"})
public class StreamEndMethod {
    public static void main(String[] args) {

        ArrayList<String> list = new ArrayList<>();
        Collections.addAll(list, "张无忌-男-14", "周芷若-女-18", "赵敏-女-16", "张强-男-12", "张三丰-男-99",
                "张翠山-女-23", "张亮-男-24", "蔡徐坤-男-2.5");

        // forEach   遍历
        list.stream().forEach(System.out::println);

        // count     统计
        System.out.println(list.stream().count());


        // toArray  将流里的数据收集为一个数组
        // 无参构造,保存为Object类型,在将其转换为String显示
        Object[] array = list.stream().toArray();
        System.out.println(Arrays.toString(array));

        // 有参构造,IntFunction的泛型:具体类型的数组
        // apply的形参:流中数据的个数,要跟数组的长度保持一致
        // apply的返回值:具体类型的数组
        // 方法体:创建数组

        // toArray方法的参数的作用:负责创建一个指定类型的数组
        // toArray方法的底层,会依次得到流中的每个数据,并把数据方法数组中
        // toArray方法的返回值:是一个装着流中所有数据的数组
        String[] arr = list.stream().toArray(new IntFunction<String[]>() {
            @Override
            public String[] apply(int value) {
                return new String[value];
            }
        });
        System.out.println(Arrays.toString(arr));

        // 将上述代码简化
        String[] arr1 = list.stream().toArray((value) -> {
            return new String[value];
        });
        System.out.println(Arrays.toString(arr1));

        System.out.println("------------------------------------");

        // collect(Collector collector)  收集流中的数据,放到集合(List Set Map)

        /**
         * 收集到List中
         * 需求:收集男性
         */
        List<String> newList = list.stream()
                .filter(s -> "男".equals(s.split("-")[1]))
                .collect(Collectors.toList());

//        System.out.println(newList);

        /**
         * 收集到Set中
         * 需求:收集女性
         */
        Set<String> newSet = list.stream()
                .filter(s -> "女".equals(s.split("-")[1]))
                .collect(Collectors.toSet());

//        System.out.println(newSet);

        /**
         * 收集到Map中
         * 注意: 如果我们要收集数据到Map中,键不能重复,否则会报错
         *
         * 需求: 谁为键,谁为值
         * 姓名:键  年龄:值  收集男性
         *
         * toMap 参数一表示键的生成规则 参数二表示值的生成规则
         *
         *      参数一:
         *              Function 泛型一:流中每一个数据的类型
         *                       泛型二:Map集合中键的数据类型
         *              方法apply:依次表示流中每一个数据  张无忌-男-14
         *                  方法体: 生成键  张无忌
         *                  返回值: 已经生成的键
         *
         *       参数二:
         *              Function 泛型一:流中每一个数据的类型
         *                       泛型二:Map集合中值的数据类型
         *              方法apply:依次表示流中每一个数据  张无忌-男-14
         *              方法体: 生成键  14
         *              返回值: 已经生成的值
         */
        Map<String, Double> map1 = list.stream()
                .filter(s -> "男".equals(s.split("-")[1]))
                .collect(Collectors.toMap(new Function<String, String>() {
                    @Override
                    public String apply(String s) {
                        return s.split("-")[0];
                    }
                }, new Function<String, Double>() {
                    @Override
                    public Double apply(String s) {
                        return Double.parseDouble(s.split("-")[2]);
                    }
                }));

        // 让我们将上述代码简化
        Map<String, Double> map2 = list.stream()
                .filter(s -> "男".equals(s.split("-")[1]))
                .collect(Collectors.toMap(s -> s.split("-")[0], s -> Double.parseDouble(s.split("-")[2])));

        System.out.println(map2);

    }
}

小结

使用案例

package javaSEStudy.JUC.JUCDemo.stream.test;

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

/**
 * 要求
 * 1.ID必须为偶数
 * 2.年龄必须大于23
 * 3.用户名转为大写
 * 4.用户名字母倒序输出
 * 5.只输出一个用户
 */
public class Test {
    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(5, "e", 25);
        User u6 = new User(6, "f", 26);

        // 集合就是存储
        List<User> list = Arrays.asList(u1, u2, u3, u4, u5, u6);

        // 计算交给Stream  filter 过滤
        // 链式编程
        list.stream()
                // ID必须为偶数
                .filter(u -> {return u.getId() % 2 == 0;})
                // 年龄必须大于23
                .filter(u -> {return u.getAge() > 23;})
                // 用户名转为大写
                .map(u ->{return u.getName().toUpperCase();})
                // 用户名字母倒序输出,无参为正序输出
                .sorted((n1,n2) ->{return n2.compareTo(n1);})
                // 只输出一个用户 limit 限制流中输出数量
                .limit(1)
                // 遍历输出
                .forEach(System.out::println);
    }
}

forkJoin

什么是forkJoin
里面维护的都是双端队列
并行执行任务,提高效率(大数据量)

批处理

forkJoin特点: 工作窃取

解释:
当A的任务未完成,B的任务完成,B不会原地等待,会去将A的任务拿过来执行

使用方法

我们直接上代码

package javaSEStudy.JUC.JUCDemo.forkJoin_;

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

// 求和计算的三种方式
public class Test {
    public static void main(String[] args) throws ExecutionException, InterruptedException {

        // 消耗时间 4632
//        test1();

        // 消耗时间 4307
//        test2();

        // 消耗时间 72
        test3();

    }

    // 传统写法
    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 +"消耗时间 "+(end - start));
    }

    // 使用ForkJoin
    public static void test2() throws ExecutionException, InterruptedException {
        long start = System.currentTimeMillis();

        ForkJoinPool forkJoinPool = new ForkJoinPool();
        ForkJoin_ task = new ForkJoin_(0L, 10_0000_0000L);
        // 执行任务,没有返回值
//        forkJoinPool.execute(task);
        // 提交任务,有返回值
        ForkJoinTask<Long> submit = forkJoinPool.submit(task);

        // 在这里会阻塞等待
        Long sum = submit.get();

        long end = System.currentTimeMillis();

        System.out.println("和 "+ sum +"消耗时间 "+(end - start));
    }

    // 使用Stream并行流
    public static void test3(){
        long start = System.currentTimeMillis();

        // range -> 开区间 ()  rangeClosed() -> 左开右闭 (]
        // parallel() 并行计算  reduce()  拿出结果
        long sum = LongStream.rangeClosed(0L, 10_0000_0000L).parallel().reduce(0, Long::sum);

        long end = System.currentTimeMillis();
        System.out.println("和 "+ sum +"消耗时间 "+(end - start));
    }
}

异步回调

Future : 对将来某个事件的结果进行建模!

package javaSEStudy.JUC.JUCDemo.future_;

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

// 异步调用: ajax
// 异步执行 成功回调 失败回调
public class Future_ {
   public static void main(String[] args) throws ExecutionException, InterruptedException {

       // 发起一个请求 无返回值 runAsync进行异步回调
       CompletableFuture<Void> cf1 = CompletableFuture.runAsync(() -> {
           try {
               TimeUnit.SECONDS.sleep(2);
           } catch (InterruptedException e) {
               e.printStackTrace();
           }
           System.out.println(Thread.currentThread().getName() + " Void 无返回值");
       });

       System.out.println("我先执行输出");
       // 获取执行结果
       cf1.get();

       System.out.println("---------------------------------------");

       /**
        * success code 200
        * error code 404 500
        */

       // 发起一个请求 有返回值 supplyAsync进行异步回调
       // ajax 成功和失败的回调
       // 失败会返回错误信息
       CompletableFuture<Integer> cf2 = CompletableFuture.supplyAsync(() -> {
           System.out.println(Thread.currentThread().getName() + " Integer 有返回值");
           // 当编译错误时
//            int i = 1 / 0;
           return 1024;
       });

       // whenComplete编译成功返回的结果
       cf2.whenComplete((t,u) -> {
           System.out.println(t);  // t 正确的返回结果
           System.out.println(u);  // u 错误信息
       })
              // 编译失败返回的结果
               .exceptionally((e)->{
                   System.out.println(e.getMessage());
                   return 404;  // 可以获取到错误返回结果
               }).get();
   }
}

理解JMM

什么是JMM?
JVM -> java虚拟机
JMM -> java内存模型,是一个不存在的东西,是一种约定,概念

关于JMM的一些同步约定

  • 1.线程解锁前: 必须把共享变量立刻刷回主存
    解释:线程有自己的工作内存,操作一个变量会从主存拷贝变量到自己的工作内存,然后对该变量进行操作,如果要解锁,必须将该变量重新在主存中刷新,确保两个变量保持一致
  • 2.线程加锁前,必须读取主存中的最新值到工作内存中
  • 3.加锁和解锁是同一把锁

线程: 工作内存,主存
八个操作

数据同步八大原子操作

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

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

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

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

(5)use(使用):作用于工作内存的变量,把工作内存中的一个变量值传递给执行引擎

(6)assign(赋值):作用于工作内存的变量,它把一个从执行引擎接收到的值赋给工作内存的变量

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

(8)write(写入):作用于工作内存的变量,它把store操作从工作内存中的一个变量的值传送到主内存的变量中

如果一个变量从主内存中复制到工作内存中,就需要按顺序的执行read、load指令,如果是工作内存刷新到主内存则需要按顺序的执行store、write操作。但JMM只保证了顺序执行,并没有保证连续执行。

原子操作
同步规则分析

  • 不允许线程无原因的将变量从工作内存写回主内存(没有经过任何的assign)。
  • 一个新的变量只能在主内存中诞生,不允许在工作内存中直接使用一个未被初始化的变量(load或者assign)的变量,就是对一个变量进行use和store操作之前,必须先自行load与assign操作。
  • 一个变量在同一时刻只能被一个线程lock,同一个线程能多次lock,必须对应的多次unlock,lock与unlock必须成对的出现。
  • 如果对一个变量执行lock操作,将会清除工作内存中此变量的值,在执行引擎使用这个变量时,必须重新load、assign操作。
  • 如果一个线程对一个变量没有进行lock操作,则不允许unlock,也不允许对其他线程进行unlock。
  • 对一个变量进行unlock时,必须先将变量刷新到主内存。

可见性,原子性与有序性
原子性
原子性指的是一个操作是不可中断的,即使是在多线程环境下,一个操作一旦开始就不会被其他线程给打断。

可见性
可见性指的是当一个共享变量被一个线程修改后,其他线程是否能够立即感知到。对于串行执行的程序是不存在可见性,当一个线程修改了共享变量后,后续的线程都能感知到共享变量的变化,也能读取到最新的值,所以对于串行程序来讲是不存在可见性问题。

对于多线程程序,就不一定了,前面分析过对于共享变量的操作,线程都是将主内存的变量copy到工作内存进行操作后,在赋值到主内存中。这样就会导致,一个线程改了之后还未回写到主内存,其余线程就无法感知到变量的更新,线程之间的工作内存是不可见的。另外指令重排序以及编译器优化也会导致可见性的问题。

有序性
有序性是指对于单线程的代码,我们总是认为程序是按照代码的顺序进行执行,对于单线程的场景这样理解是没有问题,但是在多线程情况下, 程序就会可能发生乱序的情况,编译器编译成机器码指令后,指令可能会被重排序,重排序的指令并不能保证与没有排序前的保持一致。

在java程序中,倘若在本线程内,所有的操作都可视为有序性,在多线程环境下,一个线程观察另外一个线程,都视为无顺序可言。

问题 :程序不知道主内存中的值已经被更改

package javaSEStudy.JUC.JUCDemo.volatile_;

import java.util.concurrent.TimeUnit;

// Volatile 有关问题
public class VolatileQuestion {

    // 定义一个可操作变量
    private static int num = 0;
    public static void main(String[] args) {

        // 制定一个线程,上面是main线程
        // 未结束死循环说明该线程对主存变化不可知
        new Thread(() -> {
            while (num == 0){

            }
        }).start();

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

        num = 1;
        System.out.println(num);

        /**
         * 问题?
         *      while死循环,下面已经将num值更改,但是循环并没有停下来,证明修改的值未被该子线程读取
         */
    }
}

Volatile

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

  • 保证可见性
  • 不保证原子性(不可分割)
  • 静止指令重排

如果不加Synchronized和lock,如何保证原子性

使用原子类解决解决原子性问题
Unsafe是一个很特殊的存在
这些类的底层都直接和操作系统挂钩,在内存中修改值,所以比Lock高效

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

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

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

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

执行顺序: 期望 -> 1234 可能为2134 1324 
不可能: 4132    

前提条件 a b x y = 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实现了可见性

可见性

package javaSEStudy.JUC.JUCDemo.volatile_;

import java.util.concurrent.TimeUnit;

//Volatile 详解
public class Volatile_ {
    // 定义一个可操作变量,加volatile保证可见性
    private volatile static int num = 0;
    public static void main(String[] args) {

        // 制定一个线程,上面是main线程
        // 加入volatile关键字后死循环结束,证明可见
        new Thread(() -> {
            while (num == 0) {

            }
        }).start();

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

        num = 1;
        System.out.println(num);

        /**
         * 问题?
         *      while死循环,下面已经将num值更改,但是循环并没有停下来,证明修改的值未被该子线程读取
         */
    }
}

不保证原子性
线程在执行任务的时候不可被打扰,也不可被分割,要么同时成功,要么同时失败

package javaSEStudy.JUC.JUCDemo.volatile_;

import java.util.concurrent.atomic.AtomicInteger;

// Volatile 不保证原子性
public class VolatileAtomicity {

    // 即使加入了Volatile也很难到达两万
//    private volatile static int num = 0;

    // 使用原子类解决原子性问题
    private volatile static AtomicInteger num = new AtomicInteger();

    // 加入关键字Synchronized可保证达到两万
    public  static void add(){
        // 这不是一个原子性操作
//        num++;

        // AtomicInteger 的+1 方法,底层使用CAS
        num.getAndIncrement();
    }

    public  static void main(String[] args) {

        // 理论上num结果为2万,但是由于不保证原子性,很难达到两万
        for (int i = 1; i <= 20; i++) {
            new Thread(() -> {
                for (int j = 1; j <= 1000; j++) {
                    add();
                }
            }).start();
        }

        // 会有两条线程默认执行 main gc
        // 倘若下面可以执行,证明上面线程未执行完毕
        while (Thread.activeCount() > 2) {
            Thread.yield();
        }
        System.out.println(Thread.currentThread().getName() + " :" + num);
    }
}

单例模式

饿汉式
容易导致资源浪费

package javaSEStudy.JUC.JUCDemo.single;

/**
 * 饿汉式
 *  一上来就将对象加载
 */
public class Hungry {

    // 一上来就将对象全部加载
    // 此处内存空间未被利用,导致内存空间浪费
    private byte[] byte1 = new byte[1024];
    private byte[] byte2 = new byte[1024];
    private byte[] byte3 = new byte[1024];

    private Hungry() {

    }

    private final static Hungry HUNGRY = new Hungry();

    private static Hungry getInstance(){
        return HUNGRY;
    }
}

懒汉式
单线程ok,但是多线程需要加锁,双重(锁+volatile)检测机制,但是可以使用反射破坏
加锁也可以防止反射破坏,但是两个对象都是用反射获取,,使用信号灯去防止破坏,但是也可以使用反射去获取信号灯的值再去破坏单例模式

package javaSEStudy.JUC.JUCDemo.single;

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

/**
 * 懒汉式
 * 用的时候再去加载
 */
public class Lazy {

    // 设置一个红绿灯,避免下面两个反射去破坏
    private static boolean flag = false;

    private Lazy() {
        System.out.println(Thread.currentThread().getName() + " OK");

        // 加锁可以防止反反射破坏
        // 加入信号灯防止两个反射破坏
        synchronized (Lazy.class) {
            if (flag == false) {
                flag = true;
            } else {
                throw new RuntimeException("不要使用反射破坏");
            }

//            if (lazy!=null) {
//                throw new RuntimeException("不要使用反射破坏");
//            }
        }
    }

    private volatile static Lazy lazy;

    // 单线程下情况ok
//    public static Lazy getInstance(){
//        if(lazy == null){
//            lazy = new Lazy();
//        }
//        return lazy;
//    }

    // 多线程需要加锁
    // 双重检测锁模式的 懒汉式单例 DCL懒汉式
    public static Lazy getInstance() {
        if (lazy == null) {
            synchronized (Lazy.class) {
                if (lazy == null) {
                    // 不是一个原子性操作
                    /**
                     * 指令重排可能导致132顺序执行,所以上面需要加入volatile
                     * 1.分配内存空间
                     * 2.执行构造方法,初始化对象
                     * 3.把这个对象指向内存空间
                     */
                    lazy = new Lazy();
                }
            }
        }
        return lazy;
    }


    // 多线程并发  可以发现多线程情况下导致线程发生异常
    public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException, NoSuchFieldException {
//        for (int i = 1; i <= 100; i++) {
//            new Thread(() -> {
//                Lazy.getInstance();
//            }).start();
//        }

        // 获取型号灯,去改变信号灯的值,就可以进行破解
        Field flag = Lazy.class.getDeclaredField("flag");
        // 将私有关键字进行公有
        flag.setAccessible(true);

        // 使用反射破坏单例模式
//        Lazy instance = Lazy.getInstance();
        // 加了null,表明获取的是无参构造器
        Constructor<Lazy> dc = Lazy.class.getDeclaredConstructor(null);
        // 使用反射获取对象,无视private,
        dc.setAccessible(true);

        // 不获取getInstance,都使用反射去破坏,如下面new出来,即可无视构造器的锁
        Lazy lazy1 = dc.newInstance();

        // 将信号灯值改变
        flag.set(lazy1, false);

        Lazy lazy2 = dc.newInstance();

        // 发现空间地址号不一致,已被改变
        System.out.println(lazy1);
        System.out.println(lazy2);

    }
}

静态内部类
使用静态内部类去实现单例模式

package javaSEStudy.JUC.JUCDemo.single;

// 静态内部类
// 也是不安全的,存在反射机制
public class Holder {
    // 构造器私有
    private Holder() {

    }

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

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


    public static void main(String[] args) {

    }
}

枚举类
单例不安全,使用枚举类去防止反射破坏
注意idea可能会骗人,展示的是无参构造,实际上是有参构造
使用jad实现反编译查看源码

package javaSEStudy.JUC.JUCDemo.single;

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

/**
 * 使用枚举类去避免反射破坏
 * 本身也是一个Class类
 */
public enum EnumSingle {

    INSTANCE;
    public EnumSingle getInstance() {
        return INSTANCE;
    }
}

// 测试上面的INSTANCE是否能被拿到且是同一个对象
class Test{
    public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
//        EnumSingle instance1 = EnumSingle.INSTANCE;
//        EnumSingle instance2 = EnumSingle.INSTANCE;
//
//        // 输出true,证明上面那两个是同一个对象
//        System.out.println(instance1 == instance2);

        // 如果我们使用反射区破坏该单例模式
        // 枚举类的源码会发生报错,指出不能使用反射破坏枚举
        EnumSingle instance1 = EnumSingle.INSTANCE;
        // 查看idea的out文件夹,说这是个无参构造器,但是程序运行却不是无参构造器,证明idea出错
        // 下面这个报错指没有无参构造器
        // NoSuchMethodException: javaSEStudy.JUC.JUCDemo.single.EnumSingle.<init>()
        // 使用jad工具,发现是一个String和int两个参数的构造器

        // 该报错信息才是正确报错
        // IllegalArgumentException: Cannot reflectively create enum objects
        Constructor<EnumSingle> dc = EnumSingle.class.getDeclaredConstructor(String.class,int.class);
        // 私有改公有
        dc.setAccessible(true);
        EnumSingle instance2 = dc.newInstance();

        // 使用反射创建对象去比较两个是否是同一个对象
        System.out.println(instance1==instance2);

    }
}

深入理解CAS

什么是CAS?
CPU操作的原语
一句话概括:比较当前工作内存中的值和主内存中的值,如果这个值是期望的,就执行操作 ! 如果不是就一直循环!

优点:操作自带原子性
缺点:

  1. 底层是自旋锁,循环会浪费时间
  2. 一次性只能保证一个共享变量的原子性
  3. ABA问题
package javaSEStudy.JUC.JUCDemo.cas;

import java.util.concurrent.atomic.AtomicInteger;

// CAS详解
public class CAS_ {

    // CAS 是CPU的并发原语
    public static void main(String[] args) {

        // 先实现Atomic 原子类 可以给定初始值
        AtomicInteger ai = new AtomicInteger(1024);

        // CAS就是compareAndSet的缩写,比较并交换 参数:期望 新值
        // public final boolean compareAndSet(int expectedValue, int newValue)
        // 如果等于我期望的值,就将其换为新值,否则不更新
        ai.compareAndSet(1024, 2048);
        // 可以试着更新初始值,当不为初始值,就不更新值,当达到了初始值,就会更新该值
        System.out.println(ai.get());
        
    }
}

Unsafe类

Java无法操作内存,但是有个后门 Unsafe类可以去操作内存
上面的valueOffset 是内存地址偏移值

CAS的+1操作

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

两条线程去执行操作,B快于A,先将1修改为3,又将3改为1,但是此时A不知道这个1已经是被修改过的

package javaSEStudy.JUC.JUCDemo.cas;

import java.util.concurrent.atomic.AtomicInteger;

// CAS详解
public class CAS_ {

    // CAS 是CPU的并发原语
    public static void main(String[] args) {

        // 先实现Atomic 原子类 可以给定初始值
        AtomicInteger ai = new AtomicInteger(1024);

        // 平时写的SQL语句 : 乐观锁!

        // CAS就是compareAndSet的缩写,比较并交换 参数:期望 新值
        // public final boolean compareAndSet(int expectedValue, int newValue)
        // 如果等于我期望的值,就将其换为新值,否则不更新
        // --------------捣乱的线程----------------
        ai.compareAndSet(1024, 2048);
        // 可以试着更新初始值,当不为初始值,就不更新值,当达到了初始值,就会更新该值
        System.out.println(ai.get());

        ai.compareAndSet(2048, 1024);
        // 可以试着更新初始值,当不为初始值,就不更新值,当达到了初始值,就会更新该值
        System.out.println(ai.get());


        // --------------期望的线程----------------
        ai.compareAndSet(1024, 9999);
        // 可以试着更新初始值,当不为初始值,就不更新值,当达到了初始值,就会更新该值
        System.out.println(ai.get());


    }
}

原子引用

本质:带版本号的原子操作

ps:Integer使用了对象缓存机制,默认范围是-128 - 127,推荐使用静态工厂方法ValueOf获取对象实例,而不是new,因为valueOf使用缓存,而new一定会创建新的对象分配内存空间

对应思想:乐观锁

解决ABA问题

package javaSEStudy.JUC.JUCDemo.cas;

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

// AtomicStampedReference 详解
public class AtomicStampedReference_ {
    public static void main(String[] args) {

        /**
         * AtomicStampedReference
         * 如果泛型是一个包装类,注意对象的引用问题
         *
         * 正常在业务中 <> 存放的是user这种对象
         */

        // 第一个参数为初始值,第二个相当于时间戳
        // 只要修改过值,时间戳+1
        AtomicStampedReference<Integer> asr = new AtomicStampedReference<>(1, 1);

        new Thread(() -> {
            // 执行时先获取版本号
            System.out.println("A1 -> " + asr.getStamp());

            // 使用sleep,保证拿到的版本号是同一个
            try {
                TimeUnit.SECONDS.sleep(2);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            // 拿到版本号后将其+1
            System.out.println(asr.compareAndSet(1, 2,
                    asr.getStamp(), asr.getStamp() + 1));
            System.out.println("A2 -> " + asr.getStamp());

            // 拿到版本号后将其+1
            System.out.println(asr.compareAndSet(2, 1,
                    asr.getStamp(), asr.getStamp() + 1));
            System.out.println("A3 -> " + asr.getStamp());

        }, "A").start();

        // 与乐观锁原理相同
        new Thread(() -> {
            // 执行时先获取版本号
            int stamp = asr.getStamp();
            System.out.println("B1 -> " + stamp);

            // 使用sleep,保证拿到的版本号是同一个
            try {
                TimeUnit.SECONDS.sleep(2);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            System.out.println(asr.compareAndSet(1, 9,
                    stamp, stamp + 1));
            System.out.println("B2 -> " + asr.getStamp());

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

各种锁的理解

公平锁与非公平锁

公平锁:非常公平,不能插队,讲究先来后到
非公平锁:不公平,允许插队,(默认都是非公平的)

    // 默认为非公平锁
    public ReentrantLock() {
        sync = new NonfairSync();
    }
// 改为公平锁
new ReentrantLock(true)

// 源码
    public ReentrantLock(boolean fair) {
        sync = fair ? new FairSync() : new NonfairSync();
    }

可重入锁

可重入锁 也叫递归锁

package javaSEStudy.JUC.JUCDemo.lock;

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

/**
* 可重入锁
* 一定是A走完了才是B
* 即使在第二个方法中也有锁,但是是在一个锁的里面,拿到外面的锁后,直接自动拿到里面的锁
*/
public class Re_entrant_Lock {
   public static void main(String[] args) {

       // 使用Synchronized
       Phone1 phone1 = new Phone1();
       new Thread(() -> {
           phone1.sms();
       },"A").start();
       new Thread(() -> {
           phone1.sms();
       },"B").start();

   }
}

// 使用Synchronized
class Phone1{
   public synchronized void sms(){
       System.out.println(Thread.currentThread().getName()+" sms");
       // call 方法也有锁
       call();
   }
   public synchronized void call(){
       System.out.println(Thread.currentThread().getName()+" call");
   }
}

class Phone2{

   // 锁必须配队,否则会死里面
   Lock lock = new ReentrantLock();

   public void sms(){
       lock.lock();
       try {
           // 先拿到外面的lock,解了之后,在拿call的锁
           System.out.println(Thread.currentThread().getName()+" sms");
           call();
       } catch (Exception e) {
           e.printStackTrace();
       } finally {
           lock.unlock();
       }
   }

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

}

自旋锁

spinLock

我们来自定义一个自旋锁

package javaSEStudy.JUC.JUCDemo.lock;

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

/**
 * 自旋锁详解
 * 下面栗子,只有当A自旋完毕,才会释放锁,B才能释放
 */
public class SpinLock {
    public static void main(String[] args) throws InterruptedException {

        SpinLock lock = new SpinLock();

        new Thread(() -> {
            lock.MyLock();
            try {
                TimeUnit.SECONDS.sleep(5);
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                lock.MyUnlock();
            }
        }, "A").start();

        // 保证A获取到锁
        TimeUnit.SECONDS.sleep(1);

        new Thread(() -> {
            lock.MyLock();
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                lock.MyUnlock();
            }
        }, "B").start();

    }

    // int 0
    // Thread null
    AtomicReference<Thread> ar = new AtomicReference();

    // 加锁
    public void MyLock() {
        Thread thread = Thread.currentThread();
        System.out.println(thread.getName() + " MyLock");

        // 自旋锁
        while (!ar.compareAndSet(null, thread)) {

        }
    }

    // 解锁
    public void MyUnlock() {
        Thread thread = Thread.currentThread();
        System.out.println(thread.getName() + " MyUnlock");
        ar.compareAndSet(thread, null);
    }
}

死锁

什么是死锁

死锁代码

package javaSEStudy.JUC.JUCDemo.lock;

import java.util.concurrent.TimeUnit;

// 死锁
public class Deadlock {
    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) {
        LockA = lockA;
        LockB = lockB;
    }

    @Override
    public void run() {
        synchronized (LockA) {
            System.out.println(Thread.currentThread().getName()+" lock:"+LockA+" --> get "+LockB);

            try {
                TimeUnit.SECONDS.sleep(3);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            synchronized (LockB) {
                System.out.println(Thread.currentThread().getName()+" lock:"+LockB+" --> get "+LockA);
            }
        }
    }
}

如何解决死锁

  1. 使用jps定位进程号 命令:jps -l // 查看全部活着进程

  1. 使用jstack查看进程信息 命令:jstack 进程号

显示找到一个死锁,哪个进程持有哪个锁,等待哪个锁

posted @ 2025-06-23 17:32  sprint077  阅读(34)  评论(0)    收藏  举报