Java学习笔记 - JUC并发编程

什么是JUC

java.util.concurrent java并发相关

java.util.concurrent.atomic Java原子性相关

java.util.concurrent.locks JavaLock锁相关

java.util.function 函数式包

写业务代码的时候, 用的最多的就是普通的线程代码 + Thread

Runnable相比Callable来说, 效率低, 没有返回值

进程和线程回顾

进程和线程

进程和线程

进程: 就是一个程序, 就是程序的集合

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

Java默认有两个线程: main线程, GC线程

对于Java而言, 开启线程有三种方式: Thread, Runnable, Callable

Java本事没有权限开启线程, 只能通过start0()调用本地方法才能开启线程

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

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

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

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

并发, 并行

并发, 并行

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

  • cpu 一核 模拟出来多条线程, 快速交替

并行(多个人一起行走)

  • cpu 多核 多个线程可以同时执行
public static void main(String[] args) {
    System.out.println(Runtime.getRuntime().availableProcessors());
}

线程状态

线程有几个状态?

public enum State {
    //新生
    NEW,

    //运行
    RUNNABLE,

    //阻塞
    BLOCKED,

    //等待, 死等
    WAITING,

    //超时等待
    TIMED_WAITING,

    //终止
    TERMINATED;
}

wait()和sleep() 的区别

wait()和sleep() 的区别

  1. 来自不同的类
    1. wait来自Object
    2. sleep是Thread类, TimeUnit是让线程休眠的方法
  2. 是否会释放锁
    1. wait会释放锁
    2. sleep不会释放锁
  3. 使用的范围不同
    1. wait必须在同步代码块中使用
    2. sleep可以在任何地方使用
  4. 是否需要捕获异常
    1. wait不需要捕获异常
    2. sleep需要捕获异常

Lock锁(重点)

传统的synchronized

传统的synchronized

package org.java.juc.test.javajuctest;

/**
 * 线程就是单独的一个资源类, 没有任何附属操作
 * 就是一个正常的class类
 */
