1. 线程状态图

 

wait: Object方法,当前线程执行wait将释放锁,进入等待池中,等待其他线程唤醒

sleep: Thread方法,当前线程只是休眠并不释放锁

yield: Thread方法,优先执行其他线程,不释放锁

join: Thread方法,是一种特殊wait。比如有两个线程t1、t2,当在t1中调用了t2.join,t1线程进入阻塞状态,直到t2执行完后,t1才继续运行

2. 多线程间通讯

线程一旦开始就会拥有自己独立的本地内存(主内存copy变量副本,线程操作都是在本地内存中进行),那么多个线程如何相互配合完成工作,这就涉及到线程间通讯

线程间通讯:多个线程在操作同一份数据时,互相告知自己的状态,避免对同一共享变量的争夺

线程通讯主要有三种方式:共享内存(主内存)、消息传递、管道流

2.1 volatile && synchronized关键字 && Lock --共享内存

synchronized实现线程间通讯是指多个线程通过synchronized关键字实现对共享变量加锁,哪个线程获取到锁哪个线程就执行;Lock也是同理

要注意volatile关键字不能保证原子性,所以对于非原子性操作不能实现线程间安全通讯

public class TestSync {
    //定义共享变量来实现通信,它需要volatile修饰,否则线程不能及时感知
    static volatile boolean notice = false;
 
    public static void main(String[] args) {
        List<String>  list = new ArrayList<>();
        //线程A
        Thread threadA = new Thread(() -> {
            for (int i = 1; i <= 10; i++) {
                list.add("abc");
                System.out.println("线程A添加元素,此时list的size为:" + list.size());
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                if (list.size() == 5)
                    notice = true;
            }
        });
        //线程B
        Thread threadB = new Thread(() -> {
            while (true) {
                if (notice) {
                    System.out.println("线程B收到通知,开始执行自己的业务...");
                    break;
                }
            }
        });
        //需要先启动线程B
        threadB.start();
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        // 再启动线程A
        threadA.start();
    }

 

2.2 wati notify -- 消息传递

2.2.1 基础理论

wait notify实现线程间通讯的理论是:一个线程修改了对象的值,另一个线程感知到了变化,然后进行相应操作,整个过程始于一个线程,终于另一个线程。wait-notify机制使用的是同一个对象锁

wait:将当前正在执行线程从调用出中断并且释放锁资源,直到收到notify/nofifyall后才从等待队列转入锁池队列。这时只是去竞争锁,但不一定成功

wait(long time):在经过time(ms)后没有收到notify/nofyall的通知,自动从等待队列转入锁池队列

notify:随机从等待队列(比如Thread A、Thread B都调用了wait方法,那么此时A B都进入等待队列,此时只有在另外线程C调用notify/notifyAll才会让A B解除wait状态重新参与竞争对象锁)中通知一个持有相同锁的一个线程

notifyAll:通知等待队列中持有相同锁的所有线程,让这些线程转入锁池队列

注意wait和wait(time)都要注意中断问题,wait中断是从语句处中断并且释放锁,当再次获取到锁时时从中断处继续向下执行

  notify和notifyAll方法通知是延迟通知,必须等待当前线程体执行完所有同步代码推出释放锁才通知wait线程

 

demo1: notify vs notifyAll

 

public class WaitDemo1 {
 
    private static class WaitTask implements Runnable {
 
        private Object lock;
 
        public WaitTask(Object lock) {
            this.lock = lock;
        }
 
        @Override
        public void run() {
            synchronized (lock) {
                System.out.println(Thread.currentThread().getName() + "准备进入等待状态");
                try {
                    // 调用wait之后线程进入waiting状态,等待被其他线程唤醒(lock.notify())
                    lock.wait();
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                // 被唤醒之后,线程从wait方法之后开始继续执行。
                System.out.println(Thread.currentThread().getName() + "等待结束,本线程继续执行");
            }
        }
    }
 
    private static class NotifyTask implements Runnable {
 
        private Object lock;
 
        public NotifyTask(Object lock) {
            this.lock = lock;
        }
 
        @Override
        public void run() {
            synchronized (lock) {
                System.out.println("准备唤醒");
                // synchronized修饰的代码块执行结束之后才会释放锁。
                lock.notifyAll(); // 是唤醒全部
//                lock.notify(); // 是随机唤醒一个
                System.out.println("唤醒结束");
            }
        }
    }
 
    public static void main(String[] args) throws InterruptedException {
        Object lock = new Object();
        Thread thread1 = new Thread(new WaitTask(lock),"thread1");
        Thread thread2 = new Thread(new WaitTask(lock),"thread2");
        Thread thread3 = new Thread(new WaitTask(lock),"thread3");
        Thread notify = new Thread(new NotifyTask(lock),"notify线程");
        thread1.start();
        thread2.start();
        thread3.start();
        Thread.sleep(100);
        notify.start();
    }
}
View Code

