• 多线程可见


    进程和线程

    进程和线程的关系

    1. 进程包含线程,一个进程可以有一个线程(主线程),也可以有多个线程,每个线程拥有一份PCB对象
    2. 进程是操作系统分配资源的基本单位,线程是操作系统调度的基本单位.
    3. 每个进程内拥有自己独立的虚拟地址空间和独立的文件描述符表,而一个进程里的所有线程公用进程里的资源,所以同一个进程里的线程会互相影响.

    为什么会出现线程

    首先 并发编程(1.单核cpu发展遇到瓶颈,为了提高算力,需要多核cpu,就用到了并发编程 2. 有些任务需要"等待IO",在这期间,可以利用并发编程去做其他的事情) 成为刚需, 其次 虽然进程也能实现并发编程,但是线程比进程更加轻量:

    1. 创建线程比创建进程更快
    2. 销毁线程比销毁进程更快
    3. 调度线程比调度进程更快
    4. 使用多线程就是没有了操作系统为进程分配资源的时间

    java线程和操作系统线程的关系

    操作系统内部实现了线程这样的机制,并对用户层提供了API进行使用,而java的Thread类可以认为就是对操作系统的API进行了进一步的抽象和封装

    第一个多线程代码

    通过自己的MyThread继承Thread类

    class MyThread extends Thread{
        //重写run 明确新创建的线程干啥活
        @Override
        public void run() {
            while(true){
                System.out.println("hello Thread");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
    public class Demo1 {
        public static void main(String[] args) throws InterruptedException {
            System.out.println("main");
            Thread thread = new MyThread();
            thread.start();//真正开始创建线程(在操作系统内核中,创造出对应的PCB,PCB加入系统链表,参与调度)
    
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    第一个并发编程代码

    class MyThread extends Thread{
        //重写run 明确新创建的线程干啥活
        @Override
        public void run() {
            while(true){
                System.out.println("hello Thread");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
    public class Demo1 {
        public static void main(String[] args) throws InterruptedException {
            System.out.println("main");
            Thread thread = new MyThread();
            thread.start();//真正开始创建线程(在操作系统内核中,创造出对应的PCB,pcb加入系统链表,参与调度)
    
            while(true){
                System.out.println( "hello main");
                Thread.sleep(1000);
            }
        }
    }
    
    
    • 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

    我们可以看到,进程由main主线程进入,然后main线程和thread交替进行.
    (但是在阻塞一秒后,先唤醒main线程还是thread线程是不确定的,操作系统对线程的调度是随机性的)
    在这里插入图片描述

    创建线程的多种方法

    1.继承Thread类

    第一就是上文中创建类继承Thread类,并重写run方法

    2.实现Runnable接口

    第二种就是实现Runnable接口,并实现其中的run方法,
    Runnable接口是个函数式接口,其中只有一个run方法

    class MyRnnnable implements Runnable{
        @Override
        public void run() {
            while(true){
                System.out.println("hello myrunnable");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
    public class Demo2 {
        public static void main(String[] args) throws InterruptedException {
    
            //解耦合
            //任务内容 和 线程本身 分离开
            Runnable runnable = new MyRnnnable();//创建的runnable定义了任务
            Thread t = new Thread(runnable);//把任务交给thread
            t.start();//start创建具体线程
    
            while(true){
                System.out.println("hello main");
                Thread.sleep(1000);
            }
        }
    }
    
    • 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

    这样的优点是提高了解耦合度,重写的run方法只是定义了任务内容,然后通过Runnable把描述好的任务交给Thread实例,t.start()完成了线程的创建.

    3.匿名内部类

    匿名内部类可以说完成了三步: 1.继承 2. 方法的重写 3. 类的实例化

    3.1 匿名继承Thread类

    public static void main(String[] args) {
            
            //匿名内部类
            //继承  方法重写  实例化
            //只是准备好了    还是需要start创建   因为线程还是需要操作系统创建
    
            Thread t = new Thread(){
                @Override
                public void run() {
                    System.out.println("hello thread");
                        try {
                            Thread.sleep(1000);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                }
            };
            t.start();
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    3.2 匿名Runnable接口

    public static void main(String[] args) {
            //两步实现
            /*Runnable runnable = new Runnable() {
                @Override
                public void run() {
                    while(true){
                        System.out.println("hello thread");
                        try {
                            Thread.sleep(1000);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }
            };
    
            Thread thread = new Thread(runnable);
            thread.start();*/
    
    		// 一步实现
            Thread thread = new Thread(new Runnable() {
                @Override
                public void run() {
                    System.out.println("hello thread");
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            });
        }
    
    • 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

    4. lambda表达式

    public static void main(String[] args) {
            //lambda 表达式
            Thread t = new Thread(() -> {
                while(true){
                    System.out.println("hello thread");
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            });
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    通常选择 Runnable 来写更好一些,来完成更好的解耦.写代码注重 高内聚,低耦合.Runnable 单纯的只是描述了一个任务.

    多线程节约时间

    我们利用单线程和多线程来计算两个变量的自增,从0自增到20亿,并记录两个线程各自的时间来完成比较. 不过,此代码的时间戳在main线程中,为了完成多线程时间的记录,我们需要在main线程中调用t1.join和t2.join 来让main线程等待t1和t2线程执行完毕.

    public class Demo {
        private static final long COUNT = 20_0000_0000;
        private static void serial(){
            //把方法执行时间记录
            //记录当前的毫秒时间
            long beg = System.currentTimeMillis();
    
            int a = 0;
            for(long i = 0; i < COUNT; i++){
                a++;
            }
            a = 0;
    
            for(long i = 0; i < COUNT; i++){
                a++;
            }
    
            long end = System.currentTimeMillis();
            System.out.println("单线程消耗时间: " + (end - beg) +" ms");
    
        }
    
        private static void concurrency(){
            long beg = System.currentTimeMillis();
            Thread t1 = new Thread(() -> {
               int a = 0;
                for (long i = 0; i < COUNT; i++) {
                    a++;
                }
            });
    
            Thread t2 = new Thread(() ->{
                int a = 0;
                for(long i = 0; i < COUNT;i++){
                    a++;
                }
            });
    
            t1.start();
            t2.start();
    
            try{
                t1.join();
                t2.join();
            }catch(InterruptedException e){
                e.printStackTrace();
            }
            long end = System.currentTimeMillis();
            System.out.println("双线程执行事件:" + (end - beg)+ " ms");
        }
        public static void main(String[] args) {
            concurrency();
            serial();
        }
    }
    
    • 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

    在这里插入图片描述
    由此我们可以见到多线程确实对时间完成了优化,那么为什么不是完全的优化一半时间呢? 这是因为线程的创建也需要占用时间.

    Thread类的属性和方法

    创建线程对象的同时命名

    利用Thread的构造方法可以对线程进行命名,如下将线程命名为 张三

    public static void main(String[] args) {
            Thread t = new Thread(() ->{
                while(true){
                    System.out.println("Thread1");
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            },"Thread");
            t.start();
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    使用jconsole.exe来查看java进程

    打开jdk文件夹下bin包中的jconsole.exe
    在这里插入图片描述
    右键以管理员身份运行,此时就可以看到正在运行的进程
    在这里插入图片描述
    点击连接后,大胆的选择不安全的连接
    在这里插入图片描述
    之后就可以得到我们创建的线程的信息
    在这里插入图片描述

    isDaemon 是否为后台程序

    线程分为后台线程和前台线程,进程在执行完所有的前台线程后就会退出进程,而不管后台线程是否执行完毕.

    线程启动 start

    start决定了一个线程真正的启动,而run方法只是定义了线程要完成的任务

    public class Demo8 {
        public static void main(String[] args) throws InterruptedException {
            Thread t = new Thread(new Runnable() {
                @Override
                public void run() {
                    while(true){
                        System.out.println("hello MyThread");
                        try {
                            Thread.sleep(1000);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }, "MyThread");
            t.start();
    
            while(true){
                System.out.println("hello main");
                Thread.sleep(1000);
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    在这里插入图片描述
    如此可见 start启动线程后 ,t线程和main线程随机执行
    但是如果只调用run方法的话

    public class Demo8 {
        public static void main(String[] args) throws InterruptedException {
            Thread t = new Thread(new Runnable() {
                @Override
                public void run() {
                    while(true){
                        System.out.println("hello MyThread");
                        try {
                            Thread.sleep(1000);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }, "MyThread");
            t.run();
    
            while(true){
                System.out.println("hello main");
                Thread.sleep(1000);
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    在这里插入图片描述
    可见只调用run方法的话,只是在main线程中调用了一个方法,并没有启动新的线程.

    中断线程

    在线程执行过程中,如果发生问题我们可以中断线程

    手动设置标志位控制线程结束

    设置isQuit变量来控制线程的结束

     private static boolean isQuit = false;
    
        public static void main(String[] args) {
            Thread t = new Thread(() ->{
                while(!isQuit){
                    System.out.println("线程运行中");
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                System.out.println("新线程执行结束");
            });
    
            t.start();
    
            try {
                Thread.sleep(5000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("控制新线程退出");
            isQuit = true;
    
        }
    
    • 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

    使用Thread内置的标志位

    1.Thread.interrupted() 是Thread类的静态方法 它的返回值是boolean, 他在默认情况下返回值是false,当在使用 thread 对象的 interrupted() 方法通知线程结束时,Thread.interruped的标志位变成true,然后在变回false.
    2.Thread.currentThread().isInterrupted() 是Thread的实例方法, Thread.currentThread()返回的是当前Thread类的实例.

     public static void main(String[] args) throws InterruptedException {
            Thread t = new Thread(() ->{
                while(!Thread.currentThread().isInterrupted()){//获取到当前线程的实例  判断内置的标志位
                    System.out.println("线程运行中");
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        //e.printStackTrace();
                        System.out.println("即将退出");
                        try {
                            Thread.sleep(1000);
                        } catch (InterruptedException ex) {
                            ex.printStackTrace();
                        }
                        System.out.println("退出");
                        //break;
                    }
                }
            });
            t.start();
    
            Thread.sleep(5000);
            System.out.println("控制新线程退出");
            t.interrupt();//对线程内部进行操作,若处于sleep阻塞状态,则会以 InterruptedException 异常的形式通知,清除中断标志,否则是对内置标志位进行操作
    
        }
    
    • 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

    当出现InterruptedException是,是否结束线程取决于catch()内部的写法,可以忽略,也可以选择结束

    jion线程等待

    多个线程之间的执行顺序是不一定的,我们无法彻底决定调度器调度哪个线程,但是我们可以使用一些手段,来决定谁是先执行的,使用join()实例方法

    哪个线程调用的join,哪个线程就会优先执行,一直到调用join线程的run方法执行完毕

    public static void main(String[] args) throws InterruptedException {
            Thread t = new Thread(() ->{
                    System.out.println("Hello Thread");
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
            });
    
            System.out.println(t.getState());
            t.start();
            t.join();
    
            System.out.println(t.getState());
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    在这里插入图片描述

    线程的状态

    java中线程的状态是一个枚举类型

    1. NEW:任务分配完毕,但是线程还未启动的,即还未调用start方法的
    2. TERMINATED:工作完成了的,即线程的run方法执行完毕
    3. RUNNABLE:可工作的,即就绪的线程,为正在工作中或马上要进行工作的线程
    4. TIMED_WAITING:调用sleep()进入排队
    5. BLOCKED:表示当前线程在等待索,也是在排队
    6. WAITING:线程等待被唤醒状态,
      在这里插入图片描述

    线程的安全

    如果多线程代码运行的效果符合我们的预期想法,即在单线程下运行的效果,我们就认为线程是安全的

    引起线程不安全主要有以下几个原因:
    1.因为线程是抢占式执行,随机调度的,这是造成线程不安全的主要原因
    2.多个线程对同一个变量进行修改操作
    3.操作方式不是原子性的,会造成线程不安全
    4.内存可见性问题(可见性指,一个线程对共享变量进行多次修改时,其他线程能及时的读取到修改后的变量的值.)
    5.指令重排序:其是指在一段代码中,JVM、CPU指令集会对其进行优化,从而达到时间的更好地效率,这在多线程的情况下很可能使逻辑和之前不对等.

    操作方式非原子的线程不安全

    我们对同一个共享变量,使用两个线程对其分别进行50000次增加,最后来看一下变量的数值

    class Counter{
        public int count;
    
        public void increase(){
            count++;
        }
    }
    public class Demo13 {
        public static Counter counter = new Counter();
    
    
        public static void main(String[] args) throws InterruptedException {
            Thread t1 = new Thread(() ->{
                for(int i = 0; i < 50000; i++){
                    counter.increase();
                }
            });
    
            Thread t2 = new Thread(() ->{
                for(int i = 0; i < 50000; i++){
                    counter.increase();
                }
            });
    
            t1.start();
            t2.start();
    
            t1.join();
            t2.join();
    
            System.out.println("count: "+ counter.count);
        }
    }
    
    • 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

    在这里插入图片描述
    由此我们可以看到结果并不是我们预想的10万,这就是出现了线程安全的问题,
    但是为什么是这样呢?

    原因

    这是因为count++ 其实是三个指令:
    1.将内存中的count值读取到CPU的寄存器中
    2.CPU的寄存器完成 +1 的运算
    3.将寄存器中count的值重新读取到内存中

    但是因为抢占式执行,随机调度的原因,两个线程的三个步骤执行可能会有很多种情况,
    在这里插入图片描述
    在这种情况时,t1线程 load 后,一个寄存器中count的值为0,
    t2 load add save后,另一个寄存器中count的值为1,内存中count的值也变为1,
    此时t1线程 add save后 寄存器中的count值为1,内存中count的值还为1,
    这时,两个线程分别进行了count的自增,但是count的值为1.

    这就出现了线程不安全的问题

    解决

    像这种情况,我们就需要加锁()来解决问题, 通过synchronized加锁,使代码的执行变成原子的(就是整体的 将加锁中的代码完全执行完,才可以变换为令一个线程)

    class Counter{
        public int count;
    
        public synchronized void increase(){
            count++;
        }
    }
    public class Demo13 {
        public static Counter counter = new Counter();
    
    
        public static void main(String[] args) throws InterruptedException {
            Thread t1 = new Thread(() ->{
                for(int i = 0; i < 50000; i++){
                    counter.increase();
                }
            });
    
            Thread t2 = new Thread(() ->{
                for(int i = 0; i < 50000; i++){
                    counter.increase();
                }
            });
    
            t1.start();
            t2.start();
    
            t1.join();
            t2.join();
    
            System.out.println("count: "+ counter.count);
        }
    }
    
    • 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

    在这里插入图片描述

    synchronized 用法

    synchronized的意思使"同步",其在不同的环境中有不同的意义,在多线程的环境下,"同步"的意义就是互斥,即一个在进行时,另一个无法插入.

    我们称synchronized为对象的synchronized,某个线程执行到一个对象的synchronized时,另一个线程执行到同一个对象的synchronized就会阻塞等待.

    java的 锁 是存在在对象头里的
    在这里插入图片描述

    针对每一把锁,操作系统内部都维护了一个等待队列,当某个锁被一个线程占有时,其他的线程想要进行加锁操作就加不上了,从而进入了阻塞等待的状态,一直到之前被锁住的线程执行完后,操作系统在调度其他等待锁的线程
    在这里插入图片描述
    synchronized是 可重入锁 的,即当同一个线程对同一个锁对象进行二次执行,是可以允许完成的.
    如下图,两个increase都是对 this 对象加锁的

    class Counter1{
        public volatile static int counter;
    
        public synchronized void increase1(){
            counter++;
        }
        public synchronized void increase2(){
            counter++;
        }
    }
    public class Demo26 {
        public static Counter1 counter = new Counter1();
    
        public static void main(String[] args) throws InterruptedException {
    
            Thread t1 = new Thread(() ->{
                counter.increase1();
                counter.increase2();
            });
    
            t1.start();
            t1.join();
            System.out.println(counter.counter);
        }
    
    }
    
    
    • 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

    synchronized 使用示例

    1. synchronized直接修饰普通方法 锁的是synchronized对象
    public class SynchronizedDemo {
      public synchronized void methond() {
     }
    }
    
    • 1
    • 2
    • 3
    • 4
    1. synchronized修饰类方法 锁的是synchronized的类对象
    public class SynchronizedDemo {
      public synchronized static void method() {
     }
    }
    
    • 1
    • 2
    • 3
    • 4
    1. 修饰代码块: 需要明确指定锁哪个对象
      锁当前对象:
    public class SynchronizedDemo {
      public void method() {
        synchronized (this) {
         
       }
     }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    锁类对象

    public class SynchronizedDemo {
      public void method() {
        synchronized (SynchronizedDemo.class) {
       }
     }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    两个线程竞争竞争同一把锁才会产生竞争,两个线程分别尝试获取不同的锁不会产生竞争.

    内存可见性导致的线程不安全

    可见性指, 一个线程对共享变量值的修改,能够及时地被其他线程看到.

    1. 当线程要读取一个共享变量的时候, 会先把变量从内存拷贝到cpu的寄存器中, 再从寄存器读取数据.
    2. 当线程要修改一个共享变量的时候, 也会先修改寄存器中的副本, 再同步回内存

    由于每个线程有自己的cpu寄存器位置, 这些寄存器中的内容相当于同一个共享变量的 “副本”. 此时修改线程1的寄存器中的值, 线程2 的寄存器不一定会及时变化.

    1. 初始情况下, 两个线程的工作内存内容一致.
    2. 一旦线程1 修改了 a 的值, 此时内存不一定能及时同步. 对应的线程2 寄存器中的 a 的值也不一定能及时同步.
    static class Counter{
            public int flag = 0;
        }
    
        public static void main(String[] args) {
            Counter counter = new Counter();
    
            Thread t1 = new Thread(() ->{
                while(counter.flag == 0){
    
                }
                System.out.println("循环结束");
            });
    
    
            Thread t2 = new Thread(() ->{
                Scanner scanner = new Scanner(System.in);
                System.out.println("请输入flag的值");
                counter.flag = scanner.nextInt();
            });
            t1.start();
    
            t2.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

    当flag的值修改后,循环并没有立刻退出
    在这里插入图片描述
    但是,在加入volatile后.就可以保证内存可见性
    在这里插入图片描述

    在这里插入图片描述
    但是使用 synchronized 关键字加锁。不光保证原子性,还能保证内存可见性。被 synchronized 包裹起来的代码,编译器就不敢轻易的做出上面优化的那种操作。

    wait 和 notify

    wait和notify分别指阻塞和通知停止阻塞,更倾向于处理线程调度随机性的问题.
    wait必须在加锁的代码块中使用,
    wait 内部会做三件事:1、先释放锁 2、等待其他线程的通知 3、收到通知之后,重新获取锁,并继续往下执行。

    public static void main1(String[] args) throws InterruptedException {
        Object object = new Object();
        //wait 哪个对象,就得针对哪个对象加锁。
        synchronized (object) {
            System.out.println("wait 前");
            object.wait();
            System.out.println("wait 后");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    在这里插入图片描述

    public static Object locker = new Object();
        public static void main(String[] args) {
            Thread waitTask = new Thread(() ->{
                synchronized (locker){
                    try{
                        System.out.println("wait 开始");
                        locker.wait();
                        System.out.println("wait 结束");
    
                    }catch(InterruptedException e){
                        e.printStackTrace();
                    }
                }
            });
    
            waitTask.start();
    
            Thread notifyTask = new Thread(() ->{
                Scanner scanner = new Scanner(System.in);
                System.out.println("请输入任意内容");
                scanner.next();
    
                synchronized (locker){
                    System.out.println("notify 开始");
                    locker.notify();
                    System.out.println("notify 结束");
                }
            });
            notifyTask.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

    在这里插入图片描述

    notifyAll

    假如一个锁对象有10个阻塞的线程,notify只会随机唤醒一个线程,而notifyAll则会唤醒所有阻塞的线程,但是仍处于随即调度状态.

  • 相关阅读:
    【ROS2原理】IDL接口映射
    想要进入IB国际学校,这些证书你要知道
    手把手教你搭建ELK-新手必看-第四章:搭建logstash
    Spring boot 如何使用视图解析器 thymeleaf 模板引擎整合html公共部分详情
    Java基础学习之JavaScript
    【软考】系统集成项目管理工程师(九)项目成本管理
    使用证书的方式登录linux 系统或者windows系统
    Python 入门
    Vitepress搭建组件库文档(上)—— 基本配置
    java面试八股文2023完整版详解110题附带答案
  • 原文地址:https://blog.csdn.net/m0_62476684/article/details/126001121