• 多线程浅谈


    程序:用某种语言编写的一种指令的集合,即一段静态代码

    进程:是程序的一次执行过程。或是正在运行的一个程序,一个动态的过程。

    线程:进程进一步细化的结果,是一个程序内部的一条执行路径

    虚拟机栈和程序计数器是每一个线程一份
    方法区和堆是一个进程一份,多个线程共享一个进程中的方法区和堆

    并行&并发

    并行:多个CPU同时执行多个任务
    并发:一个CPU采用时间片同时执行多个任务

    使用多线程的优点:

    提高应用程序的响应
    提高计算机CPU的利用率
    改善程序结构,将既长又复杂的进程分为多个线程

    使用多线程的场景:

    程序需要执行两个或多个任务
    程序需要实现一些需要等待的任务,如用户输入,文件读写,网络操作,搜索等
    需要后台运行的程序

    创建多线程的方式一

    Thread类中的start方法有两个作用:
    启动当前线程
    调用当前线程的run()方法

    /**
     * “@Auther: yaoyunfeng
     *
     * @Date: 2022/7/18 - 07 - 18 - 23:30
     * @Description: PACKAGE_NAME
     * @version: 1.0
     *
     * 1.创建一个继承自Thread类的子类
     * 2.重写Thread类中的run方法 ——> 将此线程执行的操作声明在run()方法中
     * 3.创建Thread类的子类的对象
     * 4.调用此对象的start()方法
     * start启动一个线程后 , 不可以再启动同一个线程
     *
     */
    
    class MyThread extends Thread{
        @Override
        public void run() {
            for (int i = 0; i < 100; i++) {
                if (i % 2 == 0){
                    System.out.println(i);
                }
            }
        }
    }
    public class ThreadCreated {
    
        public static void main(String[] args) {
            MyThread myThread = new MyThread();
            myThread.start();
    
            for (int i = 0; i < 100; i++) {
                if (i % 2 == 0){
                    System.out.println(i + "--------------------");
                }
            }
    
    
        }
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41

    采用匿名内部类方式创建线程——创建三个线程

    /**
     * “@Auther: yaoyunfeng
     *
     * @Date: 2022/7/18 - 07 - 18 - 23:30
     * @Description: PACKAGE_NAME
     * @version: 1.0
     *
     * 1.创建一个继承自Thread类的子类
     * 2.重写Thread类中的run方法 ——> 将此线程执行的操作声明在run()方法中
     * 3.创建Thread类的子类的对象
     * 4.调用此对象的start()方法
     *
     */
    
    public class ThreadInnerClassCreateed {
    
        public static void main(String[] args) {
            for (int i = 0; i < 100; i++) {
                if (i % 2 == 0){
                    System.out.println(i + "--------------------" + Thread.currentThread().getName());
                }
            }
    
            new Thread(){
                @Override
                public void run() {
                    for (int i = 0; i < 100; i++) {
                        if (i % 2 == 0){
                            System.out.println(i + "偶数" + Thread.currentThread().getName());
                        }
                    }
                }
            }.start();
    
            new Thread(){
                @Override
                public void run() {
                    for (int i = 0; i < 100; i++) {
                        if (i % 2 != 0){
                            System.out.println(i + "奇数" + Thread.currentThread().getName());
                        }
                    }
                }
            }.start();
    
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47

    给线程赋予名称

    
    class HelloThread extends Thread{
        @Override
        public void run() {
            for (int i = 0; i < 100; i++) {
                if (i % 2 == 0){
                    System.out.println(i + Thread.currentThread().getName());
                }
            }
        }
    }
    public class ThreadMethodTest {
        public static void main(String[] args) {
            HelloThread helloThread = new HelloThread();
            // 给我们的线程命名
            helloThread.setName("线程1");
            helloThread.start();
    
            // 给main线程命名
            Thread.currentThread().setName("主线程");
            
            for (int i = 0; i < 100; i++) {
                if (i % 2 == 0){
                    System.out.println(i + Thread.currentThread().getName());
                }
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28

    Thread类中的常用方法:

    1.start():启动当前线程  调用当前线程的run方法
    2.run()方法通常  需要重写Thread类中的此方法 , 将创建的线程执行的操作声明在此方法中
    3.currentThread():静态方法   返回执行当前代码的线程
    4.getName():获取当前线程的名字
    5.setName():设置当前线程的名字
    6.yield():释放当前CPU执行权
    7.join():在线程A中调用线程Bjoin()方法 , 此时 线程A进入阻塞状态 , 直到线程B完全执行完以后 , 线程A才结束阻塞状态。
    8.stop():强制结束当前线程  此方法已过时
    9.sleep(long time):  让当前线程睡眠指定的milltime毫秒。在指定毫秒时间内,当前线程是阻塞状态。
    10.isAlive():判断当前线程是否还存活
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    线程调度

    Thread类中的三个枚举代表的优先级:

    MAX_PRIORITY:10
    MIN_PRIORITY:1
    NORM_PRIORITY:5 默认。

    getPriority():查看线程的优先级
    setPriority(int p):设置线程的优先级

    说明:高优先级的线程要抢占低优先级的线程的CPU执行权 , 但是只是从概率上讲 ,高优先级的线程高概率的情况下被执行,并不以围着只有高优先级的线程执行完后,低优先级就线程才执行。

    创建多线程的方式二:实现Runnable接口

    package created.two;
    
    /**
     * “@Auther: yaoyunfeng
     *
     * @Date: 2022/7/24 - 07 - 24 - 22:13
     * @Description: created.two
     * @version: 1.0
     *
     * 创建多线程的方式二 : 实现Runnable接口
     * 1. 创建一个实现了Runnable接口的类
     * 2. 实现类去实现Runnable中的抽象方法 run()
     * 3. 创建实现类的对象
     * 4. 将此对象作为参数传递到Thread类的构造器中,创建Thread类的对象
     * 5. 通过Thread类的对象调用start()方法
     *
     */
    
    class MThread implements Runnable{
        @Override
        public void run() {
            for (int i = 0; i < 100; i++) {
                if (i % 2 == 0){
                    System.out.println(i + ":" + Thread.currentThread().getName());
                }
            }
        }
    }
    public class ThreadCreatedRunnable {
        public static void main(String[] args) {
            MThread mThread = new MThread();
            Thread thread1 = new Thread(mThread);
            thread1.setName("线程1");
            thread1.start();
    
            // 再启动一个线程遍历100内的偶数
            Thread thread2 = new Thread(mThread);
            thread2.setName("线程2");
            thread2.start();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41

    继承Thread类方式和实现Runnable接口方式的比较

    开发中优先推荐选择实现Runnable接口的方式创建一个线程
    原因:

    1. 实现的方式不会受类的单继承的局限
    2. 实现的方式更适合来处理多个线程有共享数据的情况。因为我们构建的多个线程相当于 可以传入同一个实现了Runnable接口的类的对象,这样天然我们对象中的变量就是多个线程共享的

    联系:
    两种方式都需要重写run()方法,将线程要执行的逻辑声明在run()方法中。

    线程的声明周期

    • 新建:当一个Thread类或其子类的对象被声明并创建时 , 新生的线程对象处于新建状态。
    • 就绪:处于新建状态的线程被start()后,将进入线程队列等待CPU时间片,此时已具备运行的条件,只是没分配到CPU资源。
    • 运行:当就绪的线程被调度并获得CPU资源时,便进入运行状态,run()方法定义了线程的操作和功能。
    • 阻塞:在某种特殊的情况下,被人为挂起或执行输入输出操作时,让CPU临时终止自己的执行,进入阻塞状态。
    • 死亡:线程完成了他的全部工作或线程被提前强制性的终止或出现异常导致结束。
      在这里插入图片描述

    线程同步:

    为什么需要线程同步呢?
    因为多个线程同时操作共享数据的话,会导致数据的不正确性。
    就比如抢票,我们很明显可以知道票是共享的变量。多个线程同时操作这个变量,就会出现重票、错票的情况,这时我们就需要使用锁去同步线程。下面是集中线程同步的方法。

    一、同步代码块的方式:

    synchronized(同步监视器){
    	需要被同步的方法
    }
    
    • 1
    • 2
    • 3
    • 操作共享数据的代码即为需要同步的方法
    • 同步监视器可以是任何对象。就是我们说的锁,但是只能有一把锁,也就是说对象要保证唯一。
    • 共享数据:多个线程共同操作的变量。比如该问题中得ticket就是共享数据。
    • 在实现Runnable接口创建的多线程的方式中,我们可以考虑将this作为同步监视器。
    • 在继承Thread类创建多线程的方式中,慎用this充当同步监视器,可以考虑使用当前类做同步监视器类.class。

    1、 针对实现Runnable接口的方式的多线程。

    package lock;
    
    /**
     * “@Auther: yaoyunfeng
     *
     * 创建三个窗口卖票 , 总票数为100张
     *
     * 问题:卖票过程中,出现了重票、错票  ——> 出现了线程的安全问题
     * 出现原因:
     * ticket票这个变量被多个线程共享,当某个线程操作车票的过程中,尚未操作完成,其他线程参与进来,也操作车票
     *
     * 解决:加锁;当一个线程在操作票的时候,其他线程不能参与进来,直到线程A操作完ticket,其他线程才可以开始操作ticket。
     * 这种情况,及时线程A出现阻塞也不能被改变。
     *
     * java中通过同步机制解决线程安全问题:
     * 方式一:
     *  同步代码块:
     *  synchronized(同步监视器){
     *      需要被同步的代码
     *  }
     *  说明:操作共享数据的代码即为需要同步的代码。    --> 不能包含代码多了也不能包含代码少了。
     *      共享数据:多个线程共同操作的变量。比如该问题中得ticket就是共享数据。
     *      同步监视器:俗称锁。任何一个类的对象都可以充当锁。
     *          要求:多个线程必须共用同一把锁。
     *       补充:在实现Runnable接口创建的多线程的方式中,我们可以考虑将this作为同步监视器。
     *
     *
     *
     * 方式二:
     *  同步方法:
     *      如果操作共享数据的代码完整的声明在一个方法中,我们可以将此方法声明一个同步的。
     *
     *
     * 同步的方式:解决了线程的安全问题。--好处
     * 操作同步代码时只能有一个线程参与,其他线程等待,相当于单线程,效率低。  -- 局限性
     *
     *
     * @Date: 2022/7/24 - 07 - 24 - 21:57
     * @Description: created.two
     * @version: 1.0
     */
    
    class WindowRunnable implements Runnable{
    
        private int ticket = 100;
    
        Object object = new Object();
    
        @Override
        public void run() {
            while (true){
                synchronized(object) { /** this即为调用该方法的对象,在这里也就是WindowRunnable类的对象,在我们的main中只创建过一个windowRunnable对象,所以在这里this也是唯一的*/
                // synchronized(object) {
                    if (ticket > 0) {
                        try {
                            Thread.sleep(100);
                        } catch (InterruptedException e) {
                            throw new RuntimeException(e);
                        }
                        System.out.println(Thread.currentThread().getName() + ": 卖票,票号为:" + ticket);
                        ticket--;
                    } else {
                        break;
                    }
                }
            }
        }
    }
    public class WindowTestRunnable {
    
        public static void main(String[] args) {
            WindowRunnable windowRunnable = new WindowRunnable();
    
            Thread window1 = new Thread(windowRunnable);
            Thread window2 = new Thread(windowRunnable);
            Thread window3 = new Thread(windowRunnable);
            window1.setName("窗口1");
            window2.setName("窗口2");
            window3.setName("窗口3");
    
            window1.start();
            window2.start();
            window3.start();
        }
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86

    2、针对继承Thread类的实现方式

    package lock;
    
    /**
     * “@Auther: yaoyunfeng
     *
     * 创建三个窗口卖票 , 总票数为100张
     * 使用同步代码快解决继承Thread类的线程安全问题
     *
     * 在继承Thread类创建多线程的方式中,慎用this充当同步监视器,可以考虑使用当前类做同步监视器类.class。
     *
     * @Date: 2022/7/24 - 07 - 24 - 21:57
     * @Description: created.two
     * @version: 1.0
     */
    
    class WindowThread extends Thread{
    
        private static int ticket = 100;
    
        static Object object = new Object();
    
        @Override
        public void run() {
            while (true){
                // synchronized (this){  // 错误的,在这里this即WindowThread的对象,在main中我们创建了三个WindowThread对象,在这里就不唯一了
                // synchronized(WindowThread.class){ // 正确的,反射 , 类只会加载一次,意味着只有一个是唯一的。
                synchronized(object){ // 正确的
                    if (ticket > 0){
                        try {
                            Thread.sleep(100);
                        } catch (InterruptedException e) {
                            throw new RuntimeException(e);
                        }
                        System.out.println(Thread.currentThread().getName() + ": 卖票,票号为:" + ticket);
                        ticket --;
                    }else {
                        break;
                    }
                }
            }
        }
    }
    public class WindowTestThread {
    
        public static void main(String[] args) {
            WindowThread window1 = new WindowThread();
            WindowThread window2 = new WindowThread();
            WindowThread window3 = new WindowThread();
    
            window1.setName("窗口1");
            window2.setName("窗口2");
            window3.setName("窗口3");
    
            window1.start();
            window2.start();
            window3.start();
        }
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59

    二、同步方法

    • 就是将需要同步的代码抽离出一个方法,然后给该方法加上synchronized关键字即可。
    private synchronized void show(){}
    
    • 1
      • 同步方法扔涉及同步监视器,只是不需要我们显示的声明
      • 非静态的同步方法,同步监视器是this
      • 静态的同步方法,同步监视器是当前类本身。

    1、实现Runnable接口的方式

    package lock;
    
    /**
     * 使用同步方法解决实现Runnable接口的线程安全问题。
     *
     * 同步方法扔涉及同步监视器,只是不需要我们显示的声明
     * 非静态的同步方法,同步监视器是this
     * 静态的同步方法,同步监视器是当前类本身。
    
     * @Auther: yaoyunfeng
     * @Date: 2022/7/31 - 07 - 31 - 23:26
     * @Description: lock
     * @version: 1.0
     */
    
    class WindowRunnable3 implements Runnable {
        private int ticket = 100;
    
        Object object = new Object();
    
        @Override
        public void run() {
            while (true) {
                show();
            }
        }
    
        private synchronized void show(){ // 同步监视器就是this
            if (ticket > 0) {
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                System.out.println(Thread.currentThread().getName() + ": 卖票,票号为:" + ticket);
                ticket--;
            }
        }
    }
    
    public class WindowTestRunnableFunction {
        public static void main(String[] args) {
            WindowRunnable3 windowRunnable = new WindowRunnable3();
    
            Thread window1 = new Thread(windowRunnable);
            Thread window2 = new Thread(windowRunnable);
            Thread window3 = new Thread(windowRunnable);
            window1.setName("窗口1");
            window2.setName("窗口2");
            window3.setName("窗口3");
    
            window1.start();
            window2.start();
            window3.start();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56

    2、继承Thread类的方式

    package lock;
    
    /**
     * “@Auther: yaoyunfeng
     * 

    * 创建三个窗口卖票 , 总票数为100张 * 使用同步方法解决继承Thread类的线程安全问题 *

    * * * @Date: 2022/7/24 - 07 - 24 - 21:57 * @Description: created.two * @version: 1.0 */ class WindowThread2 extends Thread { private static int ticket = 100; @Override public void run() { while (true) { show(); } } private static synchronized void show(){ // 同步监视器是 当前类 WindowThread2.class // private synchronized void show(){ // 这里同步监视器默认是this 目前有三个。 此种解决方式是错误的。 if (ticket > 0) { try { Thread.sleep(100); } catch (InterruptedException e) { throw new RuntimeException(e); } System.out.println(Thread.currentThread().getName() + ": 卖票,票号为:" + ticket); ticket--; } } } public class WindowTestThreadFunction { public static void main(String[] args) { WindowThread2 window1 = new WindowThread2(); WindowThread2 window2 = new WindowThread2(); WindowThread2 window3 = new WindowThread2(); window1.setName("窗口1"); window2.setName("窗口2"); window3.setName("窗口3"); window1.start(); window2.start(); window3.start(); } }

    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56

    线程死锁

    不同的线程分别占用对方需要的同步资源不放弃,都在等待对方放弃自己需要的同步资源就形成了死锁。

    出现死锁后,不会出现异常,不会出现提示,只是所有的线程都处于阻塞状态。无法继续。

    package lock;
    
    /**
     * 演示线程死锁的问题
     *
     * 1、死锁的理解
     * 不同的线程都在等待对方的同步资源,不放弃自己的资源。
     *
     * 出现死锁不报错不抛异常。所有线程处于阻塞状态。
     * 尽量避免。
     */
    
    public class DeadLock {
        public static void main(String[] args) {
            StringBuffer s1 = new StringBuffer();
            StringBuffer s2 = new StringBuffer();
    
            new Thread() {
                @Override
                public void run() {
                    synchronized (s1) {
                        s1.append("a");
                        s2.append("1");
                        try {
                            Thread.sleep(100);
                        } catch (InterruptedException e) {
                            throw new RuntimeException(e);
                        }
                        synchronized (s2){
                            s1.append("b");
                            s2.append("2");
                            System.out.println(s1);
                            System.out.println(s2);
                        }
                    }
                }
            }.start();
    
            new Thread(new Runnable() {
                @Override
                public void run() {
                    synchronized (s2) {
                        s1.append("c");
                        s2.append("3");
                        try {
                            Thread.sleep(100);
                        } catch (InterruptedException e) {
                            throw new RuntimeException(e);
                        }
                        synchronized (s1){
                            s1.append("d");
                            s2.append("4");
                            System.out.println(s1);
                            System.out.println(s2);
                        }
                    }
                }
            }).start();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60

    如何避免死锁;
    专门的算法,原则
    尽量减少同步资源的定义
    尽量避免嵌套同步

    Lock锁

    lock用法 , 我们在需要同步的代码起始位置加 lock() , 在结束同步的位置加unlock()解锁代码即可。

    lock和synchronized的异同:
    相同:都是解决线程不安全的方式
    不同:lock需要手动调用方法进行锁定与解锁 , synchronized则是自动进行解锁和锁定的。

    package lock;
    
    import java.util.concurrent.locks.ReentrantLock;
    
    /**
     * 解决线程安全问题三 、 Lock锁。  jdk5新增。
     *
     * synchronized  与  lock  的异同:
     * 同:都可解决线程安全问题
     * 不同:
     * synchronized : 在执行完相应的代码逻辑后 , 自动的释放同步监视器
     * Lock:需要我们手动的启动同步,lock()   结束同步也需要手动调用 unlock()
     *
     * 解决线程安全的方式
     * synchronized 同步代码块
     * synchronized 同步方法
     * lock锁
     *
     */
    
    class Window implements Runnable{
        private int ticket = 100;
    
        // 1、实例化Lock
        private ReentrantLock lock = new ReentrantLock(true);
    
        @Override
        public void run() {
            while (true){
                try {
                    // 2、调用锁定  lock方法
                    lock.lock();
                    if (ticket > 0){
                        try {
                            Thread.sleep(100);
                        } catch (InterruptedException e) {
                            throw new RuntimeException(e);
                        }
                        System.out.println(Thread.currentThread().getName() + ":售票,票号为:" + ticket);
                        ticket--;
                    }else {
                        break;
                    }
                }finally {
    
                    // 解锁
                    lock.unlock();
    
                }
            }
        }
    }
    public class LockTest {
        public static void main(String[] args) {
            Window window = new Window();
    
            Thread thread1 = new Thread(window);
            Thread thread2 = new Thread(window);
            Thread thread3 = new Thread(window);
            thread1.setName("窗口1");
            thread2.setName("窗口2");
            thread3.setName("窗口3");
    
            thread1.start();
            thread2.start();
            thread3.start();
    
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69

    线程通信

    假设一种场景。

    使用两个线程打印1到100 , 线程1 , 线程2 , 交替打印。

    这时候我们就需要用到线程的通信。
    我们介绍一下wait() notify() notifyAll()。这三个方法都是Object类中的方法。

    首先我们想让线程1和线程2交替打印 ,那么我们就需要其中一个线程打印后阻塞,让下一个线程打印,下一个打印完,上一个要释放。
    我们看一下下面的例子。

    package communication;
    
    /**
     * 线程通信的例子:使用两个线程打印1——100.线程1 , 线程2 , 交替打印。
     *
     * 涉及到的三个方法:
     * wait(): 一旦执行此方法,当前线程进入阻塞状态,并释放同步监视器
     * notify():一旦执行此方法,就会唤醒被wait的一个线程,如果有多个线程被wait() , 就唤醒优先级高的那个
     * notifyAll():一旦执行此方法,就会唤醒所有被wait()的线程
     *
     * 说明:
     * 1、wait notify notifyAll 这三个方法必须使用在synchronized的同步代码块,或同步方法中。
     * 2、notify和wait以及notifyAll三个方法的调用者必须是同步代码块或同步方法中的同步监视器
     *      否则会出现 IllegalMonitorStateException 异常
     *
     * 3、这三个方法是定义在Object方法下的。
     *
     *
     */
    
    
    class Number implements Runnable {
        private int number = 1;
    
        @Override
        public void run() {
            while (true) {
                synchronized (this) {
                    this.notify();
                    if (number <= 100) {
                        try {
                            Thread.sleep(10);
                        } catch (InterruptedException e) {
                            throw new RuntimeException(e);
                        }
                        System.out.println(Thread.currentThread().getName() + ":" + number);
                        number++;
                        try {
                            // 使得调用wait方法的线程进入阻塞状态。 执行 wait() 会释放锁。
                            this.wait();
                        } catch (InterruptedException e) {
                            throw new RuntimeException(e);
                        }
                    } else break;
                }
            }
        }
    }
    
    public class CommunicationTest {
        public static void main(String[] args) {
            Number number = new Number();
            Thread thread1 = new Thread(number);
            Thread thread2 = new Thread(number);
    
            thread1.setName("线程1");
            thread2.setName("线程2");
    
            thread1.start();
            thread2.start();
    
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63

    注意: 这里我们需要强调几点:
    1、首先我们的这三个通信方法都是Object类中的。
    2、其次,我们的通信方法wait() notify() notifyAll() 这三个方法必须使用在synchronized的同步代码块,或同步方法中。
    3、最后,我们的wait() 等这三个方法必须是我们同步监视器的对象去调用。否则会出现 IllegalMonitorStateException 异常

    我们来介绍一下这三个方法的作用

    • wait():释放锁(同步监视器) , 阻塞调用他的线程。
    • notify():唤醒wait的其中一个线程,如果是多个wait线程,则唤醒优先级比较高的一个。
    • notifyAll():唤醒所有wait的线程。

    注意点: sleep()和wait()的异同:

    相同点:

    • sleep()和wait()都会使得当前线程进入阻塞状态。

    不同点:

    • sleep()声明在Thread类中,wait()声明在object类中。
    • sleep()在任何位置都可以调用 , wait()只能在同步代码块或同步方法中调用。
    • sleep()和wait()都在同步代码块或同步方法中被调用时, sleep()会阻塞不会释放锁,wait阻塞同时会释放锁。

    生产者消费者问题

    经典例题:

    package communication;
    
    import com.sun.xml.internal.bind.v2.model.core.ID;
    
    /**
     * 生产者为店员提供产品,而消费者取走产品
     * 店员一次只能持有固定数量的产品,如果生产者视图生产更多产品,会叫停生产者,同样,如果没有商品了,消费者需要等待。
     *
     * 生产者线程,消费者线程。
     * 产品是共享数据。
     * 处理线程安全问题:synchronized , lock
     * 线程通信问题:
     */
    
    class Clerk{
    
        private int productCount = 0;
    
        // 生产产品
        public synchronized void produceProduct() {
            if (productCount < 20){
                productCount++;
                System.out.println(Thread.currentThread().getName() + ":开始生产第" + productCount + "个产品");
    //            notify();
            }else {
                try {
                    wait();
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        }
    
        // 消费产品
        public synchronized void consumerProduct() {
            if (productCount > 0){
                System.out.println(Thread.currentThread().getName() + ":开始消费第" + productCount + "个产品");
                productCount--;
    //            notify();
            }else {
                try {
                    wait();
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        }
    }
    
    class Producer extends Thread{
        private Clerk clerk;
    
        public Producer(Clerk clerk) {
            this.clerk = clerk;
        }
    
        @Override
        public void run() {
            System.out.println("生产者" + Thread.currentThread().getName() + "开始生产产品。。。。");
            while (true){
                try {
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                clerk.produceProduct();
            }
        }
    }
    
    class Consumer extends Thread{
        private Clerk clerk;
    
        public Consumer(Clerk clerk) {
            this.clerk = clerk;
        }
    
        @Override
        public void run() {
            while (true){
                try {
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                clerk.consumerProduct();
            }
        }
    }
    
    public class ProductTest {
        public static void main(String[] args) {
            Clerk clerk = new Clerk();
            Producer producer1 = new Producer(clerk);
            producer1.setName("生产者1");
    
            Consumer consumer1 = new Consumer(clerk);
            consumer1.setName("消费者1");
    
            producer1.start();
            consumer1.start();
    
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104

    线程创建三之实现Callable接口

    callable与runnable的区别:

    • callable中是重写call方法 , runnable中重写run方法
    • call方法可以有返回值 , run方法不可以有返回值。
    • call方法可以抛出异常,run方法不可以
    • call方法支持泛型,run方法不支持

    使用callable的方式创建线程步骤如下:
    1、创建一个类 实现callable接口。
    2、重写call方法
    3、实现callable接口的对象放入 futureTask 构造器中 , 创建futureTask对象。
    4、创建Thread类对象,将futureTask对象放到Thread构造器中。
    5、调用start方法启动线程。
    6、获取callable实现类中call方法的返回值,futureTask.get()

    package created.three;
    
    import java.util.concurrent.Callable;
    import java.util.concurrent.ExecutionException;
    import java.util.concurrent.FutureTask;
    
    /**
     *
     * 创建线程的方式三:实现callable接口的方式  jdk5.0新增的方法
     *
     * 1、创建一个实现callable接口的实现类
     * 2、实现call方法 , 将此线程需要执行的操作声明在call方法中。
     * 3、创建callable接口实现类的对象
     * 4、将此callable接口实现类的对象传到FutureTask构造器中,创建FutureTask的对象
     * 5、将FutureTask对象传入到Thread类构造器参数中 , 创建Thread类对象,并调用start
     * 6、获取callable实现类中call方法的返回值,futureTask.get()
     *
     *
     *
     * “@Auther: yaoyunfeng
     *
     * @Date: 2022/8/2 - 08 - 02 - 23:00
     * @Description: created.three
     * @version: 1.0
     */
    
    class NumberThread implements Callable{
    
        @Override
        public Object call() throws Exception {
            int sum = 0;
            for (int i = 1; i <= 100; i++) {
                if (i % 2 == 0){
                    System.out.println(i);
                    sum+=i;
                }
            }
            return sum;
        }
    }
    
    public class ThreadCallable {
        public static void main(String[] args) {
            NumberThread numberThread = new NumberThread();
            FutureTask futureTask = new FutureTask(numberThread);
            new Thread(futureTask).start();
            try {
                // get方法返回值即为FutureTask构造函数参数Callable对象的重写的call方法返回值
                Object sum = futureTask.get();
                System.out.println("总和为:" + sum);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            } catch (ExecutionException e) {
                throw new RuntimeException(e);
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57

    线程池

    提到线程池,我们就需要想一想,为什么需要线程池,线程池有什么好处呢,什么情况下我们使用线程池呢?
    接下来我来和大家一起分享一下线程池的使用场景。

    1. 首先我们什么情况下使用线程池:
    • 线程经常经历创建和销毁,使用量特别大的资源,比如并发情况下的线程,对系统性能影响较大。
    1. 线程池有什么好处呢?
    • 提前创建多个线程,放入线程池中,使用时直接获取,使用完放回池中,可以有效的避免线程的频繁创建与销毁,可以实现重复利用,避免大量资源的消耗。
    • 可以提高响应速度(减少了创建新线程的时间)
    • 降低资源消耗(重复利用线程池中的线程,不需要每次都创建)
    • 便于线程管理。
      1、 corePoolSize:核心线程池的大小
      2、maximumPoolSize:最大线程数
      3、keepAliveTime:线程没有任务时最多保持多长时间后会终止。
    package created.four;
    
    import java.util.concurrent.ExecutorService;
    import java.util.concurrent.Executors;
    import java.util.concurrent.ThreadPoolExecutor;
    
    /**
     * @Author 姚云峰
     * @Email starcpdk@163.com
     * @Date 2022/8/4 12:11
     * @Version 1.0
     */
    
    class NumberThread implements Runnable{
        @Override
        public void run() {
            for (int i = 0; i < 100; i++) {
                if (i % 2 == 0){
                    System.out.println(Thread.currentThread().getName() + "---" + i);
                }
            }
        }
    }
    
    
    class NumberThread1 implements Runnable{
        @Override
        public void run() {
            for (int i = 0; i < 100; i++) {
                if (i % 2 != 0){
                    System.out.println(Thread.currentThread().getName() + "---" +i);
                }
            }
        }
    }
    public class ThreadPool {
        public static void main(String[] args) {
            // 提供指定线程数量的线程池
            ThreadPoolExecutor executorService = (ThreadPoolExecutor) Executors.newFixedThreadPool(10);
    
            // 设置线程池的属性:
            executorService.setCorePoolSize(15);
    //        executorService.setKeepAliveTime();
    
    
            // 执行指定的线程的操作。需要提供实现runnable接口或callable接口实现类的对象。
    //        executorService.submit(); // 适合使用于callable
            executorService.execute(new NumberThread()); // 适合使用于runnable的方式
            executorService.execute(new NumberThread1()); // 适合使用于runnable的方式
            // 关闭连接池
            executorService.shutdown();
    
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54

    线程池:

    • newCachedThreadPool():
    • newFixedThreadPool():
    • newSingleThreadPool():
      他们的底层全部用的是ThreadPoolExecutor()这个类

    newCachedThreadPool():
    在这里插入图片描述

    newFixedThreadPool():
    在这里插入图片描述

    newSingleThreadPool():
    在这里插入图片描述

    那么为什么阿里巴巴不推荐使用这三种创建线程池呢?

    • newCachedThreadPool:因为他没有核心线程数,所以相当于有一个任务就会创建一个线程,线程的本质就是CPU,在高并发的情况下,CPU会承受不了。
    • newFixedThreadPool:这个是核心线程数固定,没有非核心线程数,也就不会受影响,但是他的队列是无界队列,也就是说有多少个任务就会有多少个队列节点去存任务,这样就会导致内存溢出。
    • newSingleThreadPool:他同newFixedThreadPool一致。

    下面我们来说说线程池中的提交优先级和执行优先级:
    提交优先级:核心线程 —— 队列 —— 非核心线程
    执行优先级:核心线程 —— 非核心线程 —— 队列

    总结:

  • 相关阅读:
    MongoDB差异数据对比的快速指南
    ciscn_2019_s_9
    15.前端笔记-CSS-PS切图
    mysql 多版本冲突安装(5..5和5.7)
    Docker系列--镜像和容器备份与恢复的方法
    一种基于动态代理的通用研发提效解决方案
    redis 外部访问 --chatGPT
    【JAVA开发】提高开发效率的工具分享
    Linux内核开发——内核镜像文件及启动过程
    ElasticSearch(一)【简介】
  • 原文地址:https://blog.csdn.net/weixin_44735933/article/details/125775081