JUC

39 阅读24分钟

1、什么是JUC?

java.util.concurrent 下面的三个包

2、线程和进程

进程:进程是资源(CPU、内存等)分配的基本单位,它是程序执行时的一个实例。一个程序,比如 qq.exe;一个进程至少包含一个线程

线程:线程是程序执行时的最小单位,它是进程的一个执行流,是CPU调度和分派的基本单位。

java默认有两个线程:main线程 + GC线程

java是不可以直接开启线程的,只能通过本地方法栈调用本地方法开启线程(strat0)

3、并发、并行

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

CPU单核,模拟出来多个线程,快速切换各个线程

并行(多个线程同时进行):

CPU多核,多个线程可以同时进行,线程池

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

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

4、线程状态

    public enum State {
        // 新建
        NEW,
        // 运行
        RUNNABLE,
        // 阻塞
        BLOCKED,
        // 等待,死死的等
        WAITING,
        // 超时等待,有个时间
        TIMED_WAITING,
        // 结束
        TERMINATED;
    }

5、wait 和sleep 区别

  • 来自不同的类
    • wait() 来自 Object 类
    • sleep() 来自 Thread 类
  • 关于所得释放
    • wait() 会释放锁
    • sleep() 不会
  • 使用的范围不同
    • wait() 必须在同步代码块中使用
    • sleep() 可以任何地方使用
  • 使用需要捕获异常
    • wait() 不需要捕获异常
    • sleep() 必须捕获异常( try{} catch{} )

6、Lock

代码实现

public void sale() {
        Lock lock = new ReentrantLock();
        lock.lock();
        try {
            // 业务代码
            if (number > 0) {
                System.out.println(Thread.currentThread().getName() + "卖出了第" + (number--) + "张票,剩余" + number);
            }
        } finally {
            lock.unlock();
        }
    }

7、synchronized 和 Lock 的区别:

  • synchronized 是Java关键字 ,Lock 是一个类
  • synchronized 无法获取锁的状态,Lock 可以判断是否获取锁
  • synchronized 会自动释放锁,Lock 必须手动释放,不然会导致死锁
  • synchronized 发生线程阻塞时,会一直等待,Lock 可以尝试获取,获取不到执行另外的逻辑
  • synchronized 适合锁少量的同步代码,Lock 适合大量的同步代码

8、生产者和消费者模式(synchronized ->Lock)

代码实现