 notify只是随机唤醒某一个处于wait状态的等待线程;notifyAll是一次唤醒所有的等待线程

 

output:

thread1准备进入等待状态
thread3准备进入等待状态
thread2准备进入等待状态
准备唤醒
唤醒结束
thread2等待结束,本线程继续执行
thread3等待结束,本线程继续执行
thread1等待结束,本线程继续执行

如果将notifyAll改为notify则只会随机唤醒一个wait状态线程,此时output如下:

thread1准备进入等待状态
thread3准备进入等待状态
thread2准备进入等待状态
准备唤醒
唤醒结束
thread1等待结束,本线程继续执行

 

步骤:
1.t1拿到lock,其中 t3,t2 就进入了阻塞队列。

2.当调用 wait 方法,调用 wait 方法会释放锁,则 t3,t2 就会竞争锁,而 t1 进入等待队列。

3.此时 t3 拿到锁,t2还在阻塞队列。

4.调用 wait 方法,t3 也进入等待队列,释放锁,t2 拿到锁。

5.t2 在调用 wait 方法,这样 三个线程都进入了等待队列。

6.调用 notify方法,会同时唤醒3个线程从等待队列到阻塞队列,此时3个线程需要重新竞争锁。(synchronized 不是公平锁 )

7.t2 ,t3, t1 依次抢到锁资源,执行操作。

demo2: notify延迟通知

package com.threadconmmunication.watinotify;

import java.util.ArrayList;
import java.util.List;

public class TestSync {
    static volatile boolean notice = false;

    public static void main(String[] args) {
        //定义一个锁对象
        Object lock = new Object();
        List<String>  list = new ArrayList<>();
        // 线程A
        Thread threadA = new Thread(() -> {
            synchronized (lock) {
                for (int i = 1; i <= 10; i++) {
                    list.add("abc");
                    System.out.println("线程A添加元素,此时list的size为:" + list.size());
                    try {
                        Thread.sleep(500);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    if (list.size() == 5)
                        lock.notify();//唤醒B线程
                }
            }
        });
        //线程B
        Thread threadB = new Thread(() -> {
            while (true) {
                synchronized (lock) {
                    if (list.size() != 5) {
                        try {
                            lock.wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                    //threadB感知到threadA对list的变化,这里可以进行相应list的业务代码操作
            System.out.println(
"线程B收到通知,开始执行自己的业务..."); } } }); //需要先启动线程B threadB.start(); try { Thread.sleep(1000); //sleep 是为了保证线程B先启动 } catch (InterruptedException e) { e.printStackTrace(); } //再启动线程A threadA.start(); } }

 

输出:

线程A添加元素,此时list的size为:1
线程A添加元素,此时list的size为:2
线程A添加元素,此时list的size为:3
线程A添加元素,此时list的size为:4
线程A添加元素,此时list的size为:5
线程A添加元素,此时list的size为:6
线程A添加元素,此时list的size为:7
线程A添加元素,此时list的size为:8
线程A添加元素,此时list的size为:9
线程A添加元素,此时list的size为:10
线程B收到通知,开始执行自己的业务...

 

如上红色标记代码在list.size()=5时调用notify(),但从结果看知道list()=10之后才重新执行线程B wait之后的代码,这说明notify/notifyAll是延迟通知,只有当前线程所有代码执行完成后才释放锁

2.2.2 非法监视异常

wait notify必须和在synchronized代码块中,否则出现非法监视异常(illegalMonitorStateException)

原因:以如下采用wait() notify()实现阻塞队列为例,如果wait() notify不强制要求加锁,当thread1运行到wait()之前,由于时间片轮转,此时cpu调度到thread2运行,然后执行notify().当再交由thread1运行时,thread1 运行了wait,然后将永远无法醒过来。所以在使用wait notify时,他们必须在synchronized中竞争同一个对象锁(或者类锁)

class MyBlockingQueue {
    // 用来保存任务的集合
    Queue<String> queue = new LinkedList<>();
 
    /**
     * 添加方法
     */
    public void put(String data) {
        synchronized (MyBlockingQueue.class) {
            // 队列加入数据
            queue.add(data);
            // 为了防止 take 方法阻塞休眠,这里需要调用唤醒方法 notify
            notify(); //
        }
    }
 
    /**
     * 获取方法(阻塞式执行)
     * 如果队列里面有数据则返回数据,如果没有数据就阻塞等待数据
     * @return
     */
    public String take() throws InterruptedException {
        synchronized (MyBlockingQueue.class) {
            // 使用 while 判断是否有数据(这里使用 while 而非 if 是为了防止虚假唤醒)
            while (queue.isEmpty()) {  //// 没有任务,先阻塞等待
                wait(); //
            }
        }
        return queue.remove(); // 返回数据
    }
}

 

 

 2.3 java.util.concurrent包下CountDownLatch

和wait-notify机制比较类似

创建一个计数器,设置初始值 CountdownLatch countDownLatch = new CountDownLatch(2);
线程调用 countDownLatch.await() 方法时,会将该线程置于同步队列阻塞等待(本质是获取共享锁)
当其他线程调用 countDownLatch.countDown() 方法时,会将计数值不断递减,直到为 0 时,此时才会唤醒因调用 countDownLatch.await() 的而在同步队列中的线程,去执行自己的任务

关于countdownLatch具体参考 https://www.cnblogs.com/enhance/p/16529543.html

 

3. 线程安全

 

 

4. 多线程共享变量控制

线程范围内的共享变量是指对同一个变量,几个线程同时对它进行写和读操作,而同一个线程读到的数据就是它自己写进去的数据

 

4.1 synchronized

4.2 volatile

4.3 ReentrantLock

 具体参考: https://www.cnblogs.com/enhance/p/11265132.html

4.4 Atomic原子类

 

 参考文献

https://blog.csdn.net/u012811805/article/details/126047153

https://blog.csdn.net/weixin_43808717/article/details/115351888

https://blog.csdn.net/sufu1065/article/details/123102819

posted on 2023-01-11 08:36  colorfulworld  阅读(492)  评论(0编辑  收藏  举报