• 多线程知识点总结之温故而知新


    进程、程序、线程

    提到多线程我们一定会想到与之相似的概念~
    也就是进程、程序
    那么他们之间有什么联系又有什么区别呢?

    1、程序:一段编程语言指令的集合 组成的静态代码即为程序。
    2、进程:运行着的程序即为进程,进程是一个动态的概念。
    3、线程:是进程中的一条执行路径。

    并行、并发

    通过上述我们一定了解到这三者的概念区别了吧~
    了解后我们就需要知道为什么要有多线程?多线程能用来做什么呢?
    多个线程并行执行,也就引出了我们的并行的概念~
    那与并行相关的我们不由得会想到并发~
    那么并发与并行有什么关系或区别呢?
    1、并行:多个CPU同时执行多个任务。是同一时刻的
    2、并发:一个CPU采用时间片的方式同时执行多个任务。是宏观上的同时。

    • 并行:微观上的同时、即同一时刻。
    • 并发:宏观上的同时、即同一时间段。
      这两个词太像了,我经常分不开。这里和大家分享一个我区分这两个概念的方式。

    试想我们和朋友一起走在路上,
    并行着走~那就是时时刻刻我们都是一起走。
    并发的话就是我们一起出发~一段路上可能你走在前面一会儿也可能你朋友走在前面一会儿,在宏观上你们是同时的,但是实际上你们是交替着走着。

    多线程

    了解了并行并发后就该来看看我们的主题~多线程了。
    要了解一个东西势必需要知道这个东西有什么好处值得我们去了解~
    那么多线程有什么好处呢?
    从名字上我们就可以大概猜到~多线程顾名思义——多个线程同时执行。
    那肯定会提高执行效率呀~

    我们在什么场景使用多线程呢?
    想必我们不能处处使用多线程,试想我们本来程序就只做一件事 , 我们有一个线程就够了~
    那我们还需要多线程吗?答案是否定的~
    使用场景大概如下:
    1、程序需要执行多个任务。
    2、程序需要做些后台任务,这时候我们可以启用一个分线程专门去做后台的事情,在主线程上也不会影响我们正常使用。
    3、程序需要实现一些需要等待的任务,比如打印,文件读写操作这些比较耗时的操作。这些操作我们也同样是可以让其分线程执行,我们的主线程正常运行我们主要的任务。

    创建多线程

    我们引出多线程的概念后我们就需要去了解一下如何创建一个多线程了。

    创建多线程的方式大概有四种:
    1、让一个类继承Thread类,重写Thread类中的run方法写我们自己的逻辑,new出我们新建的Thread类子类的对象,调用对象的start方法启动线程。

    package test;
    
    /**
     * @Author 姚云峰
     * @Email
     * @Date 2022/8/4 20:55
     * @Version 1.0
     */
    
    class TestThread extends Thread{
        @Override
        public void run() {
            for (int i = 0; i < 100; i++) {
                if (i % 2 == 0) {
                    System.out.println(Thread.currentThread().getName() + ":" + i);
                }
            }
        }
    }
    public class Test {
        public static void main(String[] args) {
            TestThread testThread = new TestThread();
            testThread.setName("线程1");
            testThread.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

    2、实现runnable接口创建多线程。创建一个类实现runnable接口,重写run方法,编写我们自己需要的逻辑,将实现runnable接口的对象传到new Thread对象的构造器中,调用new出的Thread对象的start方法启动线程。

    package test;
    
    /**
     * @Author 姚云峰
     * @Email
     * @Date 2022/8/4 20:55
     * @Version 1.0
     */
    
    class TestThread 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 Test {
        public static void main(String[] args) {
            TestThread testThread = new TestThread();
            Thread thread = new Thread(testThread);
            thread.setName("线程runnable1:");
            thread.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

    3、实现callable接口
    创建一个类实现callable接口,重写call方法实现自己的业务逻辑,在主线程中创建callable的实现类对象,创建futureTask类对象,将callable实现类对象传入futuretask的构造器中,创建Thread类对象,将futureTask对象传入thread类构造器中,执行thread类对象的start方法启动线程。可以通过调用futureTask对象的get方法拿到call方法的返回值。

    package test;
    
    import java.util.concurrent.Callable;
    import java.util.concurrent.ExecutionException;
    import java.util.concurrent.FutureTask;
    
    /**
     * @Author 姚云峰
     * @Email
     * @Date 2022/8/4 20:55
     * @Version 1.0
     */
    
    class TestThread implements Callable<Integer> {
    
        private int sum = 0;
    
        @Override
        public Integer call() throws Exception {
            for (int i = 0; i < 100; i++) {
                if (i % 2 == 0){
                    System.out.println(Thread.currentThread().getName() + ":" + i);
                    sum += i;
                }
            }
            return sum;
        }
    }
    public class Test {
        public static void main(String[] args) {
            TestThread testThread = new TestThread();
            FutureTask<Integer> integerFutureTask = new FutureTask<Integer>(testThread);
            Thread thread = new Thread(integerFutureTask);
            thread.start();
    
            try {
                Integer integer = integerFutureTask.get();
                System.out.println(integer);
            } 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

    4、线程池的方式,通过Excutors工具类中的方法我们可以创建一个线程池 Executors.newFixedThreadPool(10);创建一个核心线程数为10的线程池。
    通过调用线程池对象的excute或submit方法传入我们实现runnable接口的类的对象即可执行线程。

    package test;
    
    import java.util.concurrent.*;
    
    /**
     * @Author 姚云峰
     * @Email
     * @Date 2022/8/4 20:55
     * @Version 1.0
     */
    
    class TestThread implements Runnable {
    
        @Override
        public void run() {
            for (int i = 0; i < 100; i++) {
                try {
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                if (i % 2 == 0) {
                    System.out.println(Thread.currentThread().getName() + ":" + i);
                }
            }
        }
    }
    
    public class Test {
        public static void main(String[] args) {
            ExecutorService executorService = Executors.newFixedThreadPool(10);
            executorService.execute(new TestThread());
        }
    }
    
    • 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

    创建多线程的方式对比

    介绍完我们的这四种创建线程的方式后我们就需要去对比一下了~,什么时候用什么方式比较合适 , 下面就让我们来一起探讨一下吧!

    1、继承Thread类的方式:

    • 因为继承只能是单继承,也因此继承Thread类的方式有着一定的局限性
    • 同时,继承Thread子类的方式,我们启动多个线程需要new多个继承thread类的子类对象,调用start方法,这样的话,定义在线程子类中的变量就无法共享了,因为我们new了多个对象。如果想在这种方式下使得变量共享,一种情况是将我们子类中的变量声明为静态变量,一种就是通过构造器外部传参的方式传入同一个对象这样也就共享了。

    2、实现runnable接口的方式

    • 因为接口可以多实现,也因此解决了继承thread类的局限性
    • 因为我们实现runnable接口后将实现runnable接口的对象传入thread类构造器中创建thread对象调用start方法,因此我们的runnable实现类对象始终是一个,天然就是共享的了。

    3、实现callable接口的方式

    • 首先实现callable接口的方式同runnable接口的方式一样是可以多实现的。
    • callable不同于runnable接口的是他可以有返回值,并且callable可以使用泛型。同时还可以抛出异常。

    4、线程池的方式

    • 线程池顾名思义 ,是一个池子,准备好了多个线程的意思。
      这么做得好处首先是 ,减少了频繁创建销毁线程的情况,只是用用完了就换回到池子中就可以。
      同时线程可以共用,降低了资源的消耗,不需要频繁创建线程。
      并且线程池的方式更便于管理线程,可以通过设置线程池的大小决定创建多少个线程等。
      1、 corePoolSize:核心线程池的大小
      2、maximumPoolSize:最大线程数
      3、keepAliveTime:线程没有任务时最多保持多长时间后会终止。

    Thread线程类中的一些方法

    相信到这里大家对如何创建线程,什么场景使用多线程已经有了初步的了解。接下来我们就来了解了解线程的一些方法的使用吧~

    1. start():我们最早接触的一个方法,启动线程要调用的方法
    2. run():需要被重写,创建线程需要执行的操作写在这个方法中
    3. currentThread():静态方法,返回执行当前代码的线程
    4. getName():获取当前线程名
    5. setName():设置线程名
    6. yield():释放CPU执行权
    7. join():在线程A中调用B的join()方法,此时线程A阻塞,直到线程B执行完,线程A才结束阻塞。
    8. sleep(long time):让线程睡眠指定的毫秒时间。睡眠时间内,线程处于阻塞状态。
    9. isAlive():判断当前线程是否还存活
    10. stop():结束线程,方法已过时。

    线程的生命周期

    了解过我们线程中的一些方法,我们大概也能感受到线程的从创建到销毁会有一些状态,让我们一起探索一下线程的生命周期吧。
    在这里插入图片描述

    线程的调度与同步

    通过以上介绍我们有没有想到一种情况,那便是 , 如果有多个线程,哪儿个先执行,执行顺序是什么,多个线程同时操作某个共享的数据会不会产生一些问题?

    下面让我们一一解决这些问题吧。

    线程的调度

    线程的调度是具备一定优先级的 , 有三个枚举代表线程的调度优先级

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

    我们可以使用以下方法设置线程的优先级,高优先级原则上会抢占低优先级的CPU执行权,但是只是从概率上讲 ,高优先级的线程高概率的情况下被执行,并不以围着只有高优先级的线程执行完后,低优先级就线程才执行。

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

    相信通过以上讲解,我们一定了解了线程怎么执行的了。

    线程的同步

    说完调度,我们会发现,线程的调度是随机的,两个线程同时操作一个共享变量的话 , 那这个共享变量最终的值一定不会是正常的了。这就涉及到我们的线程同步了。
    什么是线程同步呢?线程怎么能同步呢?
    下面就让我们来介绍一下吧。

    我们首先先大概了解一下线程的同步有哪儿几种方法

    1、synchronized同步代码块
    2、synchronized同步方法
    3、Lock锁

    下面就让我们一一介绍一下这三种同步吧

    同步代码块

    synchronized(同步监视器){
    	需要被同步的方法
    }
    
    • 1
    • 2
    • 3

    这里我们可以看到两个概念

    1、同步监视器:其实就是锁,可以是任何对象,但是多个线程操作这部分同步代码时,同步监视器的对象必须时同一个对象。
    2、需要被同步的方法:操作共享数据的部分就是我们需要被同步的方法
    3、共享数据:多个线程共同操作的变量。比如《创建三个窗口卖票 , 总票数为100张》这个时候三个窗口就是三个线程,他们同时操作票,这个票就是共享数据。

    同步监视器

    1、在实现Runnable接口创建的多线程的方式中,我们可以考虑将this作为同步监视器。
    2、在继承Thread类创建多线程的方式中,慎用this充当同步监视器,可以考虑使用当前类做同步监视器类.class。

    示例代码:
    1、实现runnable接口的方式

    package test;
    
    /**
     * @Author 姚云峰
     * @Email
     * @Date 2022/8/4 20:55
     * @Version 1.0
     */
    
    class TestThread implements Runnable {
    
        private int ticket = 100;
    
        @Override
        public void run() {
            while (true){
                synchronized (this){
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                    if (ticket > 0){
                        System.out.println(Thread.currentThread().getName() + "卖了第" + ticket + "张票");
                        ticket--;
                    }else {
                        break;
                    }
                }
            }
    
        }
    }
    
    public class Test {
        public static void main(String[] args) {
            TestThread testThread = new TestThread();
    
            Thread thread1 = new Thread(testThread);
            Thread thread2 = new Thread(testThread);
    
            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

    2、继承thread类的方式

    package test;
    
    import com.sun.org.apache.bcel.internal.generic.NEW;
    
    import java.util.concurrent.*;
    
    /**
     * @Author 姚云峰
     * @Email
     * @Date 2022/8/4 20:55
     * @Version 1.0
     */
    
    class TestThread extends Thread {
    
        private static int ticket = 100;
    
        @Override
        public void run() {
            while (true){
                synchronized (TestThread.class){
                    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 Test {
        public static void main(String[] args) {
            TestThread testThread1 = new TestThread();
            TestThread testThread2 = new TestThread();
    
            testThread1.setName("线程1");
            testThread2.setName("线程2");
    
            testThread1.start();
            testThread2.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

    同步方法

    给方法加一个synchronized修饰符即可

    public synchronized void test(){
    	需要同步的代码
    }
    
    • 1
    • 2
    • 3

    同步方法中也同样存在同步监视器的概念,只是隐藏了

    > 非静态的同步方法,同步监视器是this
    
    > 静态的同步方法,同步监视器是当前类本身。
    
    • 1
    • 2
    • 3

    示例代码:
    1、实现runnable接口的方式 对应的是非静态的同步方法

    package test;
    
    import com.sun.org.apache.bcel.internal.generic.NEW;
    
    import java.util.concurrent.*;
    
    /**
     * @Author 姚云峰
     * @Email
     * @Date 2022/8/4 20:55
     * @Version 1.0
     * // 默认同步监视器是 this
     */
    
    class TestThread implements Runnable {
    
        private int ticket = 100;
    
        @Override
        public void run() {
            while (true) {
                show();
            }
    
        }
    
        private synchronized void show() {
            if (ticket > 0) {
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                System.out.println(Thread.currentThread().getName() + "卖了第" + ticket + "张票");
                ticket--;
            }
        }
    }
    
    public class Test {
        public static void main(String[] args) {
        
            TestThread testThread = new TestThread();
            Thread thread1 = new Thread(testThread);
            Thread thread2 = new Thread(testThread);
            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

    2、继承thread类的方式 对应静态同步方法

    package test;
    
    import com.sun.org.apache.bcel.internal.generic.NEW;
    
    import java.util.concurrent.*;
    
    /**
     * @Author 姚云峰
     * @Email
     * @Date 2022/8/4 20:55
     * @Version 1.0
     * // 同步监视器是 TestThread.class
     */
    
    class TestThread extends Thread {
    
        private static int ticket = 100;
    
        @Override
        public void run() {
            while (true) {
                show();
            }
    
        }
    
        private static synchronized void show() {
            if (ticket > 0) {
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                System.out.println(Thread.currentThread().getName() + "卖了第" + ticket + "张票");
                ticket--;
            }
        }
    }
    
    public class Test {
        public static void main(String[] args) {
            TestThread testThread1 = new TestThread();
            TestThread testThread2 = new TestThread();
    
            testThread1.setName("线程1");
            testThread2.setName("线程2");
    
            testThread1.start();
            testThread2.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

    Lock锁

    在需要同步的代码起始位置加 lock()锁定 , 在结束同步的位置加unlock()解锁代码即可。
    需要注意这里的lock锁对象要保证唯一性。

    package test;
    
    import com.sun.org.apache.bcel.internal.generic.NEW;
    
    import java.util.concurrent.*;
    import java.util.concurrent.locks.ReentrantLock;
    
    /**
     * @Author 姚云峰
     * @Email
     * @Date 2022/8/4 20:55
     * @Version 1.0
     */
    
    class TestThread extends Thread {
    
        private static int ticket = 100;
    
        private static ReentrantLock lock = new ReentrantLock(true);
    
        @Override
        public void run() {
            while (true) {
                try {
                    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 Test {
        public static void main(String[] args) {
            TestThread testThread1 = new TestThread();
            TestThread testThread2 = new TestThread();
    
            testThread1.setName("线程1");
            testThread2.setName("线程2");
    
            testThread1.start();
            testThread2.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

    我们既然了解了这三种同步方式,那就让我们对这三种同步做一个对比吧
    同步代码块和同步方法的差异不大, 其实就是 一个是代码块用synchronized修饰一个是方法用synchronized修饰。


    我们这里着重说一下lock和synchronized的区别:

    lock和synchronized的异同:
    相同:都是解决线程不安全的方式
    不同:
    1、lock需要手动调用方法进行锁定与解锁 , synchronized则是自动进行解锁和锁定的。
    2、synchronized无法判断锁的状态,lock可以判断锁的状态
    3、synchronized可重入、不可中断、非公平 lock可重入、可判断、可公平
    4、synchronized在发生异常时候会自动释放占有的锁,因此不会出现死锁;而lock发生异常时候,不会主动释放占有的锁,必须手动unlock来释放锁,可能引起死锁的发生。
    5、Lock可以通过trylock来知道有没有获取锁,而synchronized不能;

    • 同步代码块:monitorenter指令是在编译后插入到同步代码块的开始位置,monitorexit指令插入到同步代码块的结束位置,JVM需要保证每一个monitorenter都有一个monitorexit与之相对应。任何对象都有一个monitor与之相关联,当且一个monitor被持有之后,他将处于锁定状态。线程执行到monitorenter指令时,将会尝试获取对象所对应的monitor所有权,即尝试获取对象的锁;
    • 同步方法:依靠的是方法修饰符上的ACC_SYNCHRONIZED实现。

    说完线程同步后 , 我们试想一下有没有一种情况 , 多个线程,互不相让,自己持有锁的同时都在等待对方释放锁,这种情况下就会出现死锁了,很明显一定会有的。
    让我们了解了解死锁吧

    死锁

    示例代码:

    package test;
    
    import com.sun.org.apache.bcel.internal.generic.NEW;
    
    import java.util.concurrent.*;
    import java.util.concurrent.locks.ReentrantLock;
    
    /**
     * @Author 姚云峰
     * @Email
     * @Date 2022/8/4 20:55
     * @Version 1.0
     */
    
    public class Test {
    
        public static void main(String[] args) {
    
            StringBuffer s1 = new StringBuffer();
            StringBuffer s2 = new StringBuffer();
    
            new Thread(new Runnable() {
                @Override
                public void run() {
                    synchronized (s1){
                        s1.append("a");
                        s2.append("1");
                        try {
                            Thread.sleep(10);
                        } 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(){
                @Override
                public void run() {
                    synchronized (s2){
                        s1.append("c");
                        s2.append("3");
                        try {
                            Thread.sleep(10);
                        } 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
    • 61
    • 62
    • 63
    • 64

    线程通信

    说完同步后在我们多线程中还存在一个概念是通信
    什么时候线程需要通信呢?
    也就是说线程和线程之间需要知道对方在干嘛时就需要通信

    假设一个例子:
    我们需要一个线程打印100以内奇数,一个线程打印100以内偶数,并且是打印需要是一个奇数一个偶数交替打印的。
    很明显这时候我们需要其中一个线程需要知道另外一个线程打印了没有,是否该轮到自己打印了。这时候就需要用到线程通信。

    涉及到的三个方法:

    • wait(): 一旦执行此方法,当前线程进入阻塞状态,并释放同步监视器
    • notify():一旦执行此方法,就会唤醒被wait的一个线程,如果有多个线程被wait() , 就唤醒优先级高的那个
    • notifyAll():一旦执行此方法,就会唤醒所有被wait()的线程

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

    示例代码:

    package test;
    
    import com.sun.org.apache.bcel.internal.generic.NEW;
    import com.sun.xml.internal.bind.v2.model.core.ID;
    
    import java.util.concurrent.*;
    import java.util.concurrent.locks.ReentrantLock;
    
    /**
     * @Author 姚云峰
     * @Email
     * @Date 2022/8/4 20:55
     * @Version 1.0
     */
    
    public class Test {
        public static void main(String[] args) {
    
            Runnable runnable = new Runnable() {
                private int num = 100;
    
                @Override
                public void run() {
                    while (num >= 0) {
                        synchronized (this){
                            notify();
                            System.out.println(Thread.currentThread().getName() + "打印:" + num);
                            num--;
                            try {
                                wait();
                            } catch (InterruptedException e) {
                                throw new RuntimeException(e);
                            }
                        }
                    }
                }
            };
    
            new Thread(runnable).start();
            new Thread(runnable).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

    学完线程通信我们了解到了wait()可以阻塞当前线程
    这里我们不由得会想到sleep()也可以阻塞线程 , 那么他们有什么区别呢

    1、sleep阻塞指定时间后自动唤醒,wait需要调用notify或notifyAll唤醒
    2、sleep阻塞不释放锁 , wait阻塞后释放锁。
    3、sleep在任何位置都可调用,wait只能在同步代码块或同步方法中调用
    4、sleep声明在thread类中,wait声明在object类中。

    多线程相关
    而知

  • 相关阅读:
    RabbitMQ(五)【入门案例】
    远程互动会议平台是什么?
    示例-安装office2016图文教程简体中文下载安装包
    DataFrame截断某行(列)之前(后)的数据DataFrame.truncate()
    云计算-存算一体-EDA-技术杂谈
    Source Insight使用教程(3)——常用功能扩展篇
    精准定位,智慧港口:北斗技术在港口车辆智能监管中的应用
    第三方软件测试服务有哪些形式?选择时如何避雷?
    2022华数杯建模A题思路解析
    记录QEMU上模拟ARM运行环境(内核 2.6.30)
  • 原文地址:https://blog.csdn.net/weixin_44735933/article/details/126166135