java多线程学习记录--线程间通信(三)

线程间通信:

wait、notify、notifyAll

使用场景:在多线程环境下,有时候一个线程 的执行,依赖于另外一个线程的某种状态的改变,这个时候就可以使用wait与notify或者notifyAll

wait和sleep的区别:
wait会释放持有的锁,而sleep不会,sleep只是让线程在指定的时间内不去抢占CPU资源。

注意:
wait和notify必须放在同步代码块中,切必须拥有当前对象的锁,即不能取得A对象的锁而调用B对象的wait,哪个对象wait就要调用哪个对象的notify。

notify和notifyAll的区别:
notify随机唤醒一个等待的线程。
notifyAll唤醒所有在该对象上等待的线程。

等待通知经典模型 -生产者消费者
在这里插入图片描述
中间容器:

public class Container {

    private int num = 0;
    private static final int TOTAL = 20;

    public synchronized void produc(){
        //如果容器没满则生产并通知消费者消费
        //如果满了则生产者等待
        if (num < TOTAL){
            System.out.println("生产者生产了。。当前还有" + ++num);
            notifyAll();
        }
        else {
            try {
                System.out.println("库存已满" + num);
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
    public synchronized void consume(){
        //若容器不空则消费并通知生产者生产,若空则等待
        if (num > 0){
            System.out.println("消费者消费了。。当前还有" + --num);
            notifyAll();
        }
        else {
            try {
                System.out.println("库存为空" + num);
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

生产者:

public class Producer implements Runnable {
    private Container container;
    public Producer(Container container){
        this.container = container;
    }
    @Override
    public void run() {
        while (true){
            try {
                Thread.sleep(200);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            container.produc();
        }
    }
}

消费者:

public class Consumer implements Runnable{
    private Container container;
    public Consumer(Container container){
        this.container = container;
    }
    @Override
    public void run() {
        while (true){
            try {
                Thread.sleep(200);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            container.consume();
        }
    }
}

main函数:

public class Main {
    public static void main(String[] args) {
        Container container = new Container();

        new Thread(new Producer(container)).start();
        new Thread(new Producer(container)).start();
        new Thread(new Producer(container)).start();
        new Thread(new Producer(container)).start();

        new Thread(new Consumer(container)).start();
        new Thread(new Consumer(container)).start();
    }
}

结果:

生产者生产了。。当前还有1
消费者消费了。。当前还有0
生产者生产了。。当前还有1
生产者生产了。。当前还有2
生产者生产了。。当前还有3
消费者消费了。。当前还有2
生产者生产了。。当前还有3
消费者消费了。。当前还有2
生产者生产了。。当前还有3
生产者生产了。。当前还有4
生产者生产了。。当前还有5
消费者消费了。。当前还有4

使用管道流进行通信:
以内存为媒介用于线程之间的数据传输。
主要有面向字节:PipedOutputStream、PipedInputStream
面向字符:PipedReader、PipedWriter

实例:
Reader.java

public class Reader implements Runnable {
    private PipedInputStream pipedInputStream;
    public Reader(PipedInputStream pipedInputStream){
        this.pipedInputStream = pipedInputStream;
    }

    @Override
    public void run() {
        if (pipedInputStream != null){
            System.out.println("进入run");
            String collect =  new BufferedReader(new InputStreamReader(pipedInputStream))
                    .lines().collect(Collectors.joining("\n"));
            System.out.println(collect);
        }
        try {
            pipedInputStream.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

main.java

public class main {
    public static void main(String[] args) throws IOException {
        PipedInputStream pipedInputStream = new PipedInputStream();
        PipedOutputStream pipedOutputStream = new PipedOutputStream();

        pipedOutputStream.connect(pipedInputStream);
        new Thread(new Reader(pipedInputStream)).start();
        BufferedReader bufferedReader = null;
        try{
            bufferedReader = new BufferedReader(new InputStreamReader(System.in));
            pipedOutputStream .write(bufferedReader.readLine().getBytes());
        }finally {
            pipedOutputStream.close();
            if(bufferedReader != null){
                bufferedReader.close();
            }
        }

    }
}

Thread.join通信:

使用场景:
线程A执行到一半,需要一个数据,这个数据需要线程B去执行修改,只有B修改完成之后A才能继续操作。
线程A的run方法里面,调用线程B的join方法,这时线程A会等待线程B运行完成之后再接着运行。

public class Testjoin {
    public static void main(String[] args) {
        Thread thread = new Thread(()->{
            System.out.println(Thread.currentThread().getName() + "运行了");
            try {
                Thread.sleep(2000L);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + "运行结束了");
        }, "线程1");

        new Thread(()->{
            System.out.println(Thread.currentThread().getName() + "运行了");
            thread.start();
            try {
                thread.join();   //等待线程1运行结束
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + "运行结束了");
        },"线程2").start();
    }
}

结果:

线程2运行了
线程1运行了
线程1运行结束了
线程2运行结束了

ThreadLocal的使用:

ThreadLocal 用一种存储变量与线程绑定的方式,在每个线程中用自己的 ThreadLocalMap 安全隔离变量,为解决多线程程序的并发问题提供了一种新的思路,如为每个线程创建一个独立的数据库连接。因为是线程绑定的,所以在很多场景也被用来实现线程参数传递,如 Spring 的 RequestContextHolder。也因为每个线程拥有自己唯一的 ThreadLocalMap ,所以 ThreadLocalMap 是天然线程安全的。
常用方法:
ThreadLocal.get:获取ThreadLocal当前线程共享变量的值。
ThreadLocal.set:设置ThreadLocal中当前线程共享变量的值。
ThreadLocal.remove:移除ThreadLocal中当前线程共享变量的值。
ThreadLocal.initialValue:原始值。

实例:

public class Testthreadlocal {
   ThreadLocal <Integer> num =  ThreadLocal.withInitial(() -> 0);//初始化为0

   public void inCrease(){
       Integer myNum = num.get();
       myNum++;
       System.out.println(Thread.currentThread().getName() + "-------" + myNum);
       num.set(myNum);
   }

    public static void main(String[] args) {
        Testthreadlocal testthreadlocal = new Testthreadlocal();
        for (int i = 1;i < 3; i++){
            int j = i;
            new Thread(()->{
                while (true){
                    testthreadlocal.inCrease();
                    try {
                        Thread.sleep(j*1000L);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }).start();
        }
    }
}

结果证明ThreadLocal是与线程绑定的存储:

Thread-1-------1
Thread-0-------1
Thread-0-------2
Thread-1-------2
Thread-0-------3
Thread-0-------4
Thread-1-------3
Thread-0-------5

详见博客:https://blog.csdn.net/sdfgtr/article/details/90032912

Condition的使用:

Condition 将 Object 监视器方法(wait、notify 和 notifyAll)分解成截然不同的对象,以便通过将这些对象与任意 Lock 实现组合使用,为每个对象提供多个等待 set(wait-set)。其中,Lock 替代了 synchronized 方法和语句的使用,Condition 替代了 Object 监视器方法的使用。

await()方法会使当前线程等待,同时释放当前锁,当其他线程中使用signal()时或者signalAll()方法时,线程会重新获得锁并继续执行。或者当线程被中断时,也能跳出等待。这和Object.wait()方法很相似。

awaitUninterruptibly()方法与await()方法基本相同,但awaitUninterruptibly()方法不会在等待过程中响应中断。

singal()方法用于唤醒一个在等待中的线程。相对的singalAll()方法会唤醒所有在等待中的线程。这和Obejct.notify()方法类似。

condition.await()方法必须在lock.lock()与lock.unlock()方法之间调用。

实例:缓冲队列的实现

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

class BoundedBuffer {
    final Lock lock = new ReentrantLock();// 锁对象
    final Condition notFull = lock.newCondition(); //写线程条件
    final Condition notEmpty = lock.newCondition();//读线程条件
    final Object[] items = new Object[100];// 初始化一个长度为100的队列
    int putptr/* 写索引 */, takeptr/* 读索引 */, count/* 队列中存在的数据个数 */;

    public void put(Object x) throws InterruptedException {
        lock.lock(); //获取锁
        try {
            while (count == items.length)
                notFull.await();// 当计数器count等于队列的长度时,不能再插入,因此等待。阻塞写线程。
            items[putptr] = x;//赋值
            putptr++;

            if (putptr == items.length)
                putptr = 0;// 若写索引写到队列的最后一个位置了,将putptr置为0。
            count++; // 每放入一个对象就将计数器加1。
            notEmpty.signal(); // 一旦插入就唤醒取数据线程。
        } finally {
            lock.unlock(); // 最后释放锁
        }
    }

    public Object take() throws InterruptedException {
        lock.lock(); // 获取锁
        try {
            while (count == 0)
                notEmpty.await(); // 如果计数器等于0则等待,即阻塞读线程。
            Object x = items[takeptr]; // 取值
            takeptr++;
            if (takeptr == items.length)
                takeptr = 0; //若读锁应读到了队列的最后一个位置了,则读锁应置为0;即当takeptr达到队列长度时,从零开始取
            count++; // 每取一个将计数器减1。
            notFull.signal(); //枚取走一个就唤醒存线程。
            return x;
        } finally {
            lock.unlock();// 释放锁
        }
    }

}

此即Condition的强大之处,假设缓存队列中已经存满,那么阻塞的肯定是写线程,唤醒的肯定是读线程,相反,阻塞的肯定是读线程,唤醒的肯定是写线程,那么假设只有一个Condition会有什么效果呢,缓存队列中已经存满,这个Lock不知道唤醒的是读线程还是写线程了,如果唤醒的是读线程,皆大欢喜,如果唤醒的是写线程,那么线程刚被唤醒,又被阻塞了,这时又去唤醒,这样就浪费了很多时间。

例:经典问题:三个线程依次打印ABC,代码示例如下:

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

class Business {
    private Lock lock = new ReentrantLock();
    private Condition conditionA = lock.newCondition();
    private Condition conditionB = lock.newCondition();
    private Condition conditionC = lock.newCondition();
    private String type = "A"; //内部状态

    /*
     * 方法的基本要求为:
     * 1、该方法必须为原子的。
     * 2、当前状态必须满足条件。若不满足,则等待;满足,则执行业务代码。
     * 3、业务执行完毕后,修改状态,并唤醒指定条件下的线程。
     */
    public void printA() {
        lock.lock(); //锁,保证了线程安全。
        try {
            while (type != "A") { //type不为A,
                try {
                    conditionA.await(); //将当前线程阻塞于conditionA对象上,将被阻塞。
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }

            //type为A,则执行。
            System.out.println(Thread.currentThread().getName() + " 正在打印A");
            type = "B"; //将type设置为B。
            conditionB.signal(); //唤醒在等待conditionB对象上的一个线程。将信号传递出去。
        } finally {
            lock.unlock(); //解锁
        }
    }

    public void printB() {
        lock.lock(); //锁
        try {
            while (type != "B") { //type不为B,
                try {
                    conditionB.await(); //将当前线程阻塞于conditionB对象上,将被阻塞。
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }

            //type为B,则执行。
            System.out.println(Thread.currentThread().getName() + " 正在打印B");
            type = "C"; //将type设置为C。
            conditionC.signal(); //唤醒在等待conditionC对象上的一个线程。将信号传递出去。
        } finally {
            lock.unlock(); //解锁
        }
    }

    public void printC() {
        lock.lock(); //锁
        try {
            while (type != "C") {
                try {
                    conditionC.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }

            System.out.println(Thread.currentThread().getName() + " 正在打印C");
            type = "A";
            conditionA.signal();
        } finally {
            lock.unlock(); //解锁
        }
    }
}


public class Test{

    public static void main(String[] args) {
        final Business business = new Business();//业务对象。

        //线程1号,打印10次A。
        Thread ta = new Thread(new Runnable() {

            @Override
            public void run() {
                for(int i=0;i<10;i++){
                    business.printA();
                }
            }
        });

        //线程2号,打印10次B。
        Thread tb = new Thread(new Runnable() {

            @Override
            public void run() {
                for(int i=0;i<10;i++){
                    business.printB();
                }
            }
        });

        //线程3号,打印10次C。
        Thread tc = new Thread(new Runnable() {

            @Override
            public void run() {
                for(int i=0;i<10;i++){
                    business.printC();
                }
            }
        });

        //执行3条线程。
        ta.start();
        tb.start();
        tc.start();
    }

}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值