zoukankan      html  css  js  c++  java
  • JUC并发编程丶

    工程准备

    新建项目

    • 新建maven项目juc
    • 设置好maven配置
    • Project Structure面板Project和Modules配置Java版本
    • Settings面板中搜javac配置好编译器版本

    在线JDK

    英文版

    image-20201108092235092

    中文版(机器翻译)

    image-20201108092510018

    Java线程

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

    Java本身不能开启一个线程(不能操作硬件),是通过调用native本地C++方法对硬件进行操作

    并发和并行

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

    • CPU一核,同时服务多个线程,快速交替
    • 要提高性能,核心是把握:充分利用CPU资源

    并行(CPU多核(多个逻辑处理器)的前提下)

    • 多核,多个线程可以同时执行
    • 要提高性能可以使用线程池
    package com.gfpz.demo01;
    
    public class Test1 {
        public static void main(String[] args) {
    //        new Thread().start();
            //获取cpu核数
            //CPU密集型,IO密集型
            System.out.println(Runtime.getRuntime().availableProcessors());
        }
    }
    

    线程的状态

    Jdk源码中Thread.State是有6个状态(可能Java不要操作的状态没有列出来)

    WAITING 和 TIMED_WAITING:前者会一直等,后者是限制了时间的等待

    wait()和sleep()

    • wait()属于Object类;sleep()属于Thread类
    • wait()会释放锁;sleep()睡觉了,抱着锁睡的,不会释放锁
    • wait()必须在同步代码块中使用();sleep()可以在任意地方睡觉
    • wait()、sleep()都需要捕获InterruptedException异常

    同步方式:synchronized

    package com.gfpz.demo01;
    
    //基本的卖票例子
    
    /**
     * 公司中的开发,降低耦合性
     * 线程就是一个单独的资源类,它没有任何附属操作
     */
    public class SaleTicketDemo01 {
        public static void main(String[] args) {
            Ticket ticket = new Ticket();
            new Thread(() -> {for (int i = 0; i < 60; i++) ticket.sale();}, "A").start();
            new Thread(() -> {for (int i = 0; i < 60; i++) ticket.sale();}, "B").start();
            new Thread(() -> {for (int i = 0; i < 60; i++) ticket.sale();}, "C").start();
        }
    }
    
    //资源类 OOP
    class Ticket {
    
        private int number = 50;
    
        //synchronized 本质是 队列/锁
        public synchronized void sale() {
            if (number > 0) {
                System.out.println(Thread.currentThread().getName() + "卖出了第:" + (50 - number--) + "张票,剩余:" + number + "张票");
            }
        }
    }
    

    同步方式:Lock(JUC)

    锁代码块,手动开启和关闭(按照JDK固定模板)

    image-20201108185832007

    package com.gfpz.demo01;
    
    import java.util.concurrent.locks.Lock;
    import java.util.concurrent.locks.ReentrantLock;
    
    public class SaleTicketDemo02 {
        public static void main(String[] args) {
            Ticket2 ticket2 = new Ticket2();
            new Thread(() -> {for (int i = 0; i < 100; i++) ticket2.sale();}, "A").start();
            new Thread(() -> {for (int i = 0; i < 100; i++) ticket2.sale();}, "B").start();
            new Thread(() -> {for (int i = 0; i < 100; i++) ticket2.sale();}, "C").start();
        }
    }
    
    class Ticket2 {
    
        private int number = 60;
    
        Lock l = new ReentrantLock();
    
        public void sale() {
            l.lock();
            try { // 业务代码
                if (number > 0) {
                    System.out.println(Thread.currentThread().getName() + "卖出了第:" + (60 - number--) + "张票,剩余:" + number + "张票");
                }
            } finally {
                l.unlock();
            }
        }
    }
    

    synchronized 与 Lock 的区别:

    1. synchronized 是Java内置的关键字,Lock是一个Java类
    2. synchronized 无法判断获取锁的状态,Lock可以判断是否获取到了锁
    3. synchronized 会自动释放锁,Lock必须要手动释放,不释放会死锁
    4. synchronized 线程会阻塞着一直等待锁,Lock可以通过 tryLock() 非阻塞方式获取锁
    5. synchronized 可重入锁,不可以中断的,非公平;Lock 可重入锁,可以判断锁,非公平(可设置)
    6. synchronized 适合锁少量的代码同步问题,Lock适合大量的同步代码,Lock更加灵活
    7. synchronized 使用形式是 锁方法或对象(代码块形式),Lock是按固定形式new锁对象/释放锁对象
    8. Lock 更细粒度,更精准的通知唤醒

    生产者消费者:synchronized

    package com.gfpz.pc;
    
    /**
     * 线程之间操作同一个资源,通过等待和唤醒进行通信
     */
    public class A {
        public static void main(String[] args) {
            Data data = new Data();
            //生产
            new Thread(()->{
                for (int i = 0; i < 10; i++) {
                    try {
                        data.increment();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            },"A").start();
            //消费
            new Thread(()->{
                for (int i = 0; i < 10; i++) {
                    try {
                        data.decrement();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            },"B").start();
        }
    }
    
    //生产者消费者:等待 业务 通知  面试手写(单例模式、排序算法、生产者消费者、死锁)
    class Data {
        private int number;
    
        public synchronized void increment() throws InterruptedException {
            if (number != 0) {//等于0才进行+1,不等于0等着
                //等待
                this.wait();
            }
            number++;
            System.out.println(Thread.currentThread().getName() + "=>" + number);
            //通知其他线程,我+1完了
            this.notifyAll();
        }
    
        public synchronized void decrement() throws InterruptedException {
            if (number == 0) {//不等于0才进行-1,等于0等着
                //等待
                this.wait();
            }
            number--;
            System.out.println(Thread.currentThread().getName() + "=>" + number);
            //通知其他线程,我-1完了
            this.notifyAll();
        }
    }
    

    上面只开启A,B线程时没有问题,但是多开一组生产者消费者C,D的时候,就会有问题,原因是wait存在虚假唤醒的问题,需要写在while循环中。

    image-20201109092521193

    将if改成while即可

    package com.gfpz.pc;
    
    /**
     * 线程之间操作同一个资源,通过等待和唤醒进行通信
     */
    public class A {
        public static void main(String[] args) {
            Data data = new Data();
            //生产
            new Thread(()->{
                for (int i = 0; i < 10; i++) {
                    try {
                        data.increment();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            },"A").start();
            //消费
            new Thread(()->{
                for (int i = 0; i < 10; i++) {
                    try {
                        data.decrement();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            },"B").start();
            //生产
            new Thread(()->{
                for (int i = 0; i < 10; i++) {
                    try {
                        data.increment();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            },"C").start();
            //消费
            new Thread(()->{
                for (int i = 0; i < 10; i++) {
                    try {
                        data.decrement();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            },"D").start();
        }
    }
    
    //生产者消费者:等待 业务 通知  面试手写(单例模式、排序算法、生产者消费者、死锁)
    class Data {
        private int number;
    
        public synchronized void increment() throws InterruptedException {
            while (number != 0) {//等于0才进行+1,不等于0等着
                //等待
                this.wait();
            }
            number++;
            System.out.println(Thread.currentThread().getName() + "=>" + number);
            //通知其他线程,我+1完了
            this.notifyAll();
        }
    
        public synchronized void decrement() throws InterruptedException {
            while (number == 0) {//不等于0才进行-1,等于0等着
                //等待
                this.wait();
            }
            number--;
            System.out.println(Thread.currentThread().getName() + "=>" + number);
            //通知其他线程,我-1完了
            this.notifyAll();
        }
    }
    

    生产者消费者:Lock(JUC)

    image-20201109093749314

    synchronized、wait、notifyAll都进行相应简单替换

    package com.gfpz.pc;
    
    import java.util.concurrent.locks.Condition;
    import java.util.concurrent.locks.Lock;
    import java.util.concurrent.locks.ReentrantLock;
    
    /**
     * 线程之间操作同一个资源,通过等待和唤醒机制进行通信
     */
    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) {
                        e.printStackTrace();
                    }
                }
            },"A").start();
            //消费
            new Thread(()->{
                for (int i = 0; i < 10; i++) {
                    try {
                        data.decrement();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            },"B").start();
            //生产
            new Thread(()->{
                for (int i = 0; i < 10; i++) {
                    try {
                        data.increment();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            },"C").start();
            //消费
            new Thread(()->{
                for (int i = 0; i < 10; i++) {
                    try {
                        data.decrement();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            },"D").start();
        }
    }
    
    //生产者消费者:等待 业务 通知  面试手写(单例模式、排序算法、生产者消费者、死锁)
    class Data2 {
        private int number;
        final Lock lock = new ReentrantLock();
        final Condition condition = lock.newCondition();
    
        public void increment() throws InterruptedException {
            lock.lock();
            try {
                while (number != 0) {//等于0才进行+1,不等于0等着
                    //等待
                    condition.await();
                }
                number++;
                System.out.println(Thread.currentThread().getName() + "=>" + number);
                //通知其他线程,我+1完了
                condition.signalAll();
            } finally {lock.unlock();}
        }
    
        public void decrement() throws InterruptedException {
            lock.lock();
            try {
                while (number == 0) {//不等于0才进行-1,等于0等着
                    //等待
                    condition.await();
                }
                number--;
                System.out.println(Thread.currentThread().getName() + "=>" + number);
                //通知其他线程,我-1完了
                condition.signalAll();
            } finally {lock.unlock();}
        }
    }
    

    但是,这样做还不够,没有充分利用Lock的功能;精准的通知/唤醒一个线程才是它的最佳实践

    package com.gfpz.pc;
    
    import java.util.concurrent.locks.Condition;
    import java.util.concurrent.locks.Lock;
    import java.util.concurrent.locks.ReentrantLock;
    
    /**
     * A执行完调用B,B执行完调用C,C执行完调用A
     */
    public class C {
        public static void main(String[] args) {
            Data3 data = new Data3();
            new Thread(()->{
                for (int i = 0; i < 10; i++) {
                    data.printA();
                }
            },"A").start();
            new Thread(()->{
                for (int i = 0; i < 10; i++) {
                    data.printB();
                }
            },"B").start();
            new Thread(()->{
                for (int i = 0; i < 10; i++) {
                    data.printC();
                }
            },"C").start();
        }
    }
    
    class Data3 {
        private int number = 1;//1A 2B 3C
        private Lock lock = new ReentrantLock();
        private Condition condition1 = lock.newCondition();
        private Condition condition2 = lock.newCondition();
        private Condition condition3 = lock.newCondition();
    
        public void printA() {
            lock.lock();try{
                while (number != 1) {
                    condition1.await();
                }
                System.out.println(Thread.currentThread().getName() + "=>AAA");
                number = 2;
                //唤醒B
                condition2.signal();
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally{lock.unlock();}
        }
        public void printB() {
            lock.lock();try{
                while (number != 2) {
                    condition2.await();
                }
                System.out.println(Thread.currentThread().getName() + "=>BBB");
                number = 3;
                //唤醒C
                condition3.signal();
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally{lock.unlock();}
        }
        public void printC() {
            lock.lock();try{
                while (number != 3) {
                    condition3.await();
                }
                System.out.println(Thread.currentThread().getName() + "=>CCC");
                number = 1;
                //唤醒A
                condition1.signal();
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally{lock.unlock();}
        }
    }
    

    锁应用的问题

    同步方法,锁调用方法的对象,挟对象以执行方法,执行完才能释放

    package com.gfpz.lock8;
    
    import java.util.concurrent.TimeUnit;
    
    /**
     * 先打印啥?发短信还是打电话
     */
    public class Test1 {
        public static void main(String[] args) {
            Phone phone = new Phone();
            new Thread(()->{phone.sendSms();},"A").start();
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            new Thread(()->{phone.call();},"B").start();
        }
    }
    
    class Phone{
        //synchronized 锁的是方法的调用者,就是上面new的phone对象,先调用的哪个就执行哪个
        public synchronized void sendSms() {
            try {
                TimeUnit.SECONDS.sleep(4);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("发短信");
        }
    
        public synchronized void call() {
            System.out.println("打电话");
        }
    }
    

    非同步方法不需要等待对象释放锁

    package com.gfpz.lock8;
    
    import java.util.concurrent.TimeUnit;
    
    /**
     * 先打印啥?发短信还是hello
     */
    public class Test2 {
        public static void main(String[] args) {
            Phone2 phone = new Phone2();
            new Thread(()->{phone.sendSms();},"A").start();
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            new Thread(()->{phone.hello();},"B").start();//不受锁的影响
        }
    }
    
    class Phone2{
        //synchronized 锁的是方法的调用者,就是上面new的phone对象,先调用的哪个就执行哪个
        public synchronized void sendSms() {
            try {
                TimeUnit.SECONDS.sleep(4);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("发短信");
        }
    
        public synchronized void call() {
            System.out.println("打电话");
        }
    
        public void hello() {
            System.out.println("hello");
        }
    }
    

    不同的对象不考虑等待锁释放

    package com.gfpz.lock8;
    
    import java.util.concurrent.TimeUnit;
    
    /**
     * 先打印啥?显然是打电话
     */
    public class Test3 {
        public static void main(String[] args) {
            Phone3 phone1 = new Phone3();
            Phone3 phone2 = new Phone3();
            new Thread(()->{phone1.sendSms();},"A").start();
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            new Thread(()->{phone2.call();},"B").start();
        }
    }
    
    class Phone3{
        public synchronized void sendSms() {
            try {
                TimeUnit.SECONDS.sleep(4);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("发短信");
        }
    
        public synchronized void call() {
            System.out.println("打电话");
        }
    
        public void hello() {
            System.out.println("hello");
        }
    }
    

    加了 static 的 synchronized 锁的是类的 Class 对象

    package com.gfpz.lock8;
    
    import java.util.concurrent.TimeUnit;
    
    /**
     * 这个需要记住!关注 synchronized 是不是加了static,所以出现 synchronized 就要关注它锁的是什么
     */
    public class Test4 {
        public static void main(String[] args) {
            Phone4 phone = new Phone4();
            Phone4 phone2 = new Phone4();
            new Thread(()->{phone.sendSms();},"A").start();
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            new Thread(()->{phone2.call();},"B").start();
        }
    }
    
    class Phone4 {
        //锁的是 Phone4的Class对象
        public static synchronized void sendSms() {
            try {
                TimeUnit.SECONDS.sleep(4);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("发短信");
        }
    
        public static synchronized void call() {
            System.out.println("打电话");
        }
    
    }
    

    类的对象 和 类的Class对象

    package com.gfpz.lock8;
    
    import java.util.concurrent.TimeUnit;
    
    /**
     * 打电话 (关注第二个方法调用处是否需要等待第一个方法释放锁)
     */
    public class Test5 {
        public static void main(String[] args) {
            Phone5 phone = new Phone5();
            Phone5 phone2 = new Phone5();
            new Thread(()->{phone.sendSms();},"A").start();
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            new Thread(()->{phone.call();},"B").start();
        }
    }
    
    class Phone5 {
        //静态
        public static synchronized void sendSms() {
            try {
                TimeUnit.SECONDS.sleep(4);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("发短信");
        }
    
        //普通
        public synchronized void call() {
            System.out.println("打电话");
        }
    
    }
    

    集合安全问题

    并发修改异常java.util.ConcurrentModificationException

    List

    package com.gfpz.unsafe;
    
    import java.util.*;
    import java.util.concurrent.CopyOnWriteArrayList;
    
    public class ListTest {
        public static void main(String[] args) {
    //        List<String> list = Arrays.asList("1", "2", "3");
    //        list.forEach(System.out::println);
    
            /**
             * 并发下 ArrayList 是不安全的
             * 方案1:安全list:List<String> list = new Vector<>();
             * 方案2:用工具类让它变得安全:List<String> list = Collections.synchronizedList(new ArrayList<>());
             * 方案3:JUC写入时复制:List<String> list = new CopyOnWriteArrayList<>();
             */
            List<String> list = new CopyOnWriteArrayList<>();
            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();
            }
        }
    }
    

    image-20201109124947110

    Set

    package com.gfpz.unsafe;
    
    import java.util.Collections;
    import java.util.HashSet;
    import java.util.Set;
    import java.util.UUID;
    import java.util.concurrent.CopyOnWriteArraySet;
    
    /**
     * 同理
     * 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 = 1; i <= 30; i++) {
                new Thread(() -> {
                    set.add(UUID.randomUUID().toString().substring(0, 5));
                    System.out.println(set);
                }, String.valueOf(i)).start();
            }
    
        }
    }
    

    HashSet的底层就是HashMap

    public HashSet() {
     map = new HashMap<>();
    }
    
    public boolean add(E e) {
     return map.put(e, PRESENT)==null;
    }
    

    Map

    package com.gfpz.unsafe;
    
    import java.util.*;
    import java.util.concurrent.ConcurrentHashMap;
    
    //ConcurrentModificationException
    public class MapTest {
        public static void main(String[] args) {
            //Map 是这样用的吗?不是,工作中不用 HashMap
            //Map 默认等价于什么 new HashMap<>(16,0.75);//加载因子、初始容量
    //        Map<String, String> map = new HashMap<>();
    //        Map<String, String> map = Collections.synchronizedMap(new HashMap<>());
            Map<String, String> map = new ConcurrentHashMap<>();
            for (int i = 1; i <= 30; i++) {
                new Thread(() -> {
                    map.put(Thread.currentThread().getName(), UUID.randomUUID().toString().substring(0, 5));
                    System.out.println(map);
                }, String.valueOf(i)).start();
            }
    
        }
    }
    

    ConcurrentHashMap/HashMap 的原理官方文档学习

    Callable实现多线程

    余生多线程就用者玩意儿吧丶

    • java.util.concurrent包下的接口
    • 可以有返回值
    • 可以抛出异常
    • 方法是call()
    package com.gfpz.callable;
    
    import java.util.concurrent.Callable;
    import java.util.concurrent.ExecutionException;
    import java.util.concurrent.FutureTask;
    
    public class CallableTest {
        public static void main(String[] args) throws ExecutionException, InterruptedException {
            //FutureTask 实现了runnable接口,又可以通过callable进行构造
            FutureTask futureTask = new FutureTask(new MyThread());
            new Thread(futureTask,"A").start();
            new Thread(futureTask,"B").start();//只会打印1个call,结果会被缓存
            Integer ret = (Integer) futureTask.get();//这个方法可能产生阻塞,要放到最后,或者异步通信
            System.out.println(ret);
        }
    }
    
    //泛型对应call方法的返回值
    class MyThread implements Callable<Integer> {
    
        @Override
        public Integer call() throws Exception {
            System.out.println("call执行了");
            return 1024;
        }
    }
    

    常用的辅助类

    image-20201109141618199

    1、CountDownLatch 减法计数器

    最佳实践:所有人出门了才关教室门

    package com.gfpz.add;
    
    import java.util.concurrent.CountDownLatch;
    
    //计数器
    public class CountDownLatchDemo {
        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();//数量-1
                },String.valueOf(i)).start();
            }
            countDownLatch.await();//阻塞,等待计数器归零,然后再向下执行
            System.out.println("Close door");
        }
    }
    

    2、CycleBarrier 加法计数器

    最佳实践:集齐7颗龙珠召唤神龙

    package com.gfpz.add;
    
    import java.util.concurrent.BrokenBarrierException;
    import java.util.concurrent.CyclicBarrier;
    
    public class CyclicBarrierDemo {
        public static void main(String[] args) {
            /**
             * 集齐7颗龙珠召唤神龙
             */
            CyclicBarrier cyclicBarrier = new CyclicBarrier(7, () -> {
                System.out.println("召唤神龙!");
            });
            for (int i = 1; i <= 7; i++) {
                final int temp = i;//记住这个操作,在lambda中得到i的值
                new Thread(()->{
                    System.out.println(Thread.currentThread().getName()+"收集第:"+temp+"颗龙珠");
                    try {
                        cyclicBarrier.await();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    } catch (BrokenBarrierException e) {
                        e.printStackTrace();
                    }
                }).start();
            }
        }
    }
    

    3、Semaphore 信号量

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

    package com.gfpz.add;
    
    import java.util.concurrent.Semaphore;
    import java.util.concurrent.TimeUnit;
    
    public class SemaphoreDemo {
        public static void main(String[] args) {
            //线程数量:停车位 
            Semaphore semaphore = new Semaphore(3);
            for (int i = 1; i <= 6; i++) {
                new Thread(()->{
                    try {
                        semaphore.acquire();//得到车位
                        System.out.println(Thread.currentThread().getName() + "抢到车位");
                        TimeUnit.SECONDS.sleep(2);
                        System.out.println(Thread.currentThread().getName() + "离开车位");
                    } catch (InterruptedException e) {
                        e.printStackTrace();//释放
                    }
                    semaphore.release();
                },String.valueOf(i)).start();
            }
        }
    }
    

    读写锁ReadWriteLock

    image-20201109152052487

    • 独占锁(写锁):一次只能被一个线程占有
    • 共享锁(读锁):多个线程可以同时占有
    package com.gfpz.rw;
    
    import java.util.HashMap;
    import java.util.Map;
    import java.util.concurrent.locks.ReadWriteLock;
    import java.util.concurrent.locks.ReentrantLock;
    import java.util.concurrent.locks.ReentrantReadWriteLock;
    
    public class ReadWriteLockDemo {
        public static void main(String[] args) {
            MyCacheLock myCache = new MyCacheLock();
            //写入
            for (int i = 1; i <= 5; i++) {
                final int temp = i;
                new Thread(() -> {
                    myCache.put(temp + "", temp + "");
                }, String.valueOf(i)).start();
            }
            //读取
            for (int i = 1; i <= 5; i++) {
                final int temp = i;
                new Thread(() -> {
                    myCache.get(temp + "");
                }, String.valueOf(i)).start();
            }
        }
    }
    
    //加锁的
    class MyCacheLock {
        private volatile Map<String, Object> map = new HashMap<>();
        //读写锁:更加细粒度的控制
        private ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
    
        //写入的时候,只希望同时只有一个线程写
        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) {
                e.printStackTrace();
            } finally {
                readWriteLock.writeLock().unlock();
            }
        }
    
        //读 所有人都可以
        public void get(String key) {
            readWriteLock.readLock().lock();
            try {
                System.out.println(Thread.currentThread().getName() + "读取" + key);
                Object o = map.get(key);
                System.out.println(Thread.currentThread().getName() + "读取OK");
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                readWriteLock.readLock().unlock();
            }
        }
    }
    
    /**
     * 自定义缓存,未加锁的
     */
    class MyCache {
        private volatile Map<String, Object> map = new HashMap<>();
    
        public void put(String key, Object value) {
            System.out.println(Thread.currentThread().getName() + "写入" + key);
            map.put(key, value);
            System.out.println(Thread.currentThread().getName() + "写入OK");
        }
    
        public void get(String key) {
            System.out.println(Thread.currentThread().getName() + "读取" + key);
            Object o = map.get(key);
            System.out.println(Thread.currentThread().getName() + "读取OK");
        }
    }
    

    阻塞队列BlockingQueue

    BlockingQueue 同List、Set及Queue都是同级的,都继承了Collection接口,都有实现类诸如ArrayBlockingQueue、LinkedBlockingQueue

    image-20201110203318117

    最佳实践:多线程并发处理、线程池,添加移除

    主要了解四组API

    动作 会抛出异常 不会抛出异常,有返回值 阻塞等待 超时等待
    添加 add offer put offer("d", 2, TimeUnit.SECONDS)
    移除 remove poll take poll(2, TimeUnit.SECONDS)
    查看队首元素 element peek - -
    package com.gfpz.bq;
    
    import java.util.concurrent.ArrayBlockingQueue;
    import java.util.concurrent.TimeUnit;
    
    public class Test {
        public static void main(String[] args) throws InterruptedException {
    //        test1();
    //        test2();
    //        test3();
            test4();
        }
    
        /**
         * 抛出异常
         */
        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"));//抛出异常 IllegalStateException: Queue full
            System.out.println("队首元素:"+blockingQueue.element());//
            System.out.println("-------------------------");
            System.out.println(blockingQueue.remove());//FIFO 先进先出
            System.out.println("队首元素:"+blockingQueue.element());//
            System.out.println(blockingQueue.remove());
            System.out.println(blockingQueue.remove());
    //        System.out.println(blockingQueue.remove());//NoSuchElementException
        }
    
        /**
         * 不抛出异常,有返回值
         */
        public static void test2() {
            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"));//返回 false 不抛出异常
            System.out.println("队首元素:"+blockingQueue.peek());
            System.out.println("-------------------------");
            System.out.println(blockingQueue.poll());
            System.out.println("队首元素:"+blockingQueue.peek());
            System.out.println(blockingQueue.poll());
            System.out.println(blockingQueue.poll());
            System.out.println(blockingQueue.poll());//返回一个null,没有异常
        }
    
        /**
         * 等待 阻塞(一直阻塞)
         */
        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(blockingQueue.take());
            System.out.println(blockingQueue.take());
            System.out.println(blockingQueue.take());
    //        System.out.println(blockingQueue.take());//取不到,一直阻塞
        }
    
        /**
         * 等待 阻塞(等待超时)
         */
        public static void test4() throws InterruptedException {
            ArrayBlockingQueue blockingQueue = new ArrayBlockingQueue<>(3);//参数为队列的大小
            blockingQueue.offer("a");
            blockingQueue.offer("b");
            blockingQueue.offer("c");
            //blockingQueue.offer("d", 2, TimeUnit.SECONDS);//超时时间 单位
            System.out.println("-------------------------");
            System.out.println(blockingQueue.poll());
            System.out.println(blockingQueue.poll());
            System.out.println(blockingQueue.poll());
            System.out.println(blockingQueue.poll(2, TimeUnit.SECONDS));//2秒拿不到就不拿了 返回个null
        }
    }
    

    同步队列SynchronousQueue

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

    是上文阻塞队列BlockingQueue的实现类,同时也继承了非阻塞队列AbstractQueue

    package com.gfpz.bq;
    
    import java.util.concurrent.BlockingQueue;
    import java.util.concurrent.SynchronousQueue;
    import java.util.concurrent.TimeUnit;
    
    /**
     * 同步队列:让几个线程像几兄弟一样,保持一路
     */
    public class SynchronousQueueDemo {
        public static void main(String[] args) {
            BlockingQueue<String> blockingQueue = new SynchronousQueue<>();//同步队列
            new Thread(() -> {
                try {
                    System.out.println(Thread.currentThread().getName() + " put 1 ");
                    blockingQueue.put("1");
                    System.out.println(Thread.currentThread().getName() + " put 2 ");
                    blockingQueue.put("2");
                    System.out.println(Thread.currentThread().getName() + " put 3 ");
                    blockingQueue.put("3");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }, "T1").start();
            new Thread(() -> {
                try {
                    TimeUnit.SECONDS.sleep(3);
                    System.out.println(Thread.currentThread().getName()+"-->"+blockingQueue.take());
                    TimeUnit.SECONDS.sleep(3);
                    System.out.println(Thread.currentThread().getName()+"-->"+blockingQueue.take());
                    TimeUnit.SECONDS.sleep(3);
                    System.out.println(Thread.currentThread().getName()+"-->"+blockingQueue.take());
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }, "T2").start();
        }
    }
    

    线程池

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

    程序运行就意味着占用着系统资源,就需要考虑优化使用

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

    阿里Java开发手册要求:

    image-20201110220626539

    不允许使用 Executors 的三大方法,先了解它

    package com.gfpz.pool;
    
    import java.util.concurrent.ExecutorService;
    import java.util.concurrent.Executors;
    
    public class Demo01 {
        public static void main(String[] args) {
    //        ExecutorService threadPool = Executors.newSingleThreadExecutor();//单个线程;不会拒绝请求,可能堆积大量请求,导致OOM
    //        ExecutorService threadPool = Executors.newFixedThreadPool(5);//固定数目的线程数;不会拒绝,可能堆积大量请求,导致OOM
            ExecutorService threadPool = Executors.newCachedThreadPool();//可伸缩的,遇强则强;允许创建大量的线程,可能导致OOM
            try {
                for (int i = 0; i < 100; i++) {
                    //使用线程池创建线程
                    threadPool.execute(() -> {
                        System.out.println(Thread.currentThread().getName() + "  0k");
                    });
                }
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                //线程池用完,程序结束,关闭线程池
                threadPool.shutdown();
            }
        }
    }
    

    七大参数

    三大方法本质都是用的 ThreadPoolExecutor 创建的

    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 有7个构造参数(阿里要求同学自己创建线程池,用这个构造方法)

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

    四种拒绝策略

    image-20201111133730836

    package com.gfpz.pool;
    
    import java.util.concurrent.*;
    
    public class Demo01 {
        public static void main(String[] args) {
            //自定义线程池
            int heShu = Runtime.getRuntime().availableProcessors();//优化
            ExecutorService threadPool = new ThreadPoolExecutor(2//银行柜台平时开两个窗口
                    , heShu//,5最多开5个窗口
                    , 3//等3
                    , TimeUnit.SECONDS//秒钟
                    , new LinkedBlockingQueue<>(3)//3把等待办理业务的椅子
                    , Executors.defaultThreadFactory()//线程工厂
                    //4种拒绝策略(银行满了,还有人进来)
                    //,new ThreadPoolExecutor.AbortPolicy()//抛出异常:java.util.concurrent.RejectedExecutionException
                    //,new ThreadPoolExecutor.CallerRunsPolicy()//哪来的去哪里,main线程调用的,main线程自行处理业务
                    //,new ThreadPoolExecutor.DiscardPolicy()//不抛出异常,丢掉任务
                    ,new ThreadPoolExecutor.DiscardOldestPolicy()//队列满了,尝试去和最早的竞争,不抛出异常
            );
    
            try {
                for (int i = 1; i <= 9; i++) {
                    //使用线程池创建线程
                    threadPool.execute(() -> {
                        System.out.println(Thread.currentThread().getName() + "  0k");
                    });
                }
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                //线程池用完,程序结束,关闭线程池
                threadPool.shutdown();
            }
        }
    }
    

    所以:告诉面试官,用 ThreadPoolExecutor 创建线程池就完事了!

    上文中设置最大线程数的地方有两种设置套路(涉及到调优):

    • CPU密集型:设置为CPU核数
    • IO密集型:判断程序中十分耗资源的IO线程(任务)数m,将最大线程数设置为>2m

    四大函数式接口

    函数式接口,就是四个必会的新东西之一,四个新东西:lambda表达式、链式编程、函数式接口、Stream流式计算

    函数式接口:只有一个方法的接口;简化编程模型,在新版本的框架底层大量应用

    诸如:

    @FunctionalInterface
    public interface Runnable {
        public abstract void run();
    }
    
    List list = new ArrayList<>();
    list.forEach(消费者类型的函数式接口);
    

    四个接口(所谓接口,就是约定了一类事情的行业规则,比如三星抢占6G规则,就是定义一堆接口)

    image-20201111155351869

    函数型接口Function

    一个输入参数,一个输出类型(函数就意味着输入输出嘛)

    image-20201111160717885

    测试

    package com.gfpz.function;
    
    import java.util.function.Function;
    
    /**
     * Function 函数型接口,有一个输入参数,有一个输出
     * 只要是 函数型接口,就可以用 lambda 表达式简化
     */
    public class Demo01 {
        public static void main(String[] args) {
            //工具类:传入啥,输出啥
            /*Function function = new Function<String, String>() {//创建匿名内部类
                @Override
                public String apply(String str) {
                    return str;
                }
            };*/
    //        Function<String, String> function = (str)->{return str;};
            Function<String, String> function = str->{return str;};
            System.out.println(function.apply("test"));
        }
    }
    

    断定型接口Predicate

    一个输入参数,一个输出类型(固定为布尔类型)(把输入给老先生,测凶吉)

    image-20201111162628406

    测试

    package com.gfpz.function;
    
    import java.util.function.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(""));
        }
    }
    

    消费型接口Consumer

    只有输入,没有返回值

    image-20201111164138111

    测试

    package com.gfpz.function;
    
    import java.util.function.Consumer;
    
    /**
     * 消费型接口,只有输入没有输出
     */
    public class Demo03 {
        public static void main(String[] args) {
            /*Consumer<String> consumer = new Consumer<String>() {
                @Override
                public void accept(String s) {
                    System.out.println(s);
                }
            };*/
            Consumer<String> consumer = s -> {
                System.out.println(s);
            };
            consumer.accept("sad");
        }
    }
    

    供给型接口Supplier

    没有输入参数,只有返回值

    image-20201111165221170

    测试

    package com.gfpz.function;
    
    import java.util.function.Supplier;
    
    /**
     * 供给型接口,没有参数只有返回值
     */
    public class Demo04 {
        public static void main(String[] args) {
            /*Supplier<Integer> supplier = new Supplier<Integer>() {
                @Override
                public Integer get() {
                    return 1024;
                }
            };*/
            Supplier<Integer> supplier = () -> {return 1024;};
            System.out.println(supplier.get());
        }
    }
    

    Stream流式计算

    大数据:存储+计算;集合、MySQL本质是存储数据;计算,交给流来搞。

    package com.gfpz.stream;
    
    import lombok.AllArgsConstructor;
    import lombok.Data;
    import lombok.NoArgsConstructor;
    
    import java.util.Arrays;
    import java.util.List;
    
    /**
     * 要求:一分钟内完成,一行代码实现筛选
     * 1、id为偶数
     * 2、年龄大于23
     * 3、用户名转为大写字母
     * 4、用户名倒着排序
     * 5、只输出一个用户名
     */
    public class Test {
        public static void main(String[] args) {
            User u1 = new User(1, "a", 21);
            User u2 = new User(2, "b", 22);
            User u3 = new User(3, "c", 23);
            User u4 = new User(4, "d", 24);
            User u5 = new User(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);
        }
    }
    
    @Data//get set toString
    @NoArgsConstructor
    @AllArgsConstructor
    class User {
        private int id;
        private String name;
        private int age;
    }
    

    ForkJoin分支合并

    分治法 + 工作窃取:大数据量下使用

    image-20201111201040351

    用例:10亿以内的自然数求和

    package com.gfpz.forkjoin;
    
    import java.util.concurrent.ExecutionException;
    import java.util.concurrent.ForkJoinPool;
    import java.util.concurrent.ForkJoinTask;
    import java.util.concurrent.RecursiveTask;
    import java.util.stream.LongStream;
    
    /**
     * 求和计算
     */
    public 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 = 0L;
                for (long i = start; i <= end; i++) {
                    sum += i;
                }
                return sum;
            }else{//像递归
                long middle = (start + end) / 2;//中间值
                ForkJoinDemo task1 = new ForkJoinDemo(start, middle);
                task1.fork();//拆分任务,把任务压入线程队列
                ForkJoinDemo task2 = new ForkJoinDemo(middle+1, end);
                task2.fork();
                return task1.join() + task2.join();//合并
            }
        }
    
        public static void main(String[] args) throws ExecutionException, InterruptedException {
    //        test1();//365
    //        test2();//818
            test3();//706
        }
    
        //月薪3k
        public static void test1() {
            long sum = 0L;
            long startTime = System.currentTimeMillis();
            for (long i = 1L; i <= 10_0000_0000; i++) {
                sum += i;
            }
            long endTime = System.currentTimeMillis();
            System.out.println("sum="+sum+",时间:"+(endTime-startTime));
        }
    
        //月薪6k:ForkJoin
        public static void test2() throws ExecutionException, InterruptedException {
            long startTime = System.currentTimeMillis();
    
            ForkJoinPool forkJoinPool = new ForkJoinPool();
            ForkJoinTask<Long> task = new ForkJoinDemo(0L, 10_0000_0000L);
            ForkJoinTask<Long> submit = forkJoinPool.submit(task);//直观上看就是起多线程来完成一堆任务
            Long sum = submit.get();
    
            long endTime = System.currentTimeMillis();
            System.out.println("sum="+sum+",时间:"+(endTime-startTime));
        }
    
        //月薪9k:Stream并行流
        public static void test3() {
            long startTime = System.currentTimeMillis();
            long sum = LongStream.rangeClosed(0L, 10_0000_0000L).parallel().reduce(0, Long::sum);
            long endTime = System.currentTimeMillis();
            System.out.println("sum="+sum+",时间:"+(endTime-startTime));
        }
    }
    

    异步回调

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

    image-20201111225349257

    package com.gfpz.future;
    
    import java.util.concurrent.CompletableFuture;
    import java.util.concurrent.ExecutionException;
    import java.util.concurrent.TimeUnit;
    
    /**
     * 异步调用
     */
    public class Demo01 {
        public static void main(String[] args) throws ExecutionException, InterruptedException {
    //        test1();
            test2();
        }
    
        //没有返回值的 异步回调
        public static void test1() throws ExecutionException, InterruptedException {
            CompletableFuture<Void> completableFuture = CompletableFuture.runAsync(()->{
                try {
                    TimeUnit.SECONDS.sleep(2);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName()+" runAsync=>Void");
            });
            System.out.println("sad");
            completableFuture.get();//阻塞获取执行结果
        }
    
        //有返回值的异步回调
        public static void test2() throws ExecutionException, InterruptedException {
            CompletableFuture<Integer> integerCompletableFuture = CompletableFuture.supplyAsync(() -> {
                System.out.println(Thread.currentThread().getName()+" supplyAsync=>Integer");
                int a = 1/0;
                return 1024;
            });
            int b = integerCompletableFuture.whenComplete((t, u) -> {
                System.out.println("t=>" + t);//正常的返回结果
                System.out.println("u=>" + u);//错误信息
            }).exceptionally((e) -> {
                System.out.println(e.getMessage());
                return 2333;//类似404
            }).get();
            System.out.println(b);
        }
    }
    

    JMM(Java内存模型)

    请谈谈你对 volatile 的理解

    • Java关键字
    • Java虚拟机提供的轻量级的同步机制
    • 保证可见性
    • 不保证原子性
    • 禁止指令重排

    说到可见性,就要先从JMM说起

    因为在不同的硬件生产商和不同的操作系统下,内存的访问逻辑有一定的差异,结果就是当你的代码在某个系统环境下运行良好,并且线程安全,但是换了个系统就出现各种问题。Java内存模型,就是为了屏蔽系统和硬件的差异,让一套代码在不同平台下能到达相同的访问结果。

    内存划分

    JMM规定了内存主要划分为主内存和工作内存两种。此处的主内存和工作内存跟JVM内存划分(堆、栈、方法区)是在不同的层次上进行的,如果非要对应起来,主内存对应的是Java堆中的对象实例部分,工作内存对应的是栈中的部分区域,从更底层的来说,主内存对应的是硬件的物理内存,工作内存对应的是寄存器和高速缓存。

    image-20201112100745030

    JVM在设计时候考虑到,如果JAVA线程每次读取和写入变量都直接操作主内存,对性能影响比较大,所以每条线程拥有各自的工作内存,工作内存中的变量是主内存中的一份拷贝,线程对变量的读取和写入,直接在工作内存中操作,而不能直接去操作主内存中的变量。但是这样就会出现一个问题,当一个线程修改了自己工作内存中变量,对其他线程是不可见的,会导致线程不安全的问题。因为JMM制定了一套标准来保证开发者在编写多线程程序的时候,能够控制什么时候内存会被同步给其他线程。

    内存交互操作

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

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

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

    • 不允许read和load、store和write操作之一单独出现。即使用了read必须load,使用了store必须write
    • 不允许线程丢弃他最近的assign操作,即工作变量的数据改变了之后,必须告知主存
    • 不允许一个线程将没有assign的数据从工作内存同步回主内存
    • 一个新的变量必须在主内存中诞生,不允许工作内存直接使用一个未被初始化的变量。就是怼变量实施use、store操作之前,必须经过assign和load操作
    • 一个变量同一时间只有一个线程能对其进行lock。多次lock后,必须执行相同次数的unlock才能解锁
    • 如果对一个变量进行lock操作,会清空所有工作内存中此变量的值,在执行引擎使用这个变量前,必须重新load或assign操作初始化变量的值
    • 如果一个变量没有被lock,就不能对其进行unlock操作。也不能unlock一个被其他线程锁住的变量
    • 对一个变量进行unlock操作之前,必须把此变量同步回主内存

    按照上图的逻辑,就可能出现:程序不知道主内存的值已经被修改过了,因此引出了volatile

    volatile

    1、volatile保证可见性

    package com.gfpz.tvolatile;
    
    import java.util.concurrent.TimeUnit;
    
    public class JMMDemo {
        //不加 volatile 的话,线程1的死循环结束不了
        //加了 volatile 可以保证可见性
        private volatile static int num = 0;
        public static void main(String[] args) {//main线程
    
            new Thread(() -> {//线程1对主内存种num已经变化了毫不知情,所以一直不停下来
                while (num == 0) {
    
                }
            }).start();
    
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
    
            num = 1;
    
            System.out.println(num);
        }
    }
    

    2、volatile不保证原子性

    线程1在执行任务的时候,不能被打扰,也不能被分割

    package com.gfpz.tvolatile;
    
    /**
     * 不保证原子性
     */
    public class VDemo02 {
        private volatile static int num = 0;//volatile 不保证原子性
    
        public static void add(){//synchronized 可以保证
            num++;
        }
    
        public static void main(String[] args) {
            for (int i = 1; i <= 20; i++) {
                new Thread(()->{
                    for (int j = 0; j < 1000; j++) {
                        add();
                    }
                }).start();
            }
    
            while (Thread.activeCount() > 2) {//main gc
                Thread.yield();//线程礼让
            }
    
            System.out.println(Thread.currentThread().getName()+" "+num);
        }
    }
    

    反编译查看字节码文件 D:cangkujuc argetclassescomgfpz volatile>javap -c VDemo02.class

    image-20201112105754063

    num++ 并非一个单纯的原子性操作

    除了使用同步锁解决上面的原子性问题外,还可以使用原子类(比较高效,这些类的底层和操作系统挂钩)

    image-20201112110303894

    package com.gfpz.tvolatile;
    
    import java.util.concurrent.atomic.AtomicInteger;
    
    /**
     * AtomicInteger证原子性
     */
    public class VDemo03 {
        private static AtomicInteger num = new AtomicInteger();
    
        public static void add(){
            num.getAndIncrement();//底层用的Unsafe类 CAS
        }
    
        public static void main(String[] args) {
            for (int i = 1; i <= 20; i++) {
                new Thread(()->{
                    for (int j = 0; j < 1000; j++) {
                        add();
                    }
                }).start();
            }
    
            while (Thread.activeCount() > 2) {//main gc
                Thread.yield();//线程礼让
            }
    
            System.out.println(Thread.currentThread().getName()+" "+num);
        }
    }
    

    3、禁止指令重排

    我们所写的程序,计算机并不是按照我们看到的那样执行的,而是进行了重排,类似编译器按照文法优化重排、内存系统重排

    利用内存屏障:

    1. 保证特定操作的执行顺序
    2. 保证某些变量的内存可见性

    volatile在单例模式中有典型应用

    彻底玩转单例模式

    饿汉模式(浪费资源)

    package com.gfpz.single;
    
    //饿汉式单例
    public class Hungry {
        //会浪费空间,所以应该是需要用得时候才创建Hungry对象 即用懒汉模式
        private byte[] data1 = new byte[1024*1024];
        private byte[] data2 = new byte[1024*1024];
        private byte[] data3 = new byte[1024*1024];
        private byte[] data4 = new byte[1024*1024];
    
        private Hungry() {
        }
    
        private final static Hungry HUNGRY = new Hungry();
    
        public static Hungry getInstance() {
            return HUNGRY;
        }
    }
    

    DCL懒汉式(双重检测锁+volatile禁止指令重排)(也可能被反射破坏)

    package com.gfpz.single;
    
    import java.lang.reflect.Constructor;
    import java.lang.reflect.Field;
    
    //懒汉式单例
    public class LazyMan {
        /*public LazyMan() {
            System.out.println(Thread.currentThread().getName()+" ok");
        }*/
    
        private static boolean xiaoming = false;
    
        public LazyMan() {
            synchronized (LazyMan.class) {
                /*
                if (lazyMan != null) {//第三重检测
                    throw new RuntimeException("不要试图使用反射破坏单例");
                }
                */
                if (!xiaoming){
                    xiaoming = true;
                }else{
                    throw new RuntimeException("不要试图使用反射破坏单例");
                }
            }
        }
    
        /*
        private static LazyMan lazyMan;
        //单线程下单例ok  多线程下有问题
        public static LazyMan getInstance() {
            if(null ==lazyMan){
                lazyMan = new LazyMan();
            }
            return lazyMan;
        }*/
    
        private volatile static LazyMan lazyMan;//volatile
        //双重检测锁模式+volatile禁止指令重排 DCL懒汉!!!!!!!!!!!!!!!!
        public static LazyMan getInstance() {
            if(null ==lazyMan){
                synchronized (LazyMan.class) {
                    if(null ==lazyMan){
                        lazyMan = new LazyMan();//不是原子性操作,极端情况下也是有问题得
                        /**
                         * 1分配内存空间
                         * 2执行构造方法,初始化对象
                         * 3把对象指向这个空间
                         * 可能因指令重排产生问题
                         */
                    }
                }
            }
            return lazyMan;
        }
    
    
        public static void main(String[] args) throws Exception {
            /*
            //测试
            for (int i = 0; i < 10; i++) {
                new Thread(()->{
                    LazyMan.getInstance();
                }).start();
            }*/
    
            //反射破坏单例模式
            //LazyMan instance = LazyMan.getInstance();
    
            Field xiaoming = LazyMan.class.getDeclaredField("xiaoming");
            xiaoming.setAccessible(true);
    
            Constructor<LazyMan> declaredConstructor = LazyMan.class.getDeclaredConstructor(null);
            declaredConstructor.setAccessible(true);
    
            LazyMan instance = declaredConstructor.newInstance();
            xiaoming.set(instance,false);
            LazyMan instance2 = declaredConstructor.newInstance();
    
            System.out.println(instance.hashCode());
            System.out.println(instance2.hashCode());
        }
    }
    

    静态内部类玩法

    package com.gfpz.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 com.gfpz.single;
    
    import java.lang.reflect.Constructor;
    
    //enum 本身也是一个class类
    public enum EnumSingle {
    
        INSTANCE;
    
        public EnumSingle getInstance() {
            return INSTANCE;
        }
    }
    
    class Test{
        //Cannot reflectively create enum objects 结论:枚举可以防止反射破坏单例
        public static void main(String[] args) throws Exception {
    //        EnumSingle instance1 = EnumSingle.INSTANCE;
    //        EnumSingle instance2 = EnumSingle.INSTANCE;
    //        System.out.println(instance1);
    //        System.out.println(instance2);
    
            EnumSingle instance1 = EnumSingle.INSTANCE;
            //Constructor<EnumSingle> declaredConstructor = EnumSingle.class.getDeclaredConstructor(null);
            Constructor<EnumSingle> declaredConstructor = EnumSingle.class.getDeclaredConstructor(String.class, int.class);
            declaredConstructor.setAccessible(true);
            EnumSingle instance2 = declaredConstructor.newInstance();
            //NoSuchMethodException
            System.out.println(instance1);
            System.out.println(instance2);
        }
    }
    

    Java自带反编译工具查看所有类和成员

    image-20201112123712442

    用 jad.exe 将字节码文件反编译为Java文件,查看发现真实参构造器 EnumSingle(String s, int i)

    image-20201112124258937

    理解CAS比较并交换

    比较 工作内存中 和 主内存中 的值,如果这是值是期望的,那么执行更新操作;如果不是,就一直循环(自旋锁)

    package com.gfpz.cas;
    
    import java.util.concurrent.atomic.AtomicInteger;
    
    public class CASDemo {
        public static void main(String[] args) {
            AtomicInteger atomicInteger = new AtomicInteger(2020);//给它一个初始值
            //CAS:compareAndSet 比较并交换
            //public final boolean compareAndSet(int expect, int update)
            //如果刚好是是期望值,就进行修改/更新
            //CAS 是cpu的并发原语 此处是Java层面的套用
            System.out.println(atomicInteger.compareAndSet(2020, 2021));
            System.out.println(atomicInteger);
    
            atomicInteger.incrementAndGet();
            atomicInteger.getAndIncrement();
    
            System.out.println(atomicInteger.compareAndSet(2020, 2021));
            System.out.println(atomicInteger);
        }
    }
    

    利用Unsafe类操作内存

    image-20201112132243890

    自旋锁

    image-20201112134231146

    缺点:

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

    ABA问题

    操作的值是被人动手脚的值,意义变了

    package com.gfpz.cas;
    
    import java.util.concurrent.atomic.AtomicInteger;
    
    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);
    
            System.out.println(atomicInteger.compareAndSet(2021, 2020));
            System.out.println(atomicInteger);
    
            //期望的线程
            System.out.println(atomicInteger.compareAndSet(2020, 6666));
            System.out.println(atomicInteger);
        }
    }
    

    原子引用解决ABA问题

    image-20201112135845249

    package com.gfpz.cas;
    
    import java.util.concurrent.TimeUnit;
    import java.util.concurrent.atomic.AtomicInteger;
    import java.util.concurrent.atomic.AtomicReference;
    import java.util.concurrent.atomic.AtomicStampedReference;
    
    public class CASDemo {
    //跟乐观锁的原理相同
        public static void main(String[] args) {
    //        AtomicInteger atomicInteger = new AtomicInteger(2020);//给它一个初始值
            //如果泛型是包装类,注意对象的引用问题  正常不会比较Integer
            AtomicStampedReference<Integer> atomicStampedReference = new AtomicStampedReference<>(1,1);
    
            new Thread(()->{
                int stamp = atomicStampedReference.getStamp();//获得版本号
                System.out.println("a1=>"+stamp);
                try {
                    TimeUnit.SECONDS.sleep(2);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("a "+atomicStampedReference.compareAndSet(1, 2
                        , atomicStampedReference.getStamp(), atomicStampedReference.getStamp() + 1));
                System.out.println("a2=>"+atomicStampedReference.getStamp());
                System.out.println("a "+atomicStampedReference.compareAndSet(2, 1
                        , atomicStampedReference.getStamp(), atomicStampedReference.getStamp() + 1));
                System.out.println("a3=>"+atomicStampedReference.getStamp());
    
            },"a").start();
    
            new Thread(()->{
                int stamp = atomicStampedReference.getStamp();
                System.out.println("b1=>"+stamp);
                try {
                    TimeUnit.SECONDS.sleep(2);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("b "+atomicStampedReference.compareAndSet(2, 6, stamp, stamp + 1));
                System.out.println("b2=>"+atomicStampedReference.getStamp());
            },"b").start();
        }
    }
    

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

    image-20201112142019765

    各种锁的概念

    公平锁

    非常公平,不能够插队,必须先来后到

    非公平锁

    不公平,可以插队,3s不用等3h,默认都是非公平锁

    //创建锁的时候设置公平锁和非公平锁
    public ReentrantLock() {
        sync = new NonfairSync();
    }
    public ReentrantLock(boolean fair) {
        sync = fair ? new FairSync() : new NonfairSync();
    }
    

    可重入锁(递归锁)

    拿到了外面的锁之后,就可以拿到里边的锁,自动获得

    synchronized 版

    package com.gfpz.lock;
    //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");
        }
    }
    

    Lock 版

    package com.gfpz.lock;
    
    import java.util.concurrent.locks.Lock;
    import java.util.concurrent.locks.ReentrantLock;
    
    //synchronized
    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();
            lock.lock();
            try {
                System.out.println(Thread.currentThread().getName() + " sms");
                call();
            } catch (Exception e) {
                e.printStackTrace();
            }finally {
                lock.unlock();
                lock.unlock();
            }
        }
    
        public void call() {
            lock.lock();
            try {
                System.out.println(Thread.currentThread().getName() + " call");
            } catch (Exception e) {
                e.printStackTrace();
            }finally {
                lock.unlock();
            }
        }
    }
    

    自旋锁

    自定义锁

    package com.gfpz.lock;
    
    import java.util.concurrent.TimeUnit;
    import java.util.concurrent.atomic.AtomicReference;
    
    public class SpinlockDemo {
        AtomicReference<Thread> atomicReference = new AtomicReference<>();
    
        //加锁
        public void myLock() {
            Thread thread = Thread.currentThread();
            System.out.println(Thread.currentThread().getName() + "==>mylock");
    
            //自旋锁
            while (!atomicReference.compareAndSet(null, thread)) {
    
            }
        }
    
        //解锁
        public void myUnLock() {
            Thread thread = Thread.currentThread();
            System.out.println(Thread.currentThread().getName() + "==>myUnLock");
            atomicReference.compareAndSet(thread, null);
        }
    }
    
    class TestSpinlock {
        public static void main(String[] args) throws Exception {
            SpinlockDemo lock = new SpinlockDemo();
    
            new Thread(() -> {
                lock.myLock();
                try {
                    TimeUnit.SECONDS.sleep(5);
                } catch (Exception e) {
                    e.printStackTrace();
                } finally {
                    lock.myUnLock();
                }
    
            }, "T1").start();
    
            TimeUnit.SECONDS.sleep(1);
    
            new Thread(() -> {
                lock.myLock();
                try {
                    TimeUnit.SECONDS.sleep(1);
                } catch (Exception e) {
                    e.printStackTrace();
                } finally {
                    lock.myUnLock();
                }
            }, "T2").start();
        }
    }
    

    死锁

    img

    package com.gfpz.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(2);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
    
                synchronized (lockB) {
                    System.out.println(Thread.currentThread().getName() + " lock:" + lockB + "==>get" + lockA);
                }
            }
        }
    }
    

    解决死锁

    1、线上异常

    2、日志

    3、查看堆栈信息

    使用 jps -l 定位进程号

    image-20201112153620653

    使用 jstack 进程号 查看

    image-20201112153919786

    击石乃有火,不击元无烟!!
  • 相关阅读:
    uva 408 Uniform Generator
    Java实现 蓝桥杯VIP 算法提高 栅格打印问题
    Java实现 蓝桥杯VIP 算法提高 栅格打印问题
    Java实现 蓝桥杯VIP 算法提高 栅格打印问题
    Java实现 蓝桥杯VIP 算法提高 打水问题
    Java实现 蓝桥杯VIP 算法提高 打水问题
    Java实现 蓝桥杯VIP 算法提高 打水问题
    Java实现 蓝桥杯VIP 算法提高 打水问题
    Java实现 蓝桥杯VIP 算法提高 不同单词个数统计
    Java实现 蓝桥杯VIP 算法提高 不同单词个数统计
  • 原文地址:https://www.cnblogs.com/rain2020/p/13955259.html
Copyright © 2011-2022 走看看