• JUC系列(一) 多线程基础复习


    什么是JUC

    问:如何学习JUC?

    答: 源码 + Java帮助文档 面试高频,

    juc 其实就是 Java.util 包下的线程分类的工具

    image-20220301194131214

    以往学习的线程只是:

    			我们使用的普通的线程代码 Thread,==Runnable== 等其实回顾JavaSE的线程知识我们可以发现, 
    

    其实我们学习线程基础的时候,也是有用到concurrent包下的东西

    比如 RunnableCallable ,Callable 就是我们concurrent 包下的

    还有就是 Lock

    image-20220301194541263

    线程与进程

    线程、进程、如何来解释

    进程 : 一个程序 如 QQ.exe Music.exe 程序的集合

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

    Java 默认是开启两个线程 main GC

    线程: 开了一个进程 比如: typora 写字,自动保存(线程负责的)

    对于Java 而言创建线程我们学习到的方法有三种 : Thread , Runnable , Callable

    PS :Java本身是无法开启线程的!!!

        public synchronized void start() {
            /**
             * This method is not invoked for the main method thread or "system"
             * group threads created/set up by the VM. Any new functionality added
             * to this method in the future may have to also be added to the VM.
             *
             * A zero status value corresponds to state "NEW".
             */
            if (threadStatus != 0)
                throw new IllegalThreadStateException();
    
            /* Notify the group that this thread is about to be started
             * so that it can be added to the group's list of threads
             * and the group's unstarted count can be decremented. */
            group.add(this);
    
            boolean started = false;
            try {
                start0();
                started = true;
            } finally {
                try {
                    if (!started) {
                        group.threadStartFailed(this);
                    }
                } catch (Throwable ignore) {
                    /* do nothing. If start0 threw a Throwable then
                      it will be passed up the call stack */
                }
            }
        }
    
    	// 本地方法 调用底层的 C++ Java本身不能直接操作硬件
        private native void start0();
    

    并发,并行

    我们要学习并发编程,首先要了解 : 并发和并行是什么

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

    • CPU一核,模拟出来多条线程,天下武功,唯快不破,快速交替。

    并行(多个程序一起走)

    • CPU 多核,多个线程同时执行;如果需要提高性能 : 线程池

    查看一下自己的cpu核数

    image-20220301201453940

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

    所有的公司都很看重,就是效率,比如裁员:一个厉害的可以大于两个不再那么厉害的甚至更多

    比如: 做事情的速度 高手:1s,一般人: 10s 肯定是 1s 的更加的之前也更加的有效率;

    做到人员(减少),但是技术力却提高了

    线程有几个状态

    通过Thread 源码里的枚举类:State 中的属性可以看出来

    线程有六个状态

        public e
    num State {
            //新的线程
            NEW,
            //运行中
            RUNNABLE,
            // 阻塞
            BLOCKED,
            //等待,一直等
            WAITING,
            //等待一段时间就不等了
            TIMED_WAITING,
            // 终止线程
            TERMINATED;
        }
    

    wait/sleep的区别

    1、他们来自不同的类

    wait => object

    sleep => Thread

    2、关于锁的释放

    wait会释放锁,sleep不会释放锁

    抽象理解 : sleep 睡着了没办法释放, wait 是等待,有人需要的释放

    wait必须在同步代码块中使用,得有需要等的人

    sleep可以在任何地方使用

    3、是否需要捕获异常

    wait 不需要捕获异常

    sleep 需要捕获异常

    Lock锁

    传统 Synchronized

    传统火车票案例

    /**
     * @projectName: JUC
     * @package: Thread
     * @className: SaleTicketDemo
     * @author: 冷环渊 doomwatcher
     * @description: TODO
     * @date: 2022/3/1 20:28
     * @version: 1.0
     */
    public class SaleTicketDemo {
        /**
         * 真正的多线程开发 公司中的需要降低耦合性
         * 线程就是一个单独的资源类,没有任何附属的操作
         * 1、 属性。方法
         * */
        public static void main(String[] args) {
            //并发: 多线程操作同一个资源类,把资源丢入线程
            Ticket ticket = new Ticket();
            new Thread(() -> {
                for (int i = 0; i < 40; i++) {
                    ticket.sale();
                }
            }, "A").start();
            new Thread(() -> {
                for (int i = 0; i < 40; i++) {
                    ticket.sale();
                }
            }, "B").start();
            new Thread(() -> {
                for (int i = 0; i < 40; i++) {
                    ticket.sale();
                }
            }, "C").start();
        }
    }
    
    //资源类 OOP
    class Ticket {
        //属性 方法
        private int number = 50;
    
        //    买票的方式
        public synchronized void sale() {
            if (number > 0) {
                System.out.println(Thread.currentThread().getName() + "卖出了" + (number--) + "票,剩余" + number);
            }
        }
    }
    
    

    Lock接口

    image-20220301204107904

    常用的一些实现类

    image-20220301204151242

    image-20220301204430613

    公平锁 : 十分公平,可以先来后到 比如 前一个线程要执行30s 后面的 需要3s 后者必须等待前者执行完

    非公平锁 : 十分不公平,可以根据条件来插队

    如何用Lock来编写火车票Demo

    1. new ReentrantLock();
    2. Lock.lock(); 解锁
    3. finally => lock.unlock(); 解锁

    Synchronized和Lock区别

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

    锁是什么,如何判断锁的是谁

    生产者和消费者问题

    生产者和消费者问题:Synchronized 版

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

    package ProduceAndconsum;
    
    /**
     * @projectName: JUC
     * @package: ProduceAndconsum
     * @className: A
     * @author: 冷环渊 doomwatcher
     * @description: TODO
     * @date: 2022/3/1 21:08
     * @version: 1.0
     */
    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();
        }
    }
    
    //数字 资源类
    // 1、 判断等待,通知
    class Data {
        private int number = 0;
    
        //    +1
        public synchronized void increment() throws InterruptedException {
            if (number != 0) {
                //    等待
                this.wait();
            }
            number++;
            System.out.println(Thread.currentThread().getName() + "=>" + number);
            //    通知其他线程 +1 完毕了
            this.notifyAll();
        }
    
        //    -1
        public synchronized void decrement() throws InterruptedException {
            if (number == 0) {
                //    等待
                this.wait();
            }
            number--;
            System.out.println(Thread.currentThread().getName() + "=>" + number);
            //    通知其他线程 -1 完毕了
            this.notifyAll();
        }
    }
    

    但是,写出来这个简单的模型 面试官还是会挑的出毛病来,问题出在那?

    问题,现在我们是两个线程 ,我们加到四个线程 A,B,C,D 现在还是安全的吗?答案是肯定的不是

    虚假唤醒问题:我们增加到四个线程输出的时候就会发现一些问题来,输出不再是0101了

    image-20220301212425229

    image-20220301212149214

    这里如何解决呢? 将 if 换成 while循环

    修改之后根据官方文档的解释之后,将if改编成while

    image-20220301212605922

    输出就又回到了正常

    package ProduceAndconsum;
    
    /**
     * @projectName: JUC
     * @package: ProduceAndconsum
     * @className: A
     * @author: 冷环渊 doomwatcher
     * @description: TODO
     * @date: 2022/3/1 21:08
     * @version: 1.0
     */
    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();
        }
    }
    
    //数字 资源类
    // 1、 判断等待,通知
    class Data {
        private int number = 0;
    
        //    +1
        public synchronized void increment() throws InterruptedException {
            while (number != 0) {
                //    等待
                this.wait();
            }
            number++;
            System.out.println(Thread.currentThread().getName() + "=>" + number);
            //    通知其他线程 +1 完毕了
            this.notifyAll();
        }
    
        //    -1
        public synchronized void decrement() throws InterruptedException {
            while (number == 0) {
                //    等待
                this.wait();
            }
            number--;
            System.out.println(Thread.currentThread().getName() + "=>" + number);
            //    通知其他线程 -1 完毕了
            this.notifyAll();
        }
    }
    

    生产者和消费者问题: JUC版

    在新的学习中 synchronized 有 Lock 替换

    那么 wait 和 notify谁来替换呢?

    image-20220301212829262

    通过 Lock来找到 Condition

    image-20220301213020385

    image-20220301213059259

    package ProduceAndconsum;
    
    import java.util.concurrent.locks.Condition;
    import java.util.concurrent.locks.Lock;
    import java.util.concurrent.locks.ReentrantLock;
    
    /**
     * @projectName: JUC
     * @package: ProduceAndconsum
     * @className: B
     * @author: 冷环渊 doomwatcher
     * @description: TODO
     * @date: 2022/3/1 21:31
     * @version: 1.0
     */
    
    
    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();
        }
    }
    
    //数字 资源类
    // 1、 判断等待,通知
    class Data2 {
        private int number = 0;
        Lock lock = new ReentrantLock();
        Condition condition = lock.newCondition();
        //等待
        //    condition.await();
        //唤醒全部
        //    condition.signalAll();
    
        //    +1
        public void increment() throws InterruptedException {
            lock.lock();
            try {
                while (number != 0) {
                    //    等待
                    condition.await();
                }
                number++;
                System.out.println(Thread.currentThread().getName() + "=>" + number);
                //    通知其他线程 +1 完毕了
                condition.signalAll();
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                lock.unlock();
            }
        }
    
        //    -1
        public void decrement() throws InterruptedException {
            lock.lock();
            try {
                while (number == 0) {
                    //    等待
                    condition.await();
                }
                number--;
                System.out.println(Thread.currentThread().getName() + "=>" + number);
                //    通知其他线程 -1 完毕了
                condition.signalAll();
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                lock.unlock();
            }
        }
    }
    
    package ProduceAndconsum;
    
    import java.util.concurrent.locks.Condition;
    import java.util.concurrent.locks.Lock;
    import java.util.concurrent.locks.ReentrantLock;
    
    /**
     * @projectName: JUC
     * @package: ProduceAndconsum
     * @className: B
     * @author: 冷环渊 doomwatcher
     * @description: TODO
     * @date: 2022/3/1 21:31
     * @version: 1.0
     */
    
    
    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();
        }
    }
    
    //数字 资源类
    // 1、 判断等待,通知
    class Data2 {
        private int number = 0;
        Lock lock = new ReentrantLock();
        Condition condition = lock.newCondition();
        //等待
        //    condition.await();
        //唤醒全部
        //    condition.signalAll();
    
        //    +1
        public void increment() throws InterruptedException {
            lock.lock();
            try {
                while (number != 0) {
                    //    等待
                    condition.await();
                }
                number++;
                System.out.println(Thread.currentThread().getName() + "=>" + number);
                //    通知其他线程 +1 完毕了
                condition.signalAll();
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                lock.unlock();
            }
        }
    
        //    -1
        public void decrement() throws InterruptedException {
            lock.lock();
            try {
                while (number == 0) {
                    //    等待
                    condition.await();
                }
                number--;
                System.out.println(Thread.currentThread().getName() + "=>" + number);
                //    通知其他线程 -1 完毕了
                condition.signalAll();
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                lock.unlock();
            }
        }
    }
    

    任何的一个新技术,都不可能是单纯的去替代老的技术,一定有优化和补充

    Condition

    image-20220301214146773

    我们更换完代码之后,可以正常输出但是还是混乱的,我们想要达到 A->B->C->D 这样输出,这个时候就引出了新的知识点

    Conddition精准唤醒

    package ProduceAndconsum;
    
    import java.util.concurrent.locks.Condition;
    import java.util.concurrent.locks.Lock;
    import java.util.concurrent.locks.ReentrantLock;
    
    /**
     * @projectName: JUC
     * @package: ProduceAndconsum
     * @className: B
     * @author: 冷环渊 doomwatcher
     * @description: TODO
     * @date: 2022/3/1 21:31
     * @version: 1.0
     */
    
    
    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();
        }
    }
    
    //数字 资源类
    // 1、 判断等待,通知
    class Data3 {
        private int number = 1;
        Lock lock = new ReentrantLock();
        Condition condition = lock.newCondition();
        Condition condition1 = lock.newCondition();
        Condition condition2 = lock.newCondition();
    
        public void printA() {
            lock.lock();
            try {
                //    业务,判断 执行 通知
                while (number != 1) {
                    //等待
                    condition.await();
                }
                System.out.println(Thread.currentThread().getName() + "=>AAA");
                number = 2;
                condition1.signal();
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                lock.unlock();
            }
        }
    
        public void printB() {
            lock.lock();
            try {
                //    业务,判断 执行 通知
                while (number != 2) {
                    //等待
                    condition1.await();
                }
                System.out.println(Thread.currentThread().getName() + "=>BBB");
                number = 3;
                condition2.signal();
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                lock.unlock();
            }
        }
    
        public void printC() {
            lock.lock();
            try {
                //    业务,判断 执行 通知
                while (number != 3) {
                    //等待
                    condition2.await();
                }
                System.out.println(Thread.currentThread().getName() + "=>BBB");
                number = 1;
                condition.signal();
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                lock.unlock();
            }
        }
    }
    

    输出的结果就达到我们的预期了

    image-20220301215538935

  • 相关阅读:
    Android studio安卓生成APK文件安装包方法
    SpringBoot学习笔记-创建个人中心页面(下)
    C++笔记2(内存分区模型,引用)
    Docker学习笔记
    安卓基础知识:Intent解析
    WPS调用“画笔”菜单的操作步骤
    Android主流插件化
    大模型高效微调详解-从Adpter、PrefixTuning到LoRA
    大数据-之LibrA数据库系统告警处理(ALM-25005 Nscd服务异常)
    微软在2022年Gartner云计算AI开发者服务魔力象限中被评为“领导者”
  • 原文地址:https://blog.csdn.net/doomwatcher/article/details/127116663