public class Test1 {
    public static void main(String[] args) {
        Data2 data = new Data2();
        new Thread(() -> {
            for (int i = 0; i < 5; i++) {
                try {
                    data.increment();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }).start();

        new Thread(() -> {
            for (int i = 0; i < 5; i++) {
                try {
                    data.increment();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }).start();

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

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

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

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

    }

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

9、八锁问题

/**
 * @Author: YanChuYu
 * @Date: 2022/07/01
 * @Description: 八🔒问题:
 *   1、synchronized 锁的是方法的调用者,谁先拿到锁谁先执行;sleep()方法不会释放锁
 *   2、当同一个对象调用同步方法和普通方法,同步方法调用 sleep() 后,普通方法先执行,因为普通方法不需要获取锁也可以执行
 *   3、当两个不同的对象调用同步方法,同步方法内没有 sleep() 的先执行;因为两个锁对象不一致,不存在等待拿锁的情况
 *   4、静态同步方法锁的是当前类,因此使用同一个类调用两个静态同步方法存在锁的竞争,先拿到锁的先执行
 *
 */
public class TestLock {
    public static void main(String[] args) {
        Phone phone = new Phone();

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


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

public class TestLock1 {
    public static void main(String[] args) {
        Phone phone = new Phone();

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


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

public class TestLock2 {
    public static void main(String[] args) {
        Phone phone1 = new Phone();
        Phone phone2 = new Phone();

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


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

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

        new Thread(() -> {
            Phone.sendEmail();
        }, "A").start();


        new Thread(() -> {
            Phone.sendWechat();
        }, "B").start();
    }
}

public class TestLock4 {
    public static void main(String[] args) {
        Phone phone = new Phone();

        new Thread(() -> {
            Phone.sendEmail();
        }, "A").start();


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

class Phone {
    public synchronized void sendMsg() {
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + "=>发短信");
    }

    public synchronized void call() {
        System.out.println(Thread.currentThread().getName() + "=>打电话");
    }

    /**
     * 该方法没有锁,不受锁的影响(普通方法)
     */
    public void hello() {
        System.out.println(Thread.currentThread().getName() + "=>hello");
    }

    public static synchronized void sendEmail() {
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + "=>发邮件");
    }

    public static synchronized void sendWechat() {
        System.out.println(Thread.currentThread().getName() + "=>发微信");
    }
}

10、不安全的集合类

/**
 * @Author: YanChuYu
 * @Date: 2022/07/01
 * @Description: ConcurrentModificationException:并发修改异常
 *
 *   并发下,ArrayList是不安全的,出现 ConcurrentModificationException
 *   解决方案:
 *   1、List<String> list = new Vector<>();
 *   2、List<String> list = Collections.synchronizedList(new ArrayList<>());
 *   3、List<String> list = new CopyOnWriteArrayList<>(); CopyOnWrite:写入时复制,写入的时候避免覆盖,造成数据问题,COW 计算机程序设计领域的一中优化策略
 *
 *   CopyOnWriteArrayList 比 Vector的优点:
 *   Vector使用synchronized关键字,影响效率
 *
 */
public class ListTest {
    public static void main(String[] args) {
        List<String> list = new CopyOnWriteArrayList<>();
//        List<String> list = new ArrayList<>();
        for (int i = 1; i <= 10; i++) {
            new Thread(() -> {
                list.add(UUID.randomUUID().toString().substring(0, 5));
                System.out.println(list);
            }, String.valueOf(i)).start();
        }
    }
}
/**
 * @Author: YanChuYu
 * @Date: 2022/07/01
 * @Description: ConcurrentModificationException
 *              解决方案:
 *              1Set set = Collections.synchronizedSet(new HashSet<String>());
 *              2Set set = new CopyOnWriteArraySet<String>();
 */
public class SetTest {
    public static void main(String[] args) {
        Set set = new HashSet<String>();
        for (int i = 1; i <= 10; i++) {
            new Thread(()->{
                set.add(UUID.randomUUID().toString().substring(0, 5));
                System.out.println(set);
            },String.valueOf(i)).start();
        }
    }
}
/**
 * @Author: YanChuYu
 * @Date: 2022/07/01
 * @Description: ConcurrentModificationException
 *              解决方案:
 *              1、Map<String, Object> map = new ConcurrentHashMap<>();
 */
public class MapTest {
    public static void main(String[] args) {
        Map<String, Object> map = new ConcurrentHashMap<>(16);
//        Map<String, Object> map = new HashMap<>(16);
        for (int i = 1; i <= 10; i++) {
            new Thread(()->{
                map.put(Thread.currentThread().getName(), UUID.randomUUID().toString().substring(0,5));
                System.out.println(map);
            }, String.valueOf(i)).start();
        }
    }
}

11、Callable接口(有返回值、可以抛异常)

public class CallableTest {
    public static void main(String[] args) {
        // 适配类
        FutureTask<String> task = new FutureTask<>(new A());
        // 两个线程并发执行,只会打印一次 call(),结果存在缓存
        new Thread(task).start();
        new Thread(task).start();
        try {
            // 获取Callable接口返回值(可能比较耗时,引发阻塞!一般放在最后,或者采用异步通信)
            System.out.println(task.get());
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }

    }
}

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

12、常用的辅助类

12.1、CountDownLatch(加法计数器)

一种同步辅助,允许一个或多个线程等待,直到在其他线程中执行的一组操作完成。

CountDownLatch使用给定的计数进行初始化。由于调用了countDown方法, await方法一直阻塞,直到当前计数达到零,之后所有等待的线程都被释放,任何后续的await调用立即返回。这是一次性现象——计数无法重置。如果您需要重置计数的版本,请考虑使用CyclicBarrier 。

CountDownLatch是一种多功能同步工具,可用于多种用途。使用计数 1 初始化的CountDownLatch用作简单的开/关锁存器或门:所有调用await的线程在门处等待,直到它被调用countDown的线程打开。初始化为N的CountDownLatch可用于使一个线程等待,直到N个线程完成某个动作,或者某个动作已完成 N 次。

CountDownLatch的一个有用属性是它不需要调用countDown的线程在继续之前等待计数达到零,它只是阻止任何线程继续await ,直到所有线程都可以通过。

示例用法:这是一对类,其中一组工作线程使用两个倒计时锁存器:

  • 第一个是启动信号,它阻止任何工人继续前进,直到司机准备好让他们继续前进;
  • 第二个是完成信号,允许驱动程序等待所有工作人员完成
/**
 * @Author: YanChuYu
 * @Date: 2022/07/02
 * @Description: CountDownLatch:加法计数器
 */
public class CountDownLatchTest {
    public static void main(String[] args) throws InterruptedException {
        // 总数为6
        CountDownLatch countDownLatch = new CountDownLatch(6);
        for (int i = 1; i <= 6; i++) {
            new Thread(()->{
                System.out.println(Thread.currentThread().getName() + " GO OUT!");
                // 手动进行加一操作
                countDownLatch.countDown();
            },String.valueOf(i)).start();
        }

        countDownLatch.await(); // 确保计数器归零后再向下执行
        System.out.println("close the door");
    }
}

12.2、CyclicBarrier(减法计数器)

一种同步辅助工具,它允许一组线程相互等待以达到共同的障碍点。 CyclicBarriers 在涉及固定大小的线程组的程序中很有用,这些线程组必须偶尔相互等待。屏障被称为循环的,因为它可以在等待线程被释放后重新使用。

CyclicBarrier支持一个可选的Runnable命令,该命令在每个屏障点运行一次,在队伍中的最后一个线程到达之后,但在任何线程被释放之前。此屏障操作对于在任何一方继续之前更新共享状态很有用。

/**
 * @Author: YanChuYu
 * @Date: 2022/07/02
 * @Description: 加法计数器
 */
public class CyclicBarrierTest {
    public static void main(String[] args) {
        //
        CyclicBarrier cyclicBarrier = new CyclicBarrier(7,()->{
            System.out.println("召唤神龙成功!");
        });
        for (int i = 1; i <= 7; i++) {
            int finalI = i;
            new Thread(()->{
                System.out.println(Thread.currentThread().getName() + "集齐了第" + finalI + "颗龙珠");
                try {
                    // 会自动进行减一操作
                    cyclicBarrier.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } catch (BrokenBarrierException e) {
                    e.printStackTrace();
                }
            },String.valueOf(i)).start();
        }
    }
}

12.3、Semaphore

计数信号量。从概念上讲,信号量维护一组许可。如有必要,每个acquire块,直到获得许可,然后获取它。每个release都会添加一个许可,可能会释放一个阻塞的收购方。但是,没有使用实际的许可对象; Semaphore只是对可用数量进行计数并采取相应措施。

信号量通常用于限制可以访问某些(物理或逻辑)资源的线程数。例如,这是一个使用信号量来控制对项目池的访问的类

/**
 * @Author: YanChuYu
 * @Date: 2022/07/02
 * @Description: Semaphore:限流的时候使用,控制最大并发数
 */
public class SemaphoreTest {
    public static void main(String[] args) {
        // 线程数量
        Semaphore semaphore = new Semaphore(5);
        for (int i = 1; i <= 6 ; i++) {
            new Thread(()->{
                try {
                    semaphore.acquire();
                    System.out.println(Thread.currentThread().getName() + "拿到车位");
                    TimeUnit.SECONDS.sleep(2);
                    System.out.println(Thread.currentThread().getName() + "离开车位");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    semaphore.release();
                }
            },String.valueOf(i)).start();
        }
    }
}

13、读写锁 ReentrantReadWriteLock

public class ReadWriteLockTest {
    public static void main(String[] args) {
        MyCache myCache = new MyCache();

        // 写入
        for (int i = 1; i <= 5; i++) {
            int finalI = i;
            new Thread(() -> {
                myCache.set(finalI+"",finalI);
            }, String.valueOf(i)).start();
        }

        // 读取
        for (int i = 1; i <= 5; i++) {
            int finalI = i;
            new Thread(() -> {
                myCache.get(finalI+"");
            }, String.valueOf(i)).start();
        }
    }
}

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

    public void set(String key, Object value) {
        reentrantReadWriteLock.writeLock().lock();
        try {
            System.out.println(Thread.currentThread().getName() + "写入" + key);
            map.put(key, value);
            System.out.println(Thread.currentThread().getName() + "写入成功");
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            reentrantReadWriteLock.writeLock().unlock();
        }

    }

    public Object get(String key) {
        reentrantReadWriteLock.readLock().lock();
        try {
            System.out.println(Thread.currentThread().getName() + "读取" + key);
            return map.get(key);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            reentrantReadWriteLock.readLock().unlock();
        }
        return null;
    }
}

14、阻塞队列(BlockingQueue)

public class ArrayBlockingQueueTest {
    public static void main(String[] args) throws InterruptedException {
//        test1();
//        test2();
//        test3();
        test4();

    }

    /**
     * add:IllegalStateException: Queue full
     */
    public static void test1() {
        ArrayBlockingQueue blockingQueue = new ArrayBlockingQueue(3);
        System.out.println(blockingQueue.add("A"));
        System.out.println(blockingQueue.add("B"));
        System.out.println(blockingQueue.add("C"));
//        System.out.println(blockingQueue.add("D"));
        System.out.println("=====================");

        // NoSuchElementException
        System.out.println(blockingQueue.remove());
        System.out.println(blockingQueue.remove());
        System.out.println(blockingQueue.remove());
        System.out.println(blockingQueue.remove());
    }

    /**
     * offer:不会抛异常
     */
    public static void test2() {
        ArrayBlockingQueue blockingQueue = new ArrayBlockingQueue(3);
        System.out.println(blockingQueue.offer("A"));	//true
        System.out.println(blockingQueue.offer("B"));	//true
        System.out.println(blockingQueue.offer("C"));	//true
        System.out.println(blockingQueue.offer("D"));	//false

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

        System.out.println(blockingQueue.poll());	//A
        System.out.println(blockingQueue.poll());	//B
        System.out.println(blockingQueue.poll());	//C
        System.out.println(blockingQueue.poll());	//null
    }

    /**
     * put 队列满了会一直阻塞
     * @throws InterruptedException
     */
    public static void test3() throws InterruptedException {
        ArrayBlockingQueue blockingQueue = new ArrayBlockingQueue(3);
        blockingQueue.put("A");
        blockingQueue.put("B");
        blockingQueue.put("C");
        blockingQueue.put("D");

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

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

    /**
     * 队列满了超时等待,超过时间后退出
     * @throws InterruptedException
     */
    public static void test4() throws InterruptedException {
  		// true
		// true
		// true
		// false
        ArrayBlockingQueue blockingQueue = new ArrayBlockingQueue(3);
        System.out.println(blockingQueue.offer("A"));						
        System.out.println(blockingQueue.offer("B"));
        System.out.println(blockingQueue.offer("C"));
        System.out.println(blockingQueue.offer("D",2,TimeUnit.SECONDS));

        System.out.println("================");
        // A
        // B
        // C
        // null
        System.out.println(blockingQueue.poll());
        System.out.println(blockingQueue.poll());
        System.out.println(blockingQueue.poll());
        System.out.println(blockingQueue.poll(2,TimeUnit.SECONDS));
    }
}

15、SynchronousQueue

/**
 * @Author: YanChuYu
 * @Date: 2022/07/03
 * @Description: 同步队列:最多只能存储一个元素,消费完才能继续往里面存储
 */
public class SynchronousQueueTest {
    public static void main(String[] args) {
        BlockingQueue<String> synchronousQueue = new SynchronousQueue<>();

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

        new Thread(()->{
            try {
                TimeUnit.SECONDS.sleep(2);
                System.out.println(Thread.currentThread().getName() + synchronousQueue.take());
                TimeUnit.SECONDS.sleep(2);
                System.out.println(Thread.currentThread().getName() + synchronousQueue.take());
                TimeUnit.SECONDS.sleep(2);
                System.out.println(Thread.currentThread().getName() + synchronousQueue.take());
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        },"线程二").start();
    }
}

16、线程池

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

线程池的好处:

  • 降低资源消耗
  • 提高响应速度
  • 方便管理

三大方法

public class Test {
    public static void main(String[] args) {
        // 单个线程的线程池
        ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor();
        // 指定线程个数的线程池
        ExecutorService executorService = Executors.newFixedThreadPool(5);
        // 可伸缩的线程池
        ExecutorService cachedThreadPool = Executors.newCachedThreadPool();

        try {
            for (int i = 1; i <= 10; i++) {
                cachedThreadPool.execute(() -> {
                    System.out.println(Thread.currentThread().getName());
                });
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            // 关闭线程池
            cachedThreadPool.shutdown();
        }
    }
}

七大参数

/**
使用给定的初始参数创建一个新的ThreadPoolExecutor 。
参数:
corePoolSize - 保留在池中的线程数,即使它们是空闲的,除非设置allowCoreThreadTimeOut
maximumPoolSize – 池中允许的最大线程数
keepAliveTime – 当线程数大于核心时,这是多余的空闲线程在终止前等待新任务的最长时间。
unit – keepAliveTime参数的时间单位
workQueue – 用于在执行任务之前保存任务的队列。此队列将仅保存由execute方法提交的Runnable任务。
threadFactory – 执行器创建新线程时使用的工厂
handler – 由于达到线程边界和队列容量而阻塞执行时使用的处理程序
抛出:
IllegalArgumentException – 如果以下条件之一成立: corePoolSize < 0 keepAliveTime < 0 maximumPoolSize <= 0 maximumPoolSize < corePoolSize
NullPointerException – 如果workQueue或threadFactory或handler为 null
*/
    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;
    }

四种拒绝策略

当提交任务数量超过核心线程数(corePoolSize),会优先将任务放入 workQueue 阻塞队列中等待,当阻塞对队列饱和以后,会扩充线程池中的线程数,直到达到最大线程数(maximumPoolSize),此时再有任务进来,会触发

new ThreadPoolExecutor.AbortPolicy()

new ThreadPoolExecutor.CallerRunsPolicy()

new ThreadPoolExecutor.DiscardOldestPolicy()

new ThreadPoolExecutor.DiscardPolicy()

/**
 * @Author: YanChuYu
 * @Date: 2022/07/04
 * @Description: 最大线程数如何定义:
 *               1、CPU密集型(计算密集的程序):CPU 核数 + 1(备份线程)
 *               2、IO密集型:2 * CPU 核数
 */
public class Test1 {
    public static void main(String[] args) {
        // 获取运行时可用处理器个数
        System.out.println(Runtime.getRuntime().availableProcessors());
        ThreadPoolExecutor poolExecutor = new ThreadPoolExecutor(
                2,
                5,
                3,
                TimeUnit.SECONDS,
                new LinkedBlockingDeque<>(3),
                Executors.defaultThreadFactory(),
                // 拒绝策略,当最大线程数和阻塞队列中都满了抛出异常
//                new ThreadPoolExecutor.AbortPolicy());
                // 哪来的去哪里
//                new ThreadPoolExecutor.CallerRunsPolicy());
                // 不会抛出异常,也不会执行任务
//                new ThreadPoolExecutor.DiscardPolicy());
                // 队列满了尝试和最早的竞争,要是第一线程结束了,就会执行,否则不执行
                new ThreadPoolExecutor.DiscardOldestPolicy());
        try {
            for (int i = 1; i <= 9; i++) {
                poolExecutor.execute(() -> {
                    System.out.println(Thread.currentThread().getName());
                });
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            poolExecutor.shutdown();
        }

    }
}

17、四大函数式接口(Function、Consumer)

Function 函数式接口,有一个入参,有一个返回值

public interface Function<T, R> {

    R apply(T t);

}
public class Test {
    public static void main(String[] args) {
        Function<String, String> function = (s) -> s + 1;
        System.out.println(function.apply("a"));
    }
}

Predicate 断定型接口

public interface Predicate<T> {

    /**
     * Evaluates this predicate on the given argument.
     *
     * @param t the input argument
     * @return {@code true} if the input argument matches the predicate,
     * otherwise {@code false}
     */
    boolean test(T t);
}
public class Test1 {
    public static void main(String[] args) {
        Predicate<String> predicate = String::isEmpty;
        System.out.println(predicate.test(""));
    }
}

Consumer 消费型接口

public interface Consumer<T> {

    /**
     * Performs this operation on the given argument.
     *
     * @param t the input argument
     */
    void accept(T t);
}
public class Test2 {
    public static void main(String[] args) {
        Consumer<String> consumer = System.out::println;
        consumer.accept("AD");
    }
}

Supplier 供给型接口

public interface Supplier<T> {

    /**
     * Gets a result.
     *
     * @return a result
     */
    T get();
}
public class Test3 {
    public static void main(String[] args) {
        Supplier<String> supplier = () -> "A";
        System.out.println(supplier.get());
    }
}

18、Stream 流式计算

/**
 * @Author: YanChuYu
 * @Date: 2022/07/04
 * @Description: 题目要求:一分钟内完成此题,只能使用一行代码实现!
 * 现有五个用户!筛选:
 * 1、ID 必须是偶数的
 * 2、年龄必须大于23的
 * 3、用户名转为大写
 * 4、用户字母倒序排列
 * 5、只输出一个用户
 */
public class Test {
    public static void main(String[] args) {
        List<User> userList = new ArrayList<>();
        userList.add(new User(1, "a", 21));
        userList.add(new User(2, "b", 22));
        userList.add(new User(3, "c", 23));
        userList.add(new User(4, "d", 24));
        userList.add(new User(5, "e", 25));

        userList.stream()
                .filter(user -> user.getId() % 2 == 0)
                .filter(user -> user.getAge() > 23)
                .map(user -> user.getName().toUpperCase(Locale.ROOT))
                .sorted(Comparator.reverseOrder())
                .limit(1)
                .forEach(System.out::println);
    }
}

19、ForkJoin (分支合并)

ForkJoin 在 jdk1.7 中引入,并行执行任务!提高效率!处理大数据!

ForkJoin 特点:工作窃取

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

工作窃取算法的优点是充分利用线程进行并行计算,并减少了线程间的竞争,其缺点是在某些情况下还是存在竞争,比如双端队列里只有一个任务时。并且消耗了更多的系统资源,比如创建多个线程和多个双端队列。

/**
 * @Author: YanChuYu
 * @Date: 2022/07/04
 * @Description:
 * 大数据求和计算:ForkJoin or stream 并行流
 */
public class Test {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
//        时间:24722
        test1();
//        时间:19219
        test2();
//        时间:4860
        test3();

    }

    /**
     * 普通方法实现
     */
    public static void test1() {
        long sum = 0L;
        long start = System.currentTimeMillis();
        for (long i = 1L; i <= 1000_0000_0000L; i++) {
            sum += i;
        }
        long end = System.currentTimeMillis();
        System.out.println("sum=" + sum + " 时间:" + (end - start));
    }

    /**
     * 使用 ForkJoin 方法
     */
    public static void test2() throws ExecutionException, InterruptedException {
        long start = System.currentTimeMillis();
        ForkJoinPool forkJoinPool = new ForkJoinPool();
        ForkJoinDemo forkJoinDemo = new ForkJoinDemo(1L, 1000_0000_0000L);
        forkJoinPool.submit(forkJoinDemo);
        Long sum = forkJoinDemo.get();
        long end = System.currentTimeMillis();
        System.out.println("sum=" + sum + " 时间:" + (end - start));
    }

    /**
     * 使用 stream 并行流方法
     */
    public static void test3() {
        long start = System.currentTimeMillis();
        long sum = LongStream.rangeClosed(0L, 1000_0000_0000L).parallel().reduce(0, Long::sum);
        long end = System.currentTimeMillis();
        System.out.println("sum=" + sum + " 时间:" + (end - start));
    }
}

class ForkJoinDemo extends RecursiveTask<Long> {
    private Long start;
    private Long end;
    private Long temp = 10000L;

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

    @Override
    protected Long compute() {
        if (end - start < temp) {
            long sum = 0;
            for (long i = start; i <= end; i++) {
                sum += i;
            }
            return sum;
        } else {
            long mid = (start + end) /2;
            ForkJoinDemo task1 = new ForkJoinDemo(start, mid);
            task1.fork(); // 拆分任务,把任务压入线程队列
            ForkJoinDemo task2 = new ForkJoinDemo(mid + 1, end);
            task2.fork();
            long sum = task1.join() + task2.join();
            return sum;
        }
    }
}

20、JMM

什么是JMM?

JMM 是虚拟的内存模型

JMM 同步约定:

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

内存间交互操作

关于主内存与工作内存之间具体的交互协议,即一个变量如何从主内存拷贝到工作内存、如何从工作内存同步回主内存之类的实现细节,Java内存模型中定义了以下8种操作来完成,虚拟机实现时必须保证下面提及的每一种操作都是原子的、不可再分的(对于double和long类型的变量来说,load、store、read和write操作在某些平台上允许有例外)。

  • lock(锁定):作用于主内存的变量,它把一个变量标识为一条线程独占的状态。
  • unlock(解锁):作用于主内存的变量,它把一个处于锁定状态的变量释放出来,释放后的变量才可以被其他线程锁定。
  • read(读取):作用于主内存的变量,它把一个变量的值从主内存传输到线程的工作内存中,以便随后的load动作使用。
  • load(载入):作用于工作内存的变量,它把read操作从主内存中得到的变量值放入工作内存的变量副本中。
  • use(使用):作用于工作内存的变量,它把工作内存中一个变量的值传递给执行引擎,每当虚拟机遇到一个需要使用到变量的值的字节码指令时将会执行这个操作。
  • assign(赋值):作用于工作内存的变量,它把一个从执行引擎接收到的值赋给工作内存的变量,每当虚拟机遇到一个给变量赋值的字节码指令时执行这个操作。
  • store(存储):作用于工作内存的变量,它把工作内存中一个变量的值传送到主内存中,以便随后的write操作使用。
  • write(写入):作用于主内存的变量,它把store操作从工作内存中得到的变量的值放入主内存的变量中。

如果要把一个变量从主内存复制到工作内存,那就要顺序地执行read和load操作,如果要把变量从工作内存同步回主内存,就要顺序地执行store和write操作。注意,Java内存模型只要求上述两个操作必须按顺序执行,而没有保证是连续执行。也就是说,read与load之间、store与write之间是可插入其他指令的,如对主内存中的变量a、b进行访问时,一种可能出现顺序是read a、read b、load b、load a。除此之外,Java内存模型还规定了在执行上述8种基本操作时必须满足如下规则:

  • 不允许read和load、store和write操作之一单独出现,即不允许一个变量从主内存读取了但工作内存不接受,或者从工作内存发起回写了但主内存不接受的情况出现。
  • 不允许一个线程丢弃它的最近的assign操作,即变量在工作内存中改变了之后必须把该变化同步回主内存。
  • 不允许一个线程无原因地(没有发生过任何assign操作)把数据从线程的工作内存同步回主内存中。
  • 一个新的变量只能在主内存中“诞生”,不允许在工作内存中直接使用一个未被初始化(load或assign)的变量,换句话说,就是对一个变量实施use、store操作之前,必须先执行过了assign和load操作。
  • 一个变量在同一个时刻只允许一条线程对其进行lock操作,但lock操作可以被同一条线程重复执行多次,多次执行lock后,只有执行相同次数的unlock操作,变量才会被解锁。
  • 如果对一个变量执行lock操作,那将会清空工作内存中此变量的值,在执行引擎使用这个变量前,需要重新执行load或assign操作初始化变量的值。
  • 如果一个变量事先没有被lock操作锁定,那就不允许对它执行unlock操作,也不允许去unlock一个被其他线程锁定住的变量。
  • 对一个变量执行unlock操作之前,必须先把此变量同步回主内存中(执行store、write操作)。

这8种内存访问操作以及上述规则限定,再加上稍后介绍的对volatile的一些特殊规定,就已经完全确定了Java程序中哪些内存访问操作在并发下是安全的。由于这种定义相当严谨但又十分烦琐,实践起来很麻烦,所以在下文将介绍这种定义的一个等效判断原则——先行发生原则,用来确定一个访问在并发环境下是否安全。

21、volatile 关键字

  1. 每个Java线程都有自己的工作内存,工作内存中的数据并不会实时刷新回主内存,因此在并发情况下,有可能线程A已经修改了成员变量k的值,但是线程B并不能读取到线程A修改后的值,这是因为线程A的工作内存还没有被刷新回主内存,导致线程B无法读取到最新的值。
  2. 在工作内存中,每次使用volatile修饰的变量前都必须先从主内存刷新最新的值,这保证了当前线程能看见其他线程对volatile修饰的变量所做的修改后的值。
  3. 在工作内存中,每次修改volatile修饰的变量后都必须立刻同步回主内存中,这保证了其他线程可以看到自己对volatile修饰的变量所做的修改。
  4. volatile修饰的变量不会被指令重排序优化,保证代码的执行顺序与程序的顺序相同。
  5. volatile保证可见性,不保证原子性,部分保证有序性(仅保证被volatile修饰的变量)。
  6. 指令重排序的目的是为了提高性能,指令重排序仅保证在单线程下不会改变最终的执行结果,但无法保证在多线程下的执行结果。
  7. 为了实现volatile的内存语义,编译器在生成字节码时,会在指令序列中插入内存屏障来禁止重排序。

22、单例模式

/**
 * @Author: YanChuYu
 * @Date: 2022/07/06
 * @Description: 饿汉式单例
 */
public class Hungry {
    /**
     * 构造器私有
     */
    private Hungry() {

    }

    public static final Hungry HUNGRY = new Hungry();

    public static Hungry getInstance() {
        return HUNGRY;
    }
}
/**
 * @Author: YanChuYu
 * @Date: 2022/07/06
 * @Description: 双重检验的单列模式
 */
public class Lazy {
    private Lazy() {

    }

    /**
     * 为保证多线程情况下单例对象是一个成品对象,添加 volatile 关键字,保证原子性
     */
    private static volatile Lazy lazy;

    public static Lazy getInstance() {
        if (lazy == null) {
            synchronized (Lazy.class) {
                if (lazy == null) {
                    // 不是一个原子性操作
                    lazy = new Lazy();
                }
            }
        }
        return lazy;
    }

    public static void main(String[] args) throws Exception {
        Lazy instance = Lazy.getInstance();

        // 暴力反射
        Constructor<Lazy> constructor = Lazy.class.getDeclaredConstructor();
        constructor.setAccessible(true);
        Lazy newInstance = constructor.newInstance();
        System.out.println(instance);
        System.out.println(newInstance);
    }
}
/**
 * @Author: YanChuYu
 * @Date: 2022/07/06
 * @Description:
 */
public enum EnumSingleton {
    /**
     * 单例对象
     */
    INSTANCE;
    public static EnumSingleton getInstance() {
        return INSTANCE;
    }

    public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
        EnumSingleton instance = EnumSingleton.getInstance();
        // Cannot reflectively create enum objects 暴力反射不能破环枚举单例
        Constructor<EnumSingleton> constructor = EnumSingleton.class.getDeclaredConstructor(String.class, int.class);
        constructor.setAccessible(true);
        EnumSingleton newInstance = constructor.newInstance();
    }
}

23、CAS (比较并交换)

缺点:

  1. 自旋锁设计循环耗时
  2. 一次性只能保证一个共享变量的原子性
  3. ABA问题
    /**
     * Atomically sets the value to the given updated value
     * if the current value {@code ==} the expected value.
     *
     * @param expect the expected value
     * @param update the new value
     * @return {@code true} if successful. False return indicates that
     * the actual value was not equal to the expected value.
     */
    public final boolean compareAndSet(int expect, int update) {
        return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
    }

23.1 Unsafe 类

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

    static {
        try {
            valueOffset = unsafe.objectFieldOffset
                (AtomicInteger.class.getDeclaredField("value"));
        } catch (Exception ex) { throw new Error(ex); }
    }

    private volatile int value;
    /**
    compareAndSwapInt(var1, var2, var5, var5 + var4) 参数解析:
    var1:要修改的对象起始地址 如:0x00000111
    var2:需要修改的具体内存地址 如100 。0x0000011+100 = 0x0000111就是要修改的值的地址
    注意没有var3
    var4:期望内存中的值,拿这个值和0x0000111内存中的中值比较,如果为true,则修改,返回ture,否则返回false,等待下次修改。
    var5:如果上一步比较为ture,则把var5更新到0x0000111其实的内存中。
    原子操作,直接操作内存
    */
    public final int getAndAddInt(Object var1, long var2, int var4) {
        int var5;
        // 自旋锁设计
        do {
            // getIntVolatile方法用于在对象指定偏移地址处volatile读取一个int
            var5 = this.getIntVolatile(var1, var2);
            // compareAndSwapInt:C++ 的 CAS 操作
        } while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));

        return var5;
    }

23.2、ABA问题(狸猫换太子)

解决方案:使用原子引用

【强制】所有整型包装类对象之间值的比较,全部使用 equals 方法比较。 说明:对于 Integer var = ? 在 -128 至 127 之间的赋值,Integer 对象是在 IntegerCache.cache 产生,会复用已有对 象,这个区间内的 Integer 值可以直接使用 == 进行判断,但是这个区间之外的所有数据,都会在堆上产生,并不会复 用已有对象,这是一个大坑,推荐使用 equals 方法进行判断

当原子引用的泛型为 Integer 时,compareAndSet() 方法判等默认采用 == ,因为导致定义的初始化数据不在 -128 至127 之间时,会导致 compareAndSet() 失败

public class Test {
    public static void main(String[] args) {
        AtomicStampedReference<Integer> atomicStampedReference = new AtomicStampedReference<Integer>(1,0);
        new Thread(()->{
            System.out.println("a1 =>" + atomicStampedReference.getStamp());

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

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

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

            try {
                TimeUnit.SECONDS.sleep(2);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            atomicStampedReference.compareAndSet(1,6
                    ,atomicStampedReference.getStamp(),atomicStampedReference.getStamp() + 1);
            System.out.println("b2 =>" + atomicStampedReference.getStamp());

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

24、各种锁的理解

24.1、公平锁、非公平锁

公平锁:不能插队

非公平锁:可以插队(默认都是非公平锁)

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

24.2、可重入锁(递归锁)

解释一:可重入就是说某个线程已经获得某个锁,可以再次获取锁而不会出现死锁

解释二:可重入锁又称递归锁,是指同一个线程在外层方法获取锁的时候,再进入该线程的内层方法会自动获取锁(前提是锁对象得是同一个对象),不会因为之前已经获取过锁还没有释放而阻塞。

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

        new Thread(()->{
            phone1.sendMsg();
        },"B").start();
    }
}

class Phone1 {
    public synchronized void sendMsg(){
        System.out.println(Thread.currentThread().getName() + " => sendMsg");
        call();
    }

    public synchronized void call(){
        System.out.println(Thread.currentThread().getName() + " => call");
    }
}
public class Test2 {
    public static void main(String[] args) {
        Phone2 phone2 = new Phone2();
        new Thread(()->{
            phone2.sendMsg();
        },"A").start();

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

class Phone2{
    Lock lock = new ReentrantLock();

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

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

24.3、自旋锁

public class SpinLock {
    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 static void main(String[] args) {
        SpinLock lock = new SpinLock();

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

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

        new Thread(()->{
            lock.myLock();
            try {
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                lock.myUnlock();
            }
        },"B").start();

    }
}

25、死锁