public class SaleTicketDemo01 {
    public static void main(String[] args) {
        // 并发就是将共享资源丢入到线程中
        Ticket ticket = new Ticket();

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

class Ticket {
    //票
    private int number = 30;

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

Lock锁

Lock锁

  • 属于java.util.concurrent.locks包下的

  • Lock有两个常用方法: lock()加锁 , unLock()解锁

  • 语法

    • Lock l = …; l.lock(); try { // access the resource protected by this lock } finally { l.unlock(); }
  • 有三个实现类

    • java.util.concurrent.locks.ReentrantLock 可重入锁

    • java.util.concurrent.locks.ReentrantReadWriteLock.ReadLock 读锁

    • java.util.concurrent.locks.ReentrantReadWriteLock.WriteLock 写锁

    • /**
       * Creates an instance of {@code ReentrantLock}.
       * This is equivalent to using {@code ReentrantLock(false)}.
       * 创建一个可重入锁, 默认是非公平锁
       */
      public ReentrantLock() {
          sync = new NonfairSync();
      }
      
      /**
       * Creates an instance of {@code ReentrantLock} with the
       * given fairness policy.
       *
       * @param fair {@code true} if this lock should use a fair ordering policy
       * 也可以通过传参的方式创建, 如果是true, 就是公平锁, 否则就是非公平锁
       */
      public ReentrantLock(boolean fair) {
          sync = fair ? new FairSync() : new NonfairSync();
      }
      

      公平锁: 十分公平, 只能是先来后到

      非公平锁: 十分不公平, 可以插队(根据cpu调度情况), 默认是非公平锁

package org.java.juc.test.javajuctest;

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

/**
 * 线程就是单独的一个资源类, 没有任何附属操作
 * 就是一个正常的class类
 */
public class SaleTicketDemo02 {
    public static void main(String[] args) {
        // 并发就是将共享资源丢入到线程中
        Ticket ticket = new Ticket();

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

class Ticket1 {
    //票
    private int number = 30;

    //卖票的方式
    public void sale(){
        Lock lock = new ReentrantLock();
        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和Lock的区别

  • synchronized是java内置的关键字; Lock是java的一个接口
  • synchronized无法判断获取锁的状态; Lock可以判断是否获取到了锁
  • synchronized会自动释放锁; Lock必须要手动释放锁, 如果不释放锁, 就会造成死锁的状态
  • synchronized如果线程1(获得锁, 但是阻塞), 线程2(等待); Lock可以使用tryLock()的方法尝试获取锁
  • synchronized可重入锁, 不可中断, 非公平锁;Lock可重入锁, 可以判断锁的状态, 是否为公平/非公平锁(可以自己设置)
  • synchronized适合锁少量的代码同步问题; Lock适合锁大量的代码同步问题

锁是什么?锁的是谁?

生产者和消费者

面试需要掌握的: 单例模式, 排序算法, 生产者和消费者, 死锁问题

生产者和消费者 synchronized版

生产者和消费者 synchronized版

生产者和消费者之间通信其实就是: 判断等待, 业务, 通知

package org.java.juc.test.pc;

/**
 * 线程之间的通信问题, 就是生产者和消费者问题
 * 线程交替执行 A B 操作同一个变量,
 * num = 0;
 * A线程 +1
 * b线程 -1
 */
public class A {
    public static void main(String[] args) {
        Data data = new Data();

        new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                try {
                    data.increment();
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        }, "A").start();
        new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                try {
                    data.decrement();
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        }, "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();
    }
}

问题存在: 如果此时多两个线程, 存在A, B, C, D 甚至更多的线程, 这个方式还安全吗?

答案是肯定不安全的

将if判断给成while判断即可

线程可以被唤醒, 但是不会被通知/中断/超时, 即所谓的虚假唤醒, 虽然在实践中很少会发生, 但应用程序必须通过测试应该是线程被唤醒的条件来防范, 并且如果条件不满足则继续等待. 换句话说, 等待应该总是出现在循环中

synchronized (obj) {
  while (<condition does not hold>)
    obj.wait(timeout);
  	... // Perform action appropriate to condition, 根据情况采取适当的行动
}

防止虚假唤醒

if改成while

package org.java.juc.test.pc;

/**
 * 线程之间的通信问题, 就是生产者和消费者问题
 * 线程交替执行 A B 操作同一个变量,
 * num = 0;
 * A线程 +1
 * b线程 -1
 */
public class A {
    public static void main(String[] args) {
        Data data = new Data();

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

class Data {
    private int number = 0;

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

JUC版的生产者和消费者

JUC版的生产者和消费者

  • 之前的生产者和消费者靠的是synchronized, wait, notifyAll进行通信

  • JUC版的通过Lock进行锁定, Condition.await, Condition.single 进行通信

    • Lock取代了synchronized方法和语句的使用

    • Condition取代了对象监视器方法的使用

    • //例如,假设我们有一个有限的缓冲区,它支持put和take方法。 如果在一个空的缓冲区尝试一个take ,则线程将阻塞直到一个项目可用; 如果put试图在一个完整的缓冲区,那么线程将阻塞,直到空间变得可用。 我们希望在单独的等待集中等待put线程和take线程,以便我们可以在缓冲区中的项目或空间可用的时候使用仅通知单个线程的优化。 这可以使用两个Condition实例来实现
      class BoundedBuffer {
         final Lock lock = new ReentrantLock();
         final Condition notFull  = lock.newCondition(); 
         final Condition notEmpty = lock.newCondition(); 
      
         final Object[] items = new Object[100];
         int putptr, takeptr, count;
      
         public void put(Object x) throws InterruptedException {
           lock.lock(); try {
             while (count == items.length)
               notFull.await();
             items[putptr] = x;
             if (++putptr == items.length) putptr = 0;
             ++count;
             notEmpty.signal();
           } finally { lock.unlock(); }
         }
      
         public Object take() throws InterruptedException {
           lock.lock(); try {
             while (count == 0)
               notEmpty.await();
             Object x = items[takeptr];
             if (++takeptr == items.length) takeptr = 0;
             --count;
             notFull.signal();
             return x;
           } finally { lock.unlock(); }
         }
       }
      

具体代码实现

package org.java.juc.test.pc;

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

/**
 * 线程之间的通信问题, 就是生产者和消费者问题
 * 线程交替执行 A B 操作同一个变量,
 * num = 0;
 * A线程 +1
 * b线程 -1
 */
public class B {
    public static void main(String[] args) {
        Data2 data = new Data2();

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

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

    // +1
    public void increment() throws InterruptedException {
        lock.lock();
        try {
            while (number!=0) {
                //等待
               condition.await();
            }
            number++;
            System.out.println(Thread.currentThread().getName() + "==>" + number);
            //唤醒, 通知其他线程, 我+1完毕了
            condition.signalAll();
        } catch (Exception e) {
            throw new RuntimeException(e);
        } finally {
            lock.unlock();
        }

    }
    //-1
    public void decrement() throws InterruptedException {
        lock.lock();
        try {
            while (number==0) {
                //等待
                condition.await();
            }
            number--;
            System.out.println(Thread.currentThread().getName() + "==>" + number);
            //唤醒, 通知其他线程, 我-1完毕了
            condition.signalAll();
        } catch (Exception e) {
            throw new RuntimeException(e);
        } finally {
            lock.unlock();
        }
    }
}

Condition精准通知和唤醒

Condition精准通知和唤醒

package org.java.juc.test.pc;

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

/**
 * 线程之间的通信问题, 就是生产者和消费者问题
 * 线程交替执行 A B C D 顺序执行
 */
public class C {
    public static void main(String[] args) {
        Data3 data = new Data3();

        new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                try {
                    data.a();
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        }, "A").start();
        new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                try {
                    data.b();
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        }, "B").start();
        new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                try {
                    data.c();
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        }, "C").start();
        new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                try {
                    data.d();
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        }, "D").start();
    }
}

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

    public void a() throws InterruptedException {
        lock.lock();
        try {
            while (number!=0) {
                //等待
               condition1.await();
            }
            number = 1;
            System.out.println(Thread.currentThread().getName() + "==>AAAAAA");
            //唤醒, 通知其他线程, 我+1完毕了
            condition2.signal();
        } catch (Exception e) {
            throw new RuntimeException(e);
        } finally {
            lock.unlock();
        }

    }

    public void b() throws InterruptedException {
        lock.lock();
        try {
            while (number!=1) {
                //等待
                condition2.await();
            }
            number = 2;
            System.out.println(Thread.currentThread().getName() + "==>BBBBBB");
            //唤醒, 通知其他线程, 我-1完毕了
            condition3.signal();
        } catch (Exception e) {
            throw new RuntimeException(e);
        } finally {
            lock.unlock();
        }
    }

    public void c() throws InterruptedException {
        lock.lock();
        try {
            while (number!=2) {
                //等待
                condition3.await();
            }
            number = 3;
            System.out.println(Thread.currentThread().getName() + "==>CCCCCC");
            //唤醒, 通知其他线程, 我-1完毕了
            condition4.signal();
        } catch (Exception e) {
            throw new RuntimeException(e);
        } finally {
            lock.unlock();
        }
    }

    public void d() throws InterruptedException {
        lock.lock();
        try {
            while (number!=3) {
                //等待
                condition4.await();
            }
            number = 0;
            System.out.println(Thread.currentThread().getName() + "==>DDDDDD");
            //唤醒, 通知其他线程, 我-1完毕了
            condition1.signal();
        } catch (Exception e) {
            throw new RuntimeException(e);
        } finally {
            lock.unlock();
        }
    }
}

8锁现象

8锁现象就是关于锁的8个问题

  1. synchronized修饰的方法, 锁的对象是方法的调用者

  2. 普通方法不受锁的限制

  3. 静态方法在类加载的时候就有了, 此时锁的是Class模版

  4. 8锁问题, 就是关于锁的8个问题
    * 1.两个线程是先打印发短信还是打电话 - 结果是发短信
    * 2.发短信休眠4秒, 两个线程是先打印发短信还是打电话 - 结果是发短信
    * 3.新增一个普通方法, 两个线程先打印发短信还是hello - 结果是hello
    * 4.两个线程同时调用两个方法, 先打印发短信还是打电话 - 打电话
    * 5.新增两个静态同步方法, 只有一个对象进行调用, 先打印方法1还是方法2 - 方法1
    * 6.新增两个静态同步方法, 两个对象进行调用, 先打印方法1还是方法2 - 方法1
    * 7.一个对象, 同时调用 静态的同步方法 和 普通的同步方法, 谁先打印? - 看代码顺序, 其实看cpu的调用顺序
    * 8.两个对象, 调用 静态的同步方法 和 普通的同步方法, 谁先打印? - 看代码顺序, 其实看cpu的调用顺序
    

具体代码实现

Phone: 对象类

package org.java.juc.test.lock8;

import java.util.concurrent.TimeUnit;

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

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

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

    // 静态方法在类加载的时候就有了, 此时锁的是Class模版, 而并非对象
    // 无论多少个对象, 锁的都是同一个Class对象
    public static synchronized void ff1(){
        System.out.println("方法1");
    }

    public static synchronized void ff2(){
        System.out.println("方法2");
    }
}

测试1

package org.java.juc.test.lock8;

import java.util.concurrent.TimeUnit;

/**
 * 8锁问题, 就是关于锁的8个问题
 * 1.两个线程是先打印发短信还是打电话 - 结果是发短信
 * 2.发短信休眠4秒, 两个线程是先打印发短信还是打电话 - 结果是发短信
 */
public class Test01 {
    public static void main(String[] args) {
        Phone phone = new Phone();
        new Thread(() -> {phone.sendMsg();}, "A").start();
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        new Thread(() -> {phone.call();}, "B").start();
    }
}

测试2

package org.java.juc.test.lock8;

import java.util.concurrent.TimeUnit;

/**
 * 8锁问题, 就是关于锁的8个问题
 * 1.两个线程是先打印发短信还是打电话 - 结果是发短信
 * 2.发短信休眠4秒, 两个线程是先打印发短信还是打电话 - 结果是发短信
 * 3.新增一个普通方法, 两个线程先打印发短信还是hello - 结果是hello
 * 4.两个线程同时调用两个方法, 先打印发短信还是打电话 - 打电话
 */
public class Test02 {
    public static void main(String[] args) {
        Phone phone = new Phone();
        Phone phone2 = new Phone();
        new Thread(() -> {phone.sendMsg();}, "A").start();
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        // 两个对象, 两把锁, 锁1有延迟, 所以锁2必定先执行完毕
        new Thread(() -> {phone2.call();}, "B").start();
    }
}

测试3

package org.java.juc.test.lock8;

import java.util.concurrent.TimeUnit;

/**
 * 8锁问题, 就是关于锁的8个问题
 * 1.两个线程是先打印发短信还是打电话 - 结果是发短信
 * 2.发短信休眠4秒, 两个线程是先打印发短信还是打电话 - 结果是发短信
 * 3.新增一个普通方法, 两个线程先打印发短信还是hello - 结果是hello
 * 4.两个线程同时调用两个方法, 先打印发短信还是打电话 - 打电话
 * 5.新增两个静态同步方法, 只有一个对象进行调用, 先打印方法1还是方法2 - 方法1
 * 6.新增两个静态同步方法, 两个对象进行调用, 先打印方法1还是方法2 - 方法1
 */
public class Test03 {
    public static void main(String[] args) {
        Phone phone = new Phone();
        Phone phone2 = new Phone();
        new Thread(() -> {phone.ff1();}, "A").start();
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        new Thread(() -> {phone2.ff2();}, "B").start();
    }
}

测试4

package org.java.juc.test.lock8;

import java.util.concurrent.TimeUnit;

/**
 * 8锁问题, 就是关于锁的8个问题
 * 1.两个线程是先打印发短信还是打电话 - 结果是发短信
 * 2.发短信休眠4秒, 两个线程是先打印发短信还是打电话 - 结果是发短信
 * 3.新增一个普通方法, 两个线程先打印发短信还是hello - 结果是hello
 * 4.两个线程同时调用两个方法, 先打印发短信还是打电话 - 打电话
 * 5.新增两个静态同步方法, 只有一个对象进行调用, 先打印方法1还是方法2 - 方法1
 * 6.新增两个静态同步方法, 两个对象进行调用, 先打印方法1还是方法2 - 方法1
 * 7.一个对象, 同时调用 静态的同步方法 和 普通的同步方法, 谁先打印? - 看代码顺序, 其实看cpu的调用顺序
 * 8.两个对象, 调用 静态的同步方法 和 普通的同步方法, 谁先打印? - 看代码顺序, 其实看cpu的调用顺序
 */
public class Test04 {
    public static void main(String[] args) {
        Phone phone = new Phone();
        Phone phone2 = new Phone();
        new Thread(() -> {phone.ff1();}, "A").start();
        new Thread(() -> {phone.call();}, "B").start();
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }

    }
}

集合类不安全

List不安全

ArrayList是线程不安全的, 多线程情况下会报并发修改异常

  • 解决方案:
    • List list = new Vector<>();
    • List list = Collections.synchronizedList(new ArrayList<>());
    • List list = new CopyOnWriteArrayList<>();
package org.java.juc.test.unsalf;

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

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

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

CopyOnWrite, 即写入时复制, COW, 是计算机程序设计领域的一种优化策略

多个线程调用的时候, 读数据的时候, list是固定不变的, 但是写入的时候却是覆盖操作

CopyOnWriteAllayList底层是使用lock进行锁定, 然后将原来的集合复制出来一个, 将新元素插入进去, 然后解锁

而Vector底层用的是synchronized, 效率低

Set不安全

HashSet是线程不安全的, 多线程情况下会报并发修改异常

解决方案:

  • Set set = Collections.synchronizedSet(new HashSet<>());
  • Set set = new CopyOnWriteArraySet<>();
package org.java.juc.test.unsalf;

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

/**
 * 跟List集合没什么区别
 * java.util.ConcurrentModificationException 并发修改异常
 */
public class SetTest {
    public static void main(String[] args) {
        // Set<String> set = new HashSet<>();
        // Set<String> set = Collections.synchronizedSet(new HashSet<>());
        Set<String> set = new CopyOnWriteArraySet<>();
        for (int i = 0; i < 100; i++) {
            new Thread(() -> {
                set.add(UUID.randomUUID().toString().substring(0, 5));
                System.out.println(set);
            }, String.valueOf(i)).start();
        }
    }
}

Map不安全

HashMap也是线程不安全的

解决方案:

  • Map<String, String> map = Collections.synchronizedMap(new HashMap<>());
  • Map<String, String> map = new ConcurrentHashMap<>();
package org.java.juc.test.unsalf;

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

/**
 * java.util.ConcurrentModificationException
 *
 */
public class MapTest {
    public static void main(String[] args) {
        // Map<String, String> map = new HashMap<>();
        Map<String, String> map = new ConcurrentHashMap<>();
        // Map<String, String> map = Collections.synchronizedMap(new HashMap<>());
        for (int i = 0; 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

  • Callable接口类似于Runnable

    • 可以有返回值
    • 可以抛出异常
    • 开启方法不同, Runnable是run()进行启动, 而Callable是call()进行启动
  • call()如何执行

    • 首先我们可以通过new Thread(Runnable).start(); 启动实现Runnable的线程

    • 第二步, 我们通过API可以看到Runnable有一个子接口: Future

    • 第三步, 我们可以发现Future有一个实现类: FutureTask, 它的构造方法是: FutureTask(Callable callable)

    • 这样的话, 我们就可以通过new Thread(new Future(Callable)).start();启动

    • 我们还可以通过futureTask.get();的方式获取返回值, 但是这个get()可能会造成阻塞, 正常业务中, 最好是放到最后执行, 或者通过异步的方式进行获取

    • 结果是有缓存的, 并且有可能会阻塞(耗时长)

    • 代码实现:

    • package org.java.juc.test.callable;
      
      import java.util.concurrent.Callable;
      import java.util.concurrent.FutureTask;
      
      /**
       * 1.new Thread(new Runnable()).start();
       * 2.new Thread(new FutureTask<v>()).start();
       * 3.new Thread(new FutureTask<String>(new MyThread())).start();
       */
      public class Test {
          public static void main(String[] args) {
              FutureTask<String> futureTask = new FutureTask<>(new MyThread());
              new Thread(futureTask).start();
              String string = futureTask.get();
              System.out.println(string);
          }
      }
      
      class MyThread implements Callable<String> {
      
          @Override
          public String call() throws Exception {
              System.out.println("call()");
              return "呀吼";
          }
      }
      

常用辅助类(必会)

CountDownLatch

减法计数器

CountDownLatch countDownLatch = new CountDownLatch(10);
//这个数值一定要比线程数相同才可以, 否则不是结束的早, 就是结束不了
必须要执行任务的时候才能够使用, 总是要等到计数器归零才能结束掉计数器

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

每次有线程调用countDown()方法时, 数量减一, 假设计数器变为0, countDownLatch.await();就会被唤醒, 继续执行
package org.java.juc.test.fuzu;

import java.util.concurrent.CountDownLatch;

/**
 * 减法计数器
 */
public class CountDownLatchDemo {
    public static void main(String[] args) throws InterruptedException {
        CountDownLatch countDownLatch = new CountDownLatch(10);
        //这个数值一定要比线程数相同才可以, 否则不是结束的早, 就是结束不了
        for (int i = 0; i < 10; i++) {
            new Thread(() -> {
                System.out.println(Thread.currentThread().getName() + "出去咯");
                countDownLatch.countDown();//每次执行都减1
            }).start();
        }
        countDownLatch.await();
        System.out.println("close door");
    }
}

CyclicBarrier

加法计数器

同样也需要线程数和初始化的时候的参数一致

两个构造方法, 一个是等待线程执行到和初始化参数一样的时候, 结束; 另外一个是等待线程执行到和初始化参数一样的时候, 调起一个线程

package org.java.juc.test.fuzu;

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

/**
 * 加法计数器
 */
public class CyclicBarrierDemo {
    public static void main(String[] args) {
        CyclicBarrier cyclicBarrier = new CyclicBarrier(7, () -> {
            System.out.println("集齐7颗龙珠, 召唤神龙");
        });
        for (int i = 1; i <= 7; i++) {
            final int temp = i;
            new Thread(() -> {
                System.out.println(Thread.currentThread().getName() + "集齐第" + temp + "颗龙珠");
                try {
                    cyclicBarrier.await();
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                } catch (BrokenBarrierException e) {
                    throw new RuntimeException(e);
                }
            }).start();
        }
    }
}

Semaphore

信号量

主要用来限制线程执行的数量

首先线程使用semaphore.acquire();获取到资源, 然后执行业务操作

等到执行完毕之后, 线程通过semaphore.release();释放资源

Semaphore仅支持初始化值的数量同时执行, 常用来做限流

在第一时间内, 没有抢到的线程只能等待

package org.java.juc.test.fuzu;

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

/**
 * 停车位
 */
public class SemaphoreDemo {
    public static void main(String[] args) {
        //将这三个想象成3个停车位
        Semaphore semaphore = new Semaphore(3);

        for (int i = 0; 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) {
                    throw new RuntimeException(e);
                } finally {
                    // 释放
                    semaphore.release();
                }
            }, String.valueOf(i)).start();
        }
    }
}

原理:

  • semaphore.acquire(); 获得 假设线程已经被占满, 需要等待线程被释放
  • semaphore.release(); 释放 会将当前的信号量释放 +1, 然后唤醒等待的线程
  • 作用: 多个共享资源互斥的使用, 并发限流, 控制最大的线程数

读写锁

ReadWriteLock

只有一个实现类: ReentrantReadWriteLock

读写锁: 读的时候可以被多线程同时读, 写的时候只能有一个线程写

package org.java.juc.test.readwrite;

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

/**
 * 独占锁(写锁)  一次只能被一个线程占有
 * 共享锁(读锁)  多个线程可以同时占有
 * ReadWriteLock
 * 读-读 可以共存
 * 读-写 不可以共存
 * 写-写 不可以共存
 */
public class ReadWriteLockDemo {
    public static void main(String[] args) {
        MyCacheLock myCacheLock = new MyCacheLock();

        //写入
        for (int i = 0; i < 50; i++) {
            final int tem = i;
            new Thread(() -> {
                myCacheLock.put(tem+"", tem+"");
            }, String.valueOf(i)).start();
        }

        //读取
        for (int i = 0; i < 50; i++) {
            final int tem = i;
            new Thread(() -> {
                myCacheLock.get(tem+"");
            }, String.valueOf(i)).start();
        }
    }
}

/**
 * 自定义缓存
 */
class MyCacheLock {
    private volatile Map<String, Object> map = new HashMap<>();

    private ReadWriteLock readWriteLock = new ReentrantReadWriteLock();

    /**
     * 存入缓存
     * 只能有一个线程进行操作
     * @param key
     * @param value
     */
    public void put(String key, Object value) {
        readWriteLock.writeLock().lock();

        try {
            System.out.println(Thread.currentThread().getName() + "写入" + key);
            map.put(key, value);
            System.out.println(Thread.currentThread().getName() + "写入ok");
        } catch (Exception e) {
            throw new RuntimeException(e);
        } finally {
            readWriteLock.writeLock().unlock();
        }
    }

    /**
     * 读取缓存
     * 所有线程可以并发读取
     * @param key
     */
    public void get(String key) {
        readWriteLock.readLock().lock();
        try {
            System.out.println(Thread.currentThread().getName() + "读取" + key);
            map.get(key);
            System.out.println(Thread.currentThread().getName() + "读取ok");
        } catch (Exception e) {
            throw new RuntimeException(e);
        } finally {
            readWriteLock.readLock().unlock();
        }
    }
}

阻塞队列

在这里插入图片描述

阻塞队列的接口: BlockingQueue

父类: Collection, Iterable, Queue

实现类: ArrayBlockingQueue(数组阻塞队列), LinkedBlockingQueue(链表阻塞队列), SynchronousQueue(同步队列)

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

什么时候会使用到BlockingQueue?

  • 多线程中并发处理
    • 多线程中, 线程A需要调用线程B, 必须要等线程B执行完毕
  • 线程池
    • 有时候会使用队列维护线程池的大小

BlockingQueue四组API

方式抛出异常不会抛出异常, 有返回值阻塞等待超时等待
添加addofferputoffer(E e, long timeout, TimeUnit unit)
移除removepolltakepoll(long timeout, TimeUnit unit)
判断队列首部elementpeek--

抛出异常

/**
 * 抛出异常
 */
private static void test1(){
    BlockingQueue arrayBlockingQueue = new ArrayBlockingQueue<>(3);
    // 添加的时候, 超出BlockingQueue的大小就会抛出异常
    System.out.println(arrayBlockingQueue.add("a"));
    System.out.println(arrayBlockingQueue.add("b"));
    System.out.println(arrayBlockingQueue.add("c"));
    // java.lang.IllegalStateException: Queue full
    // System.out.println(arrayBlockingQueue.add("d"));

	  System.out.println(arrayBlockingQueue.element());
    System.out.println("==================");
    // 取值的时候, 如果BlockingQueue已经空了, 也会报错
    System.out.println(arrayBlockingQueue.remove());
    System.out.println(arrayBlockingQueue.remove());
    System.out.println(arrayBlockingQueue.remove());
    // java.util.NoSuchElementException
    System.out.println(arrayBlockingQueue.remove());
}

不会抛出异常

/**
 * 不抛出异常, 有返回值
 */
private static void test2(){
    BlockingQueue arrayBlockingQueue = new ArrayBlockingQueue<>(3);
    // 添加的时候, 超出BlockingQueue的大小就会返回false
    System.out.println(arrayBlockingQueue.offer("a"));
    System.out.println(arrayBlockingQueue.offer("b"));
    System.out.println(arrayBlockingQueue.offer("c"));
    System.out.println(arrayBlockingQueue.offer("d"));
    System.out.println(arrayBlockingQueue.peek());
    System.out.println("==================");
    // 取值的时候, 如果BlockingQueue已经空了, 会返回null
    System.out.println(arrayBlockingQueue.poll());
    System.out.println(arrayBlockingQueue.poll());
    System.out.println(arrayBlockingQueue.poll());
    // null
    System.out.println(arrayBlockingQueue.poll());
}

阻塞等待

/**
 * 阻塞等待
 */
private static void test3() throws InterruptedException {
    BlockingQueue arrayBlockingQueue = new ArrayBlockingQueue<>(3);
    // 添加的时候, 超出BlockingQueue的大小就会一直等待
    arrayBlockingQueue.put("a");
    arrayBlockingQueue.put("b");
    arrayBlockingQueue.put("c");
    // arrayBlockingQueue.put("d");

    System.out.println("==================");
    // 取值的时候, 如果BlockingQueue已经空了, 就会一直等待
    System.out.println(arrayBlockingQueue.take());
    arrayBlockingQueue.put("d");
    System.out.println(arrayBlockingQueue.take());
    System.out.println(arrayBlockingQueue.take());
    System.out.println(arrayBlockingQueue.take());
}

超时等待

/**
 * 等待超时就退出
 */
private static void test4() throws InterruptedException {
    BlockingQueue arrayBlockingQueue = new ArrayBlockingQueue<>(3);
    // 添加的时候, 超出BlockingQueue的大小的, 会等到设定的时间, 如果等待超时, 就会退出
    System.out.println(arrayBlockingQueue.offer("a"));
    System.out.println(arrayBlockingQueue.offer("b"));
    System.out.println(arrayBlockingQueue.offer("c"));
    // System.out.println(arrayBlockingQueue.offer("d", 2, TimeUnit.SECONDS));
    System.out.println(arrayBlockingQueue.peek());
    System.out.println("==================");
    // 取值的时候, 如果BlockingQueue已经空了, 会等到设定的时间, 如果等待超时, 就返回null
    System.out.println(arrayBlockingQueue.poll());
    System.out.println(arrayBlockingQueue.poll());
    System.out.println(arrayBlockingQueue.poll());
    // null
    System.out.println(arrayBlockingQueue.poll(2, TimeUnit.SECONDS));
}

SynchronousQueue 同步队列

/**
 * 同步队列
 * 不同于其他的BlockingQueue, 同步队列不存储元素
 * put进去一个元素, 必须先从里面take取出来, 否则不能再put进去值
 */
public class SynchronousQueueDemo {
    public static void main(String[] args) {
        BlockingQueue<Object> synchronousQueue = new SynchronousQueue<>();

        new Thread(() -> {
            try {
                System.out.println(Thread.currentThread().getName() + "==>" + "put 1");
                synchronousQueue.put(1);
                System.out.println(Thread.currentThread().getName() + "==>" + "put 2");
                synchronousQueue.put(2);
                System.out.println(Thread.currentThread().getName() + "==>" + "put 3");
                synchronousQueue.put(3);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }, "T1").start();

        new Thread(() -> {
            try {
                TimeUnit.SECONDS.sleep(3);
                System.out.println(Thread.currentThread().getName() + "==>" + synchronousQueue.take());
                TimeUnit.SECONDS.sleep(3);
                System.out.println(Thread.currentThread().getName() + "==>" + synchronousQueue.take());
                TimeUnit.SECONDS.sleep(3);
                System.out.println(Thread.currentThread().getName() + "==>" + synchronousQueue.take());
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }, "T2").start();
    }
}

线程池

池化技术

程序的本质就是占用系统的资源, 优化资源的使用, 因此诞生了池化技术;

例如线程池, jdbc连接池, 内存池, 对象池…

池化技术是一种资源管理策略,旨在提高资源的利用效率。它的核心思想是在需要时动态地分配和重用资源,而不是频繁地创建和销毁它们。这些资源可以是线程、数据库连接、对象实例或缓存数据,具体取决于使用场景

  • 线程池的好处
    • 降低资源的消耗, 线程的创建和销毁十分浪费资源
    • 提高响应的速度
    • 方便管理
  • 总结下来就是线程复用, 可以控制最大并发数, 方便管理线程

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

线程池不允许使用Executors去创建, 而是通过ThreadPoolExecutor的方式, 这样的处理方式让人更加明确线程池的运行规则, 规避资源耗尽的风险

  • Executors创建的线程池对象的弊端
    1. FixedThreadPool和SingleThreadPool:
      • 允许的请求队列长度为Integer.Max_VALUE, 可能会堆积大量的请求, 从而导致OOM
    2. CacheThreadPool和ScheduledThreadPool
      • 允许的创建线程数量为Integer.Max_VALUE, 可能会创建大量的线程, 从而导致OOM

三大方法

// 创建单个线程的线程池
ExecutorService threadPool = Executors.newSingleThreadExecutor();
// 创建固定大小的线程池
ExecutorService threadPool = Executors.newFixedThreadPool(5);
// 创建可伸缩的线程池, 不固定大小
ExecutorService threadPool = Executors.newCachedThreadPool();

七大参数

  • int corePoolSize, // 核心线程池大小
  • int maximumPoolSize, // 最大核心线程池大小
  • long keepAliveTime, // 存活时间, 超时了如果没有人调用, 就会释放
  • TimeUnit unit, // 超时单位
  • BlockingQueue workQueue, // 阻塞队列
  • ThreadFactory threadFactory, // 线程工厂, 用来创建线程的, 一般不需要动
  • RejectedExecutionHandler handler // 拒绝策略

线程池最大承载数为: workQueue + maximumPoolSize

源码分析

// 创建单个线程的线程池
public static ExecutorService newSingleThreadExecutor() {
    return new FinalizableDelegatedExecutorService
        (new ThreadPoolExecutor(1, 1,
                                0L, TimeUnit.MILLISECONDS,
                                new LinkedBlockingQueue<Runnable>()));
}
// 创建固定大小的线程池
public static ExecutorService newFixedThreadPool(int nThreads) {
  return new ThreadPoolExecutor(nThreads, nThreads,
                                0L, TimeUnit.MILLISECONDS,
                                new LinkedBlockingQueue<Runnable>());
}
// 创建可伸缩的线程池, 不固定大小
public static ExecutorService newCachedThreadPool() {
  return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                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.acc = System.getSecurityManager() == null ?
    null :
  AccessController.getContext();
  this.corePoolSize = corePoolSize;
  this.maximumPoolSize = maximumPoolSize;
  this.workQueue = workQueue;
  this.keepAliveTime = unit.toNanos(keepAliveTime);
  this.threadFactory = threadFactory;
  this.handler = handler;
}

在这里插入图片描述

四种拒绝策略

  • new ThreadPoolExecutor.AbortPolicy()
    • 中止策略
    • 行为: 直接抛出RejectedExecutionException异常,强制调用方处理任务拒绝
    • 适用场景‌:对任务丢失不敏感但需明确知晓拒绝情况的场景(如结合异常监控系统)
  • new ThreadPoolExecutor.CallerRunsPolicy()
    • 调用者运行策略
    • 行为‌:将任务退回给提交线程由其直接执行,变相降低提交速度。‌‌‌‌
    • 适用场景‌:可接受延迟但需保证任务处理的场景(如日志记录)。‌‌‌‌
  • new ThreadPoolExecutor.DiscardOldestPolicy()
    • 丢弃最旧任务策略
    • 行为‌:移除队列最旧任务并重新提交当前任务,优先处理新任务。‌‌‌‌
    • 适用场景‌:时效性要求高的场景(如新消息推送优先)。‌‌‌‌
  • new ThreadPoolExecutor.DiscardPolicy()
    • 丢弃策略
    • 行为‌:静默丢弃新任务,无任何提示或异常。‌‌‌‌
    • 适用场景‌:允许数据丢失的非关键任务(如实时监控采样)。‌‌

自定义线程池

public static void main(String[] args) {
    // 创建单个线程的线程池
    ExecutorService threadPool = new ThreadPoolExecutor(
            2,
            5,
            3,
            TimeUnit.SECONDS,
            new LinkedBlockingQueue<>(3),
            new ThreadPoolExecutor.AbortPolicy()
    );

    try {
        for (int i = 0; i < 14; i++) {
            // 使用线程池来创建线程
            threadPool.execute(() -> {
                System.out.println(Thread.currentThread().getName() + " ok");
            });
        }
    } catch (Exception e) {
        throw new RuntimeException(e);
    } finally {
        threadPool.shutdown();
    }
}

最大线程池该如何定义

  • CPU密集型

跟CPU核数保持一致

Runtime.getRuntime().availableProcessors(); 获取最大CPU核数

  • IO密集型

判断程序中十分耗IO的线程, 一般是任务数量的两倍即可

四大函数式接口

Function(函数型接口)、Predicate(断定型接口)、Consumer(消费型接口)和Supplier(供给型接口)

Function : 函数型接口

传入参数T, 返回参数R

@FunctionalInterface
public interface Function<T, R> {
    R apply(T t);
}
/**
 * Funtion 函数型接口
 * 传入参数T, 返回参数R
 */
public class Demo01 {
    public static void main(String[] args) {
        // Function<String, String> function = new Function<String, String>() {
        //     @Override
        //     public String apply(String str) {
        //         return str;
        //     }
        // };
        Function<String, String> function = str -> {return str;};
        System.out.println(function.apply("asd"));
    }
}

Predicate : 断定型接口

@FunctionalInterface
public interface Predicate<T> {
    boolean test(T t);
}
/**
 * Predicate 断定型接口
 * 传入一个参数, 返回一个布尔值
 */
public class Demo02 {
    public static void main(String[] args) {
        // Predicate<String> predicate = new Predicate<String>() {
        //     @Override
        //     public boolean test(String str) {
        //         return str.isEmpty();
        //     }
        // };
        Predicate<String> predicate = str -> {return str.isEmpty();};
        System.out.println(predicate.test("a"));
    }
}

Consumer : 消费型接口

@FunctionalInterface
public interface Consumer<T> {
    void accept(T t);
}
/**
 * Consumer 消费型接口
 * 只有输入, 没有返回值
 */
public class Demo03 {
    public static void main(String[] args) {
        // Consumer<String> consumer = new Consumer<String>() {
        //     @Override
        //     public void accept(String str) {
        //         System.out.println(str);
        //     }
        // };

        Consumer<String> consumer = str -> {
            System.out.println(str);
        };
        consumer.accept("asdfa");
    }
}

Supplier : 供给型接口

@FunctionalInterface
public interface Supplier<T> {
    T get();
}
/**
 * Supplier 供给型接口
 * 没有参数, 只有返回值
 */
public class Demo04 {
    public static void main(String[] args) {
        // Supplier<String> supplier = new Supplier() {
        //     @Override
        //     public String get() {
        //         return "wewe";
        //     }
        // };
        
        Supplier<String> supplier = () -> {return "asd";};
        System.out.println(supplier.get());
    }
}

Stream流式计算

/**
 * 用户
 */
@Data
@AllArgsConstructor
public class User {
    private int id;
    private String name;
    private int age;
}

/**
 * 题目要求: 一分钟之内完成此题, 只能用一行代码实现
 * 现在有五个用户, 要求筛选:
 *  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(6, "e", 25);
        List<User> list = Arrays.asList(u1, u2, u3, u4, u5);

        list.stream()
                .filter(u -> {return u.getId()%2 == 0;})
                .filter(u -> {return u.getAge() > 23;})
                .map(u -> {return u.getName().toUpperCase();})
                .sorted((uu1, uu2) -> {return uu2.compareTo(uu1);})
                .limit(1)
                .forEach(System.out::println);
    }
}

分支合并 : ForkJoin

ForkJoin是由JDK1.7之后提供的多线程并发处理框架。

ForkJoin框架的基本思想是分而治之。

什么是分而治之?分而治之就是将一个复杂的计算,按照设定的阈值分解成多个计算,然后将各个计算结果进行汇总。

相应的,ForkJoin将复杂的计算当做一个任务,而分解的多个计算则是当做一个个子任务来并行执行

在这里插入图片描述

ForkJoin的特点: 工作窃取

其中维护的都是双端队列, 线程B执行完自己的工作之后, 将线程A尚未执行的任务偷过来执行

// 工作窃取算法
	假如我们需要做一个比较大的任务,我们可以把这个任务分割为若干互不依赖的子任务,为了减少线程间的竞争,于是把这些子任务分别放到不同的队列里,并为每个队列创建一个单独的线程来执行队列里的任务,线程和队列一一对应,比如A线程负责处理A队列里的任务。
  但是有的线程会先把自己队列里的任务干完,而其他线程对应的队列里还有任务等待处理。干完活的线程与其等着,不如去帮其他线程干活,于是它就去其他线程的队列里窃取一个任务来执行。
  而在这时它们会访问同一个队列,所以为了减少窃取任务线程和被窃取任务线程之间的竞争,通常会使用双端队列,被窃取任务线程永远从双端队列的头部拿任务执行,而窃取任务的线程永远从双端队列的尾部拿任务执行

在这里插入图片描述

ForkJoin的框架的实现

在这里插入图片描述

ForkJoinPool

实现了ForkJoin框架中的线程池,由类图可以看出,ForkJoinPool类实现了线程池的Executor接口

可以使用Executors.newWorkStealPool()方法创建ForkJoinPool

  • ForkJoinPool中提供了如下提交任务的方法
    • public void execute(ForkJoinTask<?> task)
      • 作用:将一个 ForkJoinTask 提交到 ForkJoinPool 中异步执行。
      • 特点:不返回结果,也不会阻塞当前线程
      • 适用场景:适合不关心返回值的任务
    • public void execute(Runnable task)
      • 作用:将一个 Runnable 任务提交到线程池执行。
      • 特点:也是异步执行,不返回结果。这个 Runnable 会被包装成 ForkJoinTask
      • 适用场景:任务实现了 Runnable 接口,不关心返回值。
    • public T invoke(ForkJoinTask task)
      • 作用:同步执行一个 ForkJoinTask,直到完成,并返回结果。
      • 特点:会阻塞当前线程直到任务完成,适合需要拿返回值的情况。
      • 适用场景:当前线程必须等这个任务完成后才能继续
    • public List<Future> invokeAll(Collection<? extends Callable> tasks)
      • 作用:并发执行一组 Callable 任务,阻塞直到所有任务完成,返回对应的 Future 列表。
      • 特点批量提交任务并等待执行完毕
      • 适用场景:同时执行多个任务并等待所有结果
    • public ForkJoinTask submit(ForkJoinTask task)
      • 作用:异步提交一个 ForkJoinTask,返回该任务对象(可用于获取结果)。
      • 特点:不阻塞当前线程,但可以通过 ForkJoinTask.get() 获取结果。
      • 适用场景:需要获取结果,但不需要立刻阻塞
    • public ForkJoinTask submit(Callable task)
      • 作用:将一个 Callable 封装成 ForkJoinTask 并提交执行。
      • 特点:返回 ForkJoinTask,可以获取结果。
      • 适用场景:只实现了 Callable 接口的任务
    • public ForkJoinTask submit(Runnable task, T result)
      • 作用:将 Runnable 封装成 ForkJoinTask 并执行,设置一个返回值。
      • 特点:适用于只有执行逻辑、没有返回值的任务,但仍然希望返回一个固定值。
      • 适用场景:对 Runnable 的封装,并希望获取一个人为设定的返回值
    • public ForkJoinTask<?> submit(Runnable task)
      • 作用:提交一个 Runnable,返回其对应的 ForkJoinTask
      • 特点:适合希望提交任务后稍后再拿结果(尽管 Runnable 本身无返回值)。
      • 适用场景:任务是 Runnable 类型,但仍希望获得任务状态
方法参数类型是否阻塞是否返回结果常见用途
execute(ForkJoinTask)ForkJoinTask提交 ForkJoin 任务
execute(Runnable)Runnable提交普通任务
invoke(ForkJoinTask)ForkJoinTask同步获取结果
invokeAll(Collection)Callable 列表同步批量执行
submit(ForkJoinTask)ForkJoinTask异步获取结果
submit(Callable)Callable异步提交 Callable
submit(Runnable, result)Runnable + result返回固定结果
submit(Runnable)Runnable封装为 ForkJoinTask

ForkJoinWorkerThread

ForkJoinWorkerThreadForkJoinPool 中的工作线程,用来从工作队列中取出任务并执行,是实现工作窃取(Work Stealing)的核心载体

ForkJoinWorkerThread 做了什么?

当你使用 ForkJoinPool 提交任务时,ForkJoinPool 并不会直接用普通的线程(如 ThreadExecutors.newFixedThreadPool() 中的线程)来执行任务,而是使用 ForkJoinWorkerThread 来:

  1. 维护每个线程的任务队列(work queue)
  2. 执行任务(包括递归分割和合并结果)
  3. 在任务不足时进行“窃取”操作,从其他线程的队列中偷任务执行(Work Stealing)
  4. 参与线程池的生命周期管理和状态记录

ForkJoinTask

ForkJoinTask封装了数据及其相应的计算,并且支持细粒度的数据并行。ForkJoinTask比线程要轻量,ForkJoinPool中少量工作线程能够运行大量的ForkJoinTask。

ForkJoinTask类中主要包括两个方法fork()和join(),分别实现任务的分拆与合并。

fork()方法类似于Thread.start(),但是它并不立即执行任务,而是将任务放入工作队列中。跟Thread.join()方法不同,ForkJoinTask的join()方法并不简单的阻塞线程,而是利用工作线程运行其他任务,当一个工作线程中调用join(),它将处理其他任务,直到注意到目标子任务已经完成

ForkJoinTask有3个子类
  • RecursiveAction:无返回值的任务。
    • 有返回结果的ForkJoinTask实现Callable
  • RecursiveTask:有返回值的任务。
    • 无返回结果的ForkJoinTask实现Runnable。
  • CountedCompleter:完成任务后将触发其他任务
    • 在任务完成执行后会触发执行一个自定义的钩子函数。

测试代码

package org.java.juc.test.forkjoin;

import java.util.concurrent.RecursiveTask;

/**
 * 计算从1+...+10_0000_0000的值
 *  如何使用forkjoin?
 *      1. 通过ForkJoinPool执行
 *      2. 计算任务: forkJoinPool.execute(ForkJoinTask task);
 *      3. 计算类要继承ForkJoinTask
 */
public class ForkjoinDemo extends RecursiveTask<Long> {
    private Long start;
    private Long end;

    private Long temp = 100000L;

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

    @Override
    protected Long compute() {
        if ((end - start) < temp) {
            Long sum = 0L;
            for (Long i = start; i <= end; i++) {
                sum += i;
            }
            return sum;
        } else {    //forkjoin
            long middle = (start + end) / 2;
            // 拆分任务, 将任务压入到线程队列
            ForkjoinDemo task1 = new ForkjoinDemo(start, middle);
            task1.fork();
            // 拆分任务, 将任务压入到线程队列
            ForkjoinDemo task2 = new ForkjoinDemo(middle, end);
            task2.fork();

            // 合并任务结果
            return task1.join() + task2.join();
        }
    }
}
package org.java.juc.test.forkjoin;

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

public class ForkJoinTest {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        // test1();    // 最终结果: 499999999500000000最终耗时: 368
        // test2();    // 最终结果: 500008191999986944最终耗时: 2183
        test3();    //最终结果: 499999999500000000最终耗时: 157
    }

    // 普通for循环
    private static void test1() {
        long sum = 0;
        long start = System.currentTimeMillis();
        for (long i = 1; i < 10_0000_0000; i++) {
            sum += i;
        }
        long end = System.currentTimeMillis();
        System.out.println("最终结果: " + sum + "最终耗时: " + (end - start));
    }

    // 使用ForkJoin
    private static void test2() throws ExecutionException, InterruptedException {
        long sum = 0;
        long start = System.currentTimeMillis();
        ForkJoinPool forkJoinPool = new ForkJoinPool();
        ForkjoinDemo forkjoinDemo = new ForkjoinDemo(0L, 10_0000_0000L);
        ForkJoinTask<Long> submit = forkJoinPool.submit(forkjoinDemo);
        sum = submit.get();
        long end = System.currentTimeMillis();
        System.out.println("最终结果: " + sum + "最终耗时: " + (end - start));
    }

    // 使用并行流
    private static void test3() throws ExecutionException, InterruptedException {
        long start = System.currentTimeMillis();
        long sum = LongStream.range(0L, 10_0000_0000).parallel().sum();
        long end = System.currentTimeMillis();
        System.out.println("最终结果: " + sum + "最终耗时: " + (end - start));
    }
}

为什么使用ForkJoin比普通for循环还要慢?

你写的这个 `ForkJoinDemo` 实现了 Fork/Join 框架来求和,但你会发现执行时间比普通 `for` 循环还慢,甚至比 Java 8 的并行流也慢。**这是一个很常见的问题,原因主要集中在下面几个方面:**

---

### ✅ **1. ForkJoin 的任务创建与调度开销较大**

* 在 `ForkJoin` 中,每次划分子任务(`fork()`)和合并结果(`join()`)都会产生 **线程调度开销、任务排队开销*** 比如:

  ```java
  task1.fork();
  task2.fork();
  return task1.join() + task2.join();
  ```

  每调用一次 `fork()` 都会把任务丢到工作队列中,线程池再来调度这些任务。

📌 当划分的任务粒度太细时(即你设置的 `temp=100_000L`),系统会创建大量子任务,这样划分的收益就抵不上线程调度的成本了。

---

### ✅ **2. 子任务拆分有重叠:你写成了 `[start, middle]` 和 `[middle, end]`**

```java
ForkjoinDemo task1 = new ForkjoinDemo(start, middle); // 包含 middle
ForkjoinDemo task2 = new ForkjoinDemo(middle, end);   // 同样包含 middle
```

> ❌ 这样实际上 **重复计算了 middle**,导致最终结果可能也出错!

应改为:

```java
ForkjoinDemo task1 = new ForkjoinDemo(start, middle);
ForkjoinDemo task2 = new ForkjoinDemo(middle + 1, end);
```

---

### ✅ **3. ForkJoin 默认线程数可能小于 CPU 核心数**

* `new ForkJoinPool()` 默认使用 `Runtime.getRuntime().availableProcessors()` 个线程,如果你机器是 4 核心,最多就4个线程参与计算。
* 而 `LongStream.parallel()` 底层用的是 `ForkJoin.commonPool()`,可能优化更充分。

---

### ✅ **4. 你的递归实现方式不够优化**

你的实现是:

```java
task1.fork();
task2.fork();
return task1.join() + task2.join();
```

更推荐的写法是:

```java
task1.fork();              // 异步执行 task1
long result2 = task2.compute(); // 当前线程计算 task2
long result1 = task1.join();    // 等待 task1 完成
return result1 + result2;
```

✅ 这样做的好处是:

* **减少任务排队**:当前线程直接干活,不空等。
* **更快利用线程资源**:尽量只让一部分任务 fork,避免全部任务排队等待。

---

### ✅ **5. 并行流底层实现更高效**

`LongStream.range(0L, 10_0000_0000).parallel().sum()` 使用了 Java 内部高度优化的并行实现(使用 `Spliterator` 拆分、 `ForkJoinTask` 的 `commonPool` 调度),它能更高效地划分任务、避免重复计算、减少线程间通信开销。

---

## ✅ 总结:为什么 `ForkJoin` 比 `for` 慢?

| 原因        | 解释                              |
| --------- | ------------------------------- |
| ✅ 拆分任务开销高 | 多线程调度、排队、合并需要时间                 |
| ✅ 拆分粒度不合理 | 每个小任务计算量少,调度成本大                 |
| ❌ 子任务重叠   | `middle` 重复计算,浪费性能              |
| ❌ 实现不优    | 没有采用 `fork + compute + join` 模式 |
| ✅ 并行流更优   | JDK 原生优化,性能远胜手写                 |

---

### ✅ 建议优化方案:

```java
@Override
protected Long compute() {
    if ((end - start) <= temp) {
        long sum = 0L;
        for (long i = start; i <= end; i++) {
            sum += i;
        }
        return sum;
    } else {
        long middle = (start + end) / 2;
        ForkjoinDemo left = new ForkjoinDemo(start, middle);
        ForkjoinDemo right = new ForkjoinDemo(middle + 1, end);
        left.fork();
        long rightResult = right.compute(); // 当前线程计算右边
        long leftResult = left.join();
        return leftResult + rightResult;
    }
}
```

---

如需进一步提升性能,推荐直接使用:

```java
LongStream.rangeClosed(1, 10_0000_0000L).parallel().sum();
```

因为 JDK 底层实现已经做了非常复杂的优化,几乎不需要手动再写 ForkJoin 来加速简单的数值求和场景。

最终结果: 500000000500000000最终耗时: 228

异步回调

/**
 * 异步调用:CompletableFuture
 *  异步执行
 *  成功调用
 *  失败调用
 */
public class FutureDemo {
    // 无返回值的异步执行
    // public static void main(String[] args) throws ExecutionException, InterruptedException {
    //     // 发起一个请求
    //     CompletableFuture<Void> future = CompletableFuture.runAsync(() -> {
    //         try {
    //             TimeUnit.SECONDS.sleep(2);
    //         } catch (InterruptedException e) {
    //             System.out.println(e.getMessage());
    //         }
    //         System.out.println(Thread.currentThread().getName() + "=>runAsync -> Void");
    //     });
    //
    //     System.out.println("main线程执行 => 111" );
    //
    //     // 阻塞获取执行结果
    //     future.get();
    //     /*
    //     * 最终结果:
    //     *   main线程执行 => 111
    //     *   ForkJoinPool.commonPool-worker-1=>runAsync -> Void
    //     * */
    // }

    //有返回值的异步执行
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        CompletableFuture<Integer> future = CompletableFuture.supplyAsync(() -> {
            System.out.println(Thread.currentThread().getName() + "=>supplyAsync -> Integer");
            int i = 10 /0;
            return 1024;
        });
        future.whenComplete((t, u) -> {
            // 调用执行成功时
            System.out.println("t -> " + t);
            System.out.println("u -> " + u);
        }).exceptionally((e) -> {
            // 调用出现异常时
            System.out.println(e.getMessage());
            return 233;
        }).get();
        /*
        * 正常执行时:
        *   t -> 1024 正常输出结果, u -> null
        * 出现异常时:
        *   t -> null
        *   u -> java.util.concurrent.CompletionException: java.lang.ArithmeticException: / by zero
        *   java.lang.ArithmeticException: / by zero
        * */
    }
}

JMM

Java Memory Model, Java内存模型

它不是一段具体的代码,而是 Java 虚拟机(JVM)定义的一套规则,用来规范:在多线程环境下,变量是如何在主内存和线程的工作内存之间交互的

JMM 是 Java 并发的底层规范,决定了多线程之间变量如何读写、如何保证同步与可见性。

MM 是“并发语义的规范”,而 JVM 的堆、栈、方法区等是“内存空间的物理结构

JMM主要是为了解决什么问题?

多线程程序可能存在以下三个问题, JMM就是为了解决这三个问题的

  1. 可见性: 一个线程对变量的修改,另一个线程看不到(因为缓存不一致)
  2. 原子性: 多个操作不是一个整体,中间可能被打断
  3. 有序性: 编译器或 CPU 可能会“优化”指令顺序,导致实际执行与代码顺序不一致

JMM的核心概念

1. 主内存(Main Memory)

  • 所有线程共享的一块内存区域(类似堆内存),所有实例变量都保存在主内存中。

2. 工作内存(Working Memory)

  • 每个线程都有自己的工作内存(类似线程本地缓存),从主内存拷贝变量副本来读写

主内存 ↔ 工作内存 的交互图

      [主内存]
    /    |   \
[线程A] [线程B] [线程C]

1.每个线程操作的是自己工作内存里的变量副本
2.只有通过特定的操作,才会把修改同步到主内存 or 从主内存重新读取

关于JMM的一些同步的约定

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

JMM定义的8中操作

操作说明
load从主内存加载变量到工作内存副本
store把工作内存中变量写回主内存
read从主内存中读取变量到工作内存
write把工作内存中变量更新主内存
lock作用于变量的锁,用于互斥
unlock解锁
use把变量传给执行引擎(使用变量)
assign从执行引擎接收新值赋给变量
在这里插入图片描述

这么做会有问题
在这里插入图片描述

JMM对于8大操作的约束

主要目的是避免线程看到脏数据或操作乱序

  1. 规则 1:线程必须先 lock,才能执行写操作

    • 如果一个线程对变量加锁,其他线程必须等它 unlock 才能操作这个变量。
    • 保证原子性和互斥性
  2. 规则 2:一个变量在被写入主内存前,必须先通过 store → write

    • 否则主内存永远不会更新,其他线程也看不到变化。
    • 即:assign → store → write
  3. 规则 3:一个变量在被使用前,必须先通过 read → load

    • 否则工作内存里的值就不是最新的。
    • 即:read → load → use
  4. 规则 4:每个变量的工作内存副本是线程私有的

    • 线程 A 的 loadassign,不能直接影响线程 B。
    • 必须 store → write,线程 B 再通过 read → load 才能看到
  5. 规则 5:不允许无原因地丢弃 assign 的结果

    • 如果线程对变量 assign 了值,那必须 store → write 到主内存,除非后续又被覆盖。
  6. 规则 6:unlock 必须先把变量刷回主内存

    • 如果一个线程解锁某个变量,必须将工作内存中对应的变量通过 store → write 写入主内存。
    • 这正是 synchronized 保证可见性的根本原因!
  7. 规则 7:lock 必须清空工作内存中该变量的副本

    • 加锁时,线程会强制从主内存中重新读取最新值(read → load)。
    • 这也是为什么 synchronized 保证了可见性——它强制刷新变量
  8. 规则 8:线程不能“重排序”某些操作

    • JMM 对 volatile、synchronized 等操作插入 内存屏障,禁止某些重排,保证顺序性

JMM 下关键字行为对比表

特性 \ 关键字volatilesynchronizedfinal
可见性✅ 保证写后对其他线程立即可见✅ 解锁时刷新主内存,锁时强制读取主内存✅ 构造完成前不可见,构造后不可修改
原子性❌ 不保证复合操作的原子性✅ 临界区内操作具有原子性✅ 对象构造完成后其 final 字段只读
有序性(禁止重排序)✅ 写操作后插入 StoreStore + StoreLoad 屏障✅ 锁释放前插入内存屏障,禁止重排✅ 对 final 变量写操作不能重排到构造函数之后
JMM 写操作顺序assign → store → write(强制)assign → store → write(释放锁)assign → store → write(构造结束)
JMM 读操作顺序read → load → use(强制)read → load → use(加锁时强制)构造完成后从主内存读取一次即可
是否加锁❌ 无加锁✅ 加锁机制,线程互斥❌ 不涉及锁
典型应用场景状态标志(如 stop)、单例模式 DCL计数器、集合并发访问、事务操作常量、不可变对象(如构造完成后值不变)

volatile

  • 强制变量读写都直接对主内存操作(不走缓存)
  • 禁止写 → 后续读 的重排序
  • 不保证复合操作(如 i++)的原子性

synchronized

  • 通过锁机制保证:
    • 加锁时强制读取主内存最新值(可见性)
    • 解锁时强制写回主内存(可见性)
    • 整个代码块或方法为原子操作(原子性)
    • 禁止代码块内的指令重排序(有序性)

final

  • 构造函数中被 final 赋值的字段:
    • 构造函数执行完前不可见
    • 构造函数执行完后其他线程只能看到构造完成的值(不能是半初始化)
  • 对象发布安全,尤其在多线程中创建不可变对象(如 String)

volatile

volatile是java提供的轻量级的同步机制

  • 可见性

    • 当一个线程修改了 volatile 变量的值,新值会立刻被写入主内存,其他线程读取这个变量时,也会立刻从主内存读取到最新值,而不会使用自己的工作内存中的旧值

    • //如何保证可见性?
      这个问题问得非常好!
      
      ---
      
      ## ✅ 一、什么是“可见性”?
      
      可见性是指:**一个线程对共享变量的修改,能立刻被其他线程看到。**
      
      ### ❌ 默认情况下是做不到的!
      
      Java 中每个线程都有自己的 **工作内存(类似缓存)**,它可能不会立即从主内存中读取变量的最新值,也不会立刻把更新写回主内存。
      
      ---
      
      ## 🌰 举个例子(没用 volatile):
      
      ```java
      public class VisibilityDemo {
          static boolean running = true;
      
          public static void main(String[] args) throws InterruptedException {
              Thread t = new Thread(() -> {
                  while (running) {
                      // 空循环
                  }
                  System.out.println("Thread stopped.");
              });
      
              t.start();
      
              Thread.sleep(1000);
              running = false; // 修改 running,想让线程停止
          }
      }
      ```
      
      ### ❗ 结果:
      
      有时候线程 t 会 **一直卡在 while 循环里不退出**,因为它可能从自己的工作内存读取到了 `running = true` 的旧值,看不到你主线程改成了 false---
      
      ## ✅ 二、怎么用 `volatile` 保证可见性?
      
      ```java
      static volatile boolean running = true;
      ```
      
      一旦加上 `volatile`,**就有以下语义保障:**
      
      ### 🧠 Java 内存模型(JMM)下的语义:
      
      每次写入 `volatile` 变量:
      
      ***强制刷新到主内存**
      
      每次读取 `volatile` 变量:
      
      ***强制从主内存读取,而不是用线程缓存**
      
      这样所有线程读到的都是最新值,就保证了**可见性**---
      
      ## 📊 可见性的机制实现(稍深入)
      
      `volatile` 可见性的底层是靠 **内存屏障(Memory Barrier** 实现的。
      
      例如:
      
      * 写 `volatile`:插入 **StoreStore Barrier + StoreLoad Barrier**
      * 读 `volatile`:插入 **LoadLoad Barrier + LoadStore Barrier**
      
      这些内存屏障会让编译器、CPU在执行时**禁止缓存、刷新主内存、清空工作内存**,确保线程间看的是一致的数据。
      
      ---
      
      ## ✅ 总结一句话:
      
      > `volatile` 通过强制刷新主内存和从主内存读取,**保证了变量的可见性**,防止线程间“各看各的”。
      
      ---
      
    • public class VolatileDemo {
          // 不加volatile, 程序就会死循环
          // 加volatile, 可以保证可见性
          private volatile static int num = 0;
          public static void main(String[] args) {
      
              new Thread(() -> {
                  // 线程对主内存的变化感知不到
                  while (num == 0) {
      
                  }
              }).start();
              try {
                  TimeUnit.SECONDS.sleep(1);
              } catch (InterruptedException e) {
                  throw new RuntimeException(e);
              }
              System.out.println(num = 1);;
          }
      }
      
  • 禁止指令重排序

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

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

    • 禁止 JVM 对其修饰变量的读写操作进行指令重排序优化,但它不能保证整个方法或代码块的重排序

    • 实现一些双重检查锁(DCL)单例时非常关键

    • 指令重排序会改变我们代码的“逻辑顺序”,volatile 的作用就是禁止这种重排序,确保在多线程下的“代码写法”与“执行顺序”一致。

    • volatile 保证了变量赋值操作的顺序执行,避免了由于 JVM 或 CPU 的“性能优化”导致的多线程读取未初始化变量的问题

    • 你的问题非常关键,**指令重排序** 是理解 `volatile` 的一个核心点,很多并发 bug 都是它引起的。我们一步一步来理解这个概念。
      
      ---
      
      ## 🔧 什么是指令重排序?
      
      指令重排序(Instruction Reordering)是 **编译器或 CPU** 为了优化程序执行效率,在 **不改变单线程语义** 的前提下,对代码指令的执行顺序进行调整。
      
      ### ✅ 举个简单的例子:
      
      你写了下面这段 Java 代码:
      
      ```java
      int a = 1;
      int b = 2;
      int c = a + b;
      ```
      
      理论上执行顺序是:
      
      1. `a = 1`
      2. `b = 2`
      3. `c = a + b`
      
      但由于 a 和 b 没有依赖关系,**编译器可能会先执行 `b = 2` 再执行 `a = 1`**,在单线程下这样做不会出错,甚至可能更快。
      
      这就是**指令重排序**---
      
      ## 🔁 但在多线程下,这就危险了!
      
      我们看一个经典例子:**双重检查锁(DCL)单例模式**。
      
      ### ❌ 没有 volatile 的单例
      
      ```java
      public class Singleton {
          private static Singleton instance;
      
          public static Singleton getInstance() {
              if (instance == null) {
                  synchronized (Singleton.class) {
                      if (instance == null) {
                          instance = new Singleton(); // 多线程下的问题点
                      }
                  }
              }
              return instance;
          }
      }
      ```
      
      你以为 `instance = new Singleton()` 是一步完成的?
      
      实际上它分为三步:
      
      1. 分配内存空间
      2. 调用构造方法初始化对象
      3. 将 `instance` 指向这块内存
      
      但由于**指令重排序**,可能实际执行顺序是:
      
      1. 分配内存空间
      2. 将 `instance` 指向这块内存(此时对象还没初始化)
      3. 调用构造方法初始化对象
      
      ### 🚨 这时如果线程 B 来了:
      
      * 看到 `instance != null`(因为已经指向内存了)
      * 但对象其实还没初始化完!
      
      就会使用一个“**未初始化的半成品对象**”,这是非常危险的。
      
      ---
      
      ## ✅ 怎么解决?
      
      给 `instance` 加上 `volatile`:
      
      ```java
      private static volatile Singleton instance;
      ```
      
      这样就**禁止了指令重排序**,确保:
      
      > 构造函数初始化完毕之后,才将引用赋值给 `instance`。
      
      ---
      
      ## 🧠 总结一句话:
      
      > 指令重排序会改变我们代码的“逻辑顺序”,`volatile` 的作用就是禁止这种重排序,确保在多线程下的“代码写法”与“执行顺序”一致。
      
      ---
      
  • 不保证原子性

    • 多线程下可能出现并发写丢失,需使用 AtomicInteger 或加锁。

    • 适合状态标志、配置刷新等简单同步场景,不适合复杂的原子操作

    • /**
       * volatile不保证原子性
       * synchronized保证原子性
       */
      public class VolatileDemo02 {
      
          private volatile static int num = 0;
      
          public static void add() {
              num++;
          }
      
          // 理论上num的值应该为20000
          public static void main(String[] args) {
              for (int i = 0; i < 10; i++) {
                  new Thread(() -> {
                      for (int j = 0; j < 2000; j++) {
                          add();
                      }
                  }).start();
              }
              while (Thread.activeCount() > 2) {
                  Thread.yield();
              }
              System.out.println(Thread.currentThread().getName() + " " + num);
          }
      }
      
    • 在这里插入图片描述

    • 如果不使用Lock和synchronized, 该怎么保证原子性?

      • 使用原子类

      • private volatile static AtomicInteger num = new AtomicInteger();
        
        public static void add() {
          num.getAndIncrement();
        }
        // 底层调用的都是native方法, 都直接和操作系统挂钩, 在内存中修改值
        

深入理解单例模式

饿汉式单例模式

/**
 * 饿汉式单例模式
 * 可能会浪费空间
 */
public class Hungry {

    // 单例模式最重要的就是构造器私有
    private Hungry () {

    }

    // 饿汉式默认创建
    private final static Hungry HUNGRY = new Hungry();

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

懒汉式单例模式

会有问题的饿汉式

package org.java.juc.test.single;

/**
 * 懒汉式单例模式
 */
public class LazyMan {
    
    private LazyMan () {
        System.out.println(Thread.currentThread().getName() + "-> ok");
    }
    
    private  static LazyMan lazyMan;

    public static LazyMan getLazyMan() {
        if (lazyMan == null) {
            lazyMan = new LazyMan();
        }
        return lazyMan;
    }

    // 但是这种写法在多线程下会有问题
    public static void main(String[] args) {
        for (int i = 0; i < 10; i++) {
            new Thread(() -> {
                getLazyMan();
            }).start();
        }
    }
}

加锁之后才能保证在多线程下也没有问题

双重检测锁模式的单例模式 DCL懒汉式

package org.java.juc.test.single;

/**
 * 懒汉式单例模式
 */
public class LazyMan {
    
    private LazyMan () {
        System.out.println(Thread.currentThread().getName() + "-> ok");
    }
    
    private volatile static LazyMan lazyMan;

    public static LazyMan getLazyMan() {
        if (lazyMan == null) {
            synchronized (LazyMan.class) {
                if (lazyMan == null) {
                    lazyMan = new LazyMan();
                }
            }
        }
        return lazyMan;
    }

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

静态内部类实现单例模式

package org.java.juc.test.single;

/**
 * 静态内部类
 */
public class Holder {
    private Holder () {
        
    }
    
    public static Holder getInstance() {
        return InnerClass.HOLDER;
    }
    
    public static class InnerClass {
        private static final Holder HOLDER = new Holder();
    }
}

反射破坏单例模式

package org.java.juc.test.single;

import java.lang.reflect.Constructor;

/**
 * 懒汉式单例模式
 */
public class LazyMan {
    
    private LazyMan () {
        System.out.println(Thread.currentThread().getName() + "-> ok");
    }
    
    private volatile static LazyMan lazyMan;

    public static LazyMan getLazyMan() {
        if (lazyMan == null) {
            synchronized (LazyMan.class) {
                if (lazyMan == null) {
                    lazyMan = new LazyMan();
                }
            }
        }
        return lazyMan;
    }

    public static void main(String[] args) throws Exception {
        LazyMan lazyMan = LazyMan.getLazyMan();
        Constructor<LazyMan> declaredConstructor = LazyMan.class.getDeclaredConstructor(null);
        // 无视私有构造器
        declaredConstructor.setAccessible(true);
        LazyMan lazyMan1 = declaredConstructor.newInstance();

        System.out.println(lazyMan);    //  org.java.juc.test.single.LazyMan@4d7e1886
        System.out.println(lazyMan1);   //  org.java.juc.test.single.LazyMan@3cd1a2f1
    }
}

// 对于这种情况, 我们可以采取下面的方法进行保护
// 在私有构造器那再加一把锁
private LazyMan () {
  synchronized (LazyMan.class) {
    if (lazyMan != null) {
      throw new RuntimeException("不要试图使用反射破坏单例模式");
    }
  }
  System.out.println(Thread.currentThread().getName() + "-> ok");
}

// 上面这种保护还是会有问题, 如果创建对象的时候, 不使用LazyMan.getLazyMan()进行创建, 而都是用反射进行创建, 仍然可以成功
public static void main(String[] args) throws Exception {
  // LazyMan lazyMan = LazyMan.getLazyMan();
  Constructor<LazyMan> declaredConstructor = LazyMan.class.getDeclaredConstructor(null);
  // 无视私有构造器
  declaredConstructor.setAccessible(true);
  LazyMan lazyMan = declaredConstructor.newInstance();
  LazyMan lazyMan1 = declaredConstructor.newInstance();

  System.out.println(lazyMan);    //  org.java.juc.test.single.LazyMan@4d7e1886
  System.out.println(lazyMan1);   //  org.java.juc.test.single.LazyMan@3cd1a2f1
}

// 此时我们可以在加一个成员变量, 作为一个标志位
private static boolean check = false;
private LazyMan () {
  synchronized (LazyMan.class) {
    if (check == false) {
      check = true;
    } else {
      throw new RuntimeException("不要试图使用反射破坏单例模式");
    }
  }
  System.out.println(Thread.currentThread().getName() + "-> ok");
}

// 此时还是有办法破解
public static void main(String[] args) throws Exception {
  // LazyMan lazyMan = LazyMan.getLazyMan();

  Field check = LazyMan.class.getDeclaredField("check");
  check.setAccessible(true);

  Constructor<LazyMan> declaredConstructor = LazyMan.class.getDeclaredConstructor(null);
  // 无视私有构造器
  declaredConstructor.setAccessible(true);
  LazyMan lazyMan = declaredConstructor.newInstance();
  // 在创建一次实例之后, 将check的值改回去
  check.set(lazyMan, false);
  LazyMan lazyMan1 = declaredConstructor.newInstance();

  System.out.println(lazyMan);    //  org.java.juc.test.single.LazyMan@4d7e1886
  System.out.println(lazyMan1);   //  org.java.juc.test.single.LazyMan@3cd1a2f1
}

枚举类

由于上面的测试, 我们可以了解到单例模式并不是安全的, 因此我们可以使用枚举类保证单例模式的安全性

package org.java.juc.test.single;

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

public enum EnumSingle {
    INSTANCE;

    public EnumSingle getInstance(){
        return INSTANCE;
    }
}

class Test {
    public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
        EnumSingle instance = EnumSingle.INSTANCE;
        Constructor<EnumSingle> declaredConstructor = EnumSingle.class.getDeclaredConstructor(String.class, int.class);
        declaredConstructor.setAccessible(true);
        //  Cannot reflectively create enum objects
        EnumSingle enumSingle = declaredConstructor.newInstance();
        System.out.println(instance);
        System.out.println(enumSingle);
    }
}

深入理解CAS

CAS : compareAndSet 比较并交换

CAS是CPU的并发原语

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

package org.java.juc.test.cas;

import java.util.concurrent.atomic.AtomicInteger;

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

        // 如果期望的值达到了, 就更新, 否则不做操作
        System.out.println(atomicInteger.compareAndSet(2020, 2021));
        System.out.println(atomicInteger.get());

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

// expect: 期望达到的值
// update: 需要更新到的值
public final boolean compareAndSet(int expect, int update) {
  return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}

sun.misc.Unsafe

sun.misc.Unsafe 是 Java 中一个提供底层操作内存的类

它不是公开 API,但 Atomic* 系列类都依赖它

atomicInteger.getAndIncrement() 源码分析

atomicInteger.getAndIncrement()就相当于是 num++ ;

public final int getAndIncrement() {
  // this代表当前对象
  return unsafe.getAndAddInt(this, valueOffset, 1);
}

public class AtomicInteger extends Number implements java.io.Serializable {
    private static final long serialVersionUID = 6214790243416807050L;

    // setup to use Unsafe.compareAndSwapInt for updates
    private static final Unsafe unsafe = Unsafe.getUnsafe();
    private static final long valueOffset;

    static {
        try {
          	// valueOffset: 这是 value 字段的内存偏移地址,用来告诉 Unsafe 要操作哪个字段
            valueOffset = unsafe.objectFieldOffset
                (AtomicInteger.class.getDeclaredField("value"));
        } catch (Exception ex) { throw new Error(ex); }
    }

    private volatile int value;

....
}

// unsafe.getAndAddInt(this, valueOffset, 1);
public final int getAndAddInt(Object obj, long offset, int delta) {
  int v;
  do {
    // 获取内存中的对象obj中offset的值
    v = getIntVolatile(obj, offset);
    // compareAndSwapInt是更底层的方法
    // 调用compareAndSwapInt获取obj中offset的值, 如果这个值, 就是v, 就将这个值加上传进来的delta
  } while (!compareAndSwapInt(obj, offset, v, v + delta));
  return v;
}
这个是一个内存操作, 效率很高

CAS(Compare And Swap 或 Compare And Set) 是一种用于实现并发编程无锁(lock-free)算法的技术,常见于多线程环境下的原子操作

CAS 的优点

  1. 无锁操作(Lock-Free):不会因为线程阻塞造成性能下降。
  2. 高并发性能好:适用于频繁写操作但冲突概率低的场景。
  3. 原子性强:由 CPU 指令级别保证原子性

CAS 的缺点

1. ABA 问题

某线程读取到的是 A,另一个线程将 A 改为 B 再改回 A,表面上看没变,实际已发生了变化。

解决方案:

  • 使用 AtomicStampedReferenceAtomicMarkableReference,加上版本号或标志位

2. 自旋消耗 CPU

如果多个线程反复尝试更新失败,会进入自旋(while 循环不断重试),这会浪费 CPU。

3. 只能更新单个变量

CAS 只能保证单个变量的原子性,如果要更新多个变量,需要加锁或用其他方案(如 AtomicReference 封装类)

ABA问题

/**
 * ABA问题
 */
public class CASDemo {
    public static void main(String[] args) {
        AtomicInteger atomicInteger = new AtomicInteger(2020);

        // 如果期望的值达到了, 就更新, 否则不做操作
        // ======================捣乱的线程======================
        System.out.println(atomicInteger.compareAndSet(2020, 2021));
        System.out.println(atomicInteger.get());

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

        // ======================期望的线程======================
        System.out.println(atomicInteger.compareAndSet(2020, 444));
        System.out.println(atomicInteger.get());
    }
}

原子引用

解决ABA问题

使用 AtomicStampedReferenceAtomicMarkableReference,加上版本号或标志位

/**
 * 使用原子引用解决ABA问题
 */
public class CASDemo02 {
    // 如果泛型是一个包装类, 注意对象的引用问题
    static AtomicStampedReference<Integer> atomicStampedReference = new AtomicStampedReference(12, 1);

    public static void main(String[] args) {
        new Thread(() -> {
            System.out.println("a1 => " + atomicStampedReference.getStamp());
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            System.out.println(atomicStampedReference.compareAndSet(12, 13,
                    atomicStampedReference.getStamp(), atomicStampedReference.getStamp() + 1));
            System.out.println("a2 => " + atomicStampedReference.getStamp());

            System.out.println(atomicStampedReference.compareAndSet(13, 12,
                    atomicStampedReference.getStamp(), atomicStampedReference.getStamp() + 1));
            System.out.println("a3 => " + atomicStampedReference.getStamp());


        }, "a").start();

        new Thread(() -> {
            int stamp = atomicStampedReference.getStamp();
            System.out.println("b1 => " + stamp);
            try {
                TimeUnit.SECONDS.sleep(3);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            System.out.println(atomicStampedReference.compareAndSet(12, 15,
                    stamp, stamp + 1));
            System.out.println("b2 => " + atomicStampedReference.getStamp());
        }, "b").start();
    }
}

注意事项:

Integer使用了对象缓存机制, 默认范围是-128~127, Integer对象是在IntegerCache.cache产生, 会复用已有对象, 这个区间的Integer值可以直接使用==进行比较判断, 但是这个区间之外的所有数据, 都会在堆上产生, 而不是复用已有对象

推荐使用静态工厂方法valueOf获取对象实例, 而不是new, 因为valueOf使用缓存, 而new一定会创建新的对象分配新的内存空间

所有的相同类型的包装类对象之间值的比较, 全部使用equals方法进行比较

各种锁的理解

公平锁, 非公平锁

  • 公平锁

    • 公平锁是指多个线程按照申请锁的先后顺序来获取锁,类似排队买票,先来先服务

    • ReentrantLock lock = new ReentrantLock(true); // 传入 true 表示公平锁

    • public ReentrantLock(boolean fair) {
          sync = fair ? new FairSync() : new NonfairSync();
      }
      
  • 非公平锁

    • 非公平锁在获取锁时不保证顺序,可能插队

    • ReentrantLock lock = new ReentrantLock(); // 默认就是非公平锁

    • public ReentrantLock() {
        sync = new NonfairSync();
      }
      

可重入锁

  • 一个线程已经持有锁,如果它再次请求该锁,可以成功获取,而不会被阻塞
  • 就像一个人进了屋子,还可以自由进出,不需要重新申请门票

synchronized

/**
 * Synchronized
 */
public class Demo01 {
    public static void main(String[] args) {
        Phone phone = new Phone();

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

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

    }
}

class Phone {

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

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

最终结果
  Asms
	Acall
	Bsms
	Bcall
  
线程A拿到锁之后, 一直到执行完call()才释放锁

Lock

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

/**
 * Lock
 */
public class Demo02 {
    public static void main(String[] args) {
        Phone2 phone = new Phone2();

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

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

    }
}

class Phone2 {
    Lock lock = new ReentrantLock();

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

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

自旋锁

  • 线程在尝试获取锁时,不是阻塞等待,而是持续循环尝试获取锁,这种方式叫“自旋”。
  • 适用于锁占用时间非常短的场景,避免线程切换带来的开销
  • 如果锁长期无法获得,线程会一直消耗 CPU,浪费资源
  • java.util.concurrent.atomic 包下的一些类,如 AtomicInteger 中基于 CAS 的操作就是一种自旋思想
import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.atomic.AtomicStampedReference;

/**
 * 使用CAS创建自己的自旋锁
 */
public class SpinLockDemo {
    AtomicReference<Thread> atomicReference = new AtomicReference<>();

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

        while (!atomicReference.compareAndSet(null, thread)) {

        }
    }

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


/**
 * 测试自旋锁
 */
public class SpinLockTest {

    public static void main(String[] args) {
        SpinLockDemo lock = new SpinLockDemo();
        new Thread(()->{
            lock.myLock();
            try {
                TimeUnit.SECONDS.sleep(4);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            } finally {
                lock.myUnLock();
            }
        }, "A").start();

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

最终结果:
A===> MyLock
B===> MyLock
A===> myUnLock
B===> myUnLock

死锁

  • 多个线程互相等待对方释放锁资源,导致所有线程都无法继续执行

  • 必要条件(四个条件同时满足会发生死锁):

    • 互斥**(资源只能被一个线程占有)**

    • 持有并等待

    • 不可抢占

    • 循环等待*

  • 避免死锁的策略:

    • 尽量使用定时锁尝试(如 tryLock
    • 避免嵌套锁
    • 保持统一的加锁顺序

如何排查死锁?

使用 jps + jstack

package org.java.juc.test.lock;

import java.util.concurrent.TimeUnit;

/**
 * 死锁
 */
public class DeadLockDemo {
    public static void main(String[] args) {
        String lockA = "lockA";
        String lockB = "lockB";

        new Thread(new MyThread(lockA, lockB), "T1").start();
        new Thread(new MyThread(lockB, lockA), "T2").start();
    }
}

class MyThread implements Runnable {

    private String lockA;
    private String lockB;

    public MyThread(String lockA, String lockB) {
        this.lockA = lockA;
        this.lockB = lockB;
    }

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

            }
        }
    }
}
  1. 使用jps -l命令查看全部运行的java进程
  2. 使用jstack 进程号查看堆栈信息

在这里插入图片描述

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值