Java Thread系列(四)线程通信

Java Thread系列(四)线程通信

一、传统通信

public static void main(String[] args) {
    //volatile实现两个线程间数据可见性
    private volatile static List list = new ArrayList();

    Thread t1 = new Thread(new Runnable() { // (1)
        public void run() {
            try {
                for(int i = 0; i <10; i++){
                    list.add(i);
                    System.out.println(Thread.currentThread().getName() + "线程添加第" + (i + 1) + "个元素..");
                    Thread.sleep(500);
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }, "t1");

    Thread t2 = new Thread(new Runnable() { // (2)
        public void run() {
            while(true){
                if(list.size() == 5){
                    //do something
                    throw new RuntimeException(Thread.currentThread().getName() +
                        "线程接到通知 size = " + list.size() + " 线程停止..");
                }
            }
        }
    }, "t2");

    t1.start();
    t2.start();
}
  1. t1 线程不断将生产的数据放入 list 集合中
  2. t2 线程开启 while 循环监听 t1 线程,虽然可以实现 list.size()==5 时实时通知 t2 线程,但太浪费性能,考虑用 await/notify 提高性能,程序执行结果如下:
t1线程添加第1个元素..
t1线程添加第2个元素..
t1线程添加第3个元素..
t1线程添加第4个元素..
t1线程添加第5个元素..
Exception in thread "t2" java.lang.RuntimeException: t2线程接到通知 size = 5 线程停止..
    at com.github.binarylei.thread._2_1conn.ListAdvice1$2.run(ListAdvice1.java:35)
    at java.lang.Thread.run(Thread.java:745)
t1线程添加第6个元素..
t1线程添加第7个元素..
t1线程添加第8个元素..
t1线程添加第9个元素..
t1线程添加第10个元素..

二、wait/notify 实现通信

/**
 * 使用wait/notify方法实现线程单挑通信(注意这两个方法是Object类的方法)
 *   1. wait和notity必须配合synchronized关键字使用
 *   2. wait方法(关闭线程)释放锁,notify(唤醒线程)方法不释放锁
 * 缺点:通知不实时,使用CountDownLatch实现实时通知
 */
public static void main(String[] args) {
    private volatile static List list = new ArrayList();
    final Object lock = new Object();

    Thread t1 = new Thread(new Runnable() { // (1)
        public void run() {
            try {
                synchronized (lock) {
                    System.out.println("t1启动..");
                    for(int i = 0; i <10; i++){
                        list.add(i);
                        System.out.println(Thread.currentThread().getName() + "线程添加第" + (i + 1) + "个元素..");
                        Thread.sleep(500);
                        if(list.size() == 5){
                            System.out.println("已经发出通知..");
                            lock.notify();
                        }
                    }
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }, "t1");

    Thread t2 = new Thread(new Runnable() { // (2)
        public void run() {
            synchronized (lock) {
                System.out.println("t2启动..");
                if(list.size() != 5){
                    try {
                        lock.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                //do something
                throw new RuntimeException(Thread.currentThread().getName() +
                    "线程接到通知 size = " + list.size() + " 线程停止..");
            }
        }
    }, "t2");
}
  1. t1 线程当 list.size()==5lock.notify() 唤醒 t2 线程,注意 wait/notify 必须配合 synchronized 使用
  2. t2 线程调用 lock.wait() 后处于一直阻塞状态,直到 t1 线程调用 lock.notify() 唤醒该线程,倘若没有线程唤醒 t2 线程,那么 t2 线程就一直处于阻塞状态。本例中若 t1 线程先启动,那么 t2 线程调用 lock.wait() 就永远阻塞无法执行。程序执行结果如下:。
t2启动..
t1启动..
t1线程添加第1个元素..
t1线程添加第2个元素..
t1线程添加第3个元素..
t1线程添加第4个元素..
t1线程添加第5个元素..
已经发出通知..
t1线程添加第6个元素..
t1线程添加第7个元素..
t1线程添加第8个元素..
t1线程添加第9个元素..
t1线程添加第10个元素..
Exception in thread "t2" java.lang.RuntimeException: t2线程接到通知 size = 10 线程停止..
    at com.github.binarylei.thread._2_1conn.ListAdd2$2.run(ListAdd2.java:51)
    at java.lang.Thread.run(Thread.java:745)
  1. 由于 t1 线程 lock.notify() 后不会释放锁,t2 线程虽然被唤醒但不能获取锁,所以通知就不那么实时,只有等 t1 线程执行完成释放锁后 t2 线程才能获得锁执行相应操作,解决方案:使用 CountDownLatch

三、CountDownLatch 实现实时通信


public static void main(String[] args) {
    private volatile static List list = new ArrayList();
    final CountDownLatch countDownLatch = new CountDownLatch(1); // (1)

    Thread t1 = new Thread(new Runnable() {
        public void run() {
            try {
                System.out.println("t1启动..");
                for(int i = 0; i <10; i++){
                    list.add(i);
                    System.out.println(Thread.currentThread().getName() + "线程添加第" + (i + 1) + "个元素..");
                    Thread.sleep(500);
                    if(list.size() == 5){
                        System.out.println("已经发出通知..");
                        countDownLatch.countDown(); // (2)
                    }
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

        }
    }, "t1");

    Thread t2 = new Thread(new Runnable() {
        public void run() {
            System.out.println("t2启动..");
            if(list.size() != 5){
                try {
                    countDownLatch.await(); // (3)
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            //do something
            throw new RuntimeException(Thread.currentThread().getName() +
                "线程接到通知 size = " + list.size() + " 线程停止..");
        }
    }, "t2");

    t1.start();
    t2.start();
}
  1. CountDownLatch 同步工具类,允许一个或多个线程一直等待,直到其他线程的操作执行完后再执行,参数 1 表示需要等待的线程数量,具体来说就是参数为几就必须调用几次 countDownLatch.countDown()
  2. countDownLatch.countDown() 唤醒线程
  3. countDownLatch.await() 阻塞线程,程序执行结果如下:
t1启动..
t1线程添加第1个元素..
t2启动..
t1线程添加第2个元素..
t1线程添加第3个元素..
t1线程添加第4个元素..
t1线程添加第5个元素..
已经发出通知..
Exception in thread "t2" java.lang.RuntimeException: t2线程接到通知 size = 5 线程停止..
t1线程添加第6个元素..
    at com.github.binarylei.thread._2_1conn.ListAdd3$2.run(ListAdd3.java:47)
    at java.lang.Thread.run(Thread.java:745)
t1线程添加第7个元素..
t1线程添加第8个元素..
t1线程添加第9个元素..
t1线程添加第10个元素..

四、ThreadLocal

ThreadLocal 是线程局部变量,是一种多线程间并发访问变量的无锁解决方案。

ThreadLocal 和 synchronized 比较?

  1. 与 synchronized 等加锁的方式不同,ThreadLocal 完全不提供锁,而使用以空间换时间的手段,为每个线程提供变量的独立副本,以保障线程安全。
  2. 从性能上说,ThreadLocal 不具有绝对的优势,在并发不是很高的时候,加锁的性能会更好,但作为一套无锁的解决方案,在高并发量或者竞争激烈的场景,使用 ThreadLocal 可以在一定程度上减少锁竞争。
public static void main(String[] args) throws InterruptedException {
    final ThreadLocal<String> th = new ThreadLocal<String>();

    Thread t1 = new Thread(new Runnable() {
        public void run() {
            th.set("张三");
            System.out.println(th.get()); // => "张三"
        }
    }, "t1");

    Thread t2 = new Thread(new Runnable() {
        public void run() {
            try {
                Thread.sleep(1000);
                th.set("李四");
                System.out.println(th.get()); // => "李四"
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }, "t2");

    t1.start(); //t1:张三
    t2.start(); //t2:李四
}

五、自定义同步类窗口-Queue

Java 提供了一些同步类容器,它们是 线程安全 的,如 Vector、HashTable 等。这些同步类容器是由 Collections.synchronizedMap 等工厂方法去创建实现的,底层使用 synchronized 关键字,每次只有一个线程访问容器。下面实现一个自己的同步类窗口。

import java.util.LinkedList;

public class MyQueue {
    private LinkedList list = new LinkedList();
    private int max = 5;
    private int min = 1;
    private Object lock = new Object();

    public void put(Object obj) {  // (1)
        synchronized (lock) {
            while (list.size() == max) {
                try {
                    lock.wait();
                } catch (InterruptedException e) {
                    ;
                }
            }
            list.add(obj);
            lock.notify();
            System.out.println("put元素:" + obj);
        }
    }

    public Object take() {  // (2)
        Object obj;
        synchronized (lock) {
            while (list.size() == min) {
                try {
                    lock.wait();
                } catch (InterruptedException e) {
                    ;
                }
            }
            obj = list.removeFirst();
            lock.notify();
            System.out.println("take元素:" + obj);
        }
        return obj;
    }
}

测试

public static void main(String[] args) {
    final MyQueue myQueue = new MyQueue();
    myQueue.put("a");
    myQueue.put("b");
    myQueue.put("c");
    myQueue.put("d");
    myQueue.put("e");

    new Thread(new Runnable() {
        @Override
        public void run() {
            myQueue.put("f");
            myQueue.put("g");
            myQueue.put("h");
            myQueue.put("i");
        }
    }).start();

    new Thread(new Runnable() {
        @Override
        public void run() {
            myQueue.take();
            myQueue.take();
            myQueue.take();
        }
    }).start();
}


每天用心记录一点点。内容也许不重要,但习惯很重要!

原文地址:https://www.cnblogs.com/binarylei/p/8999690.html

时间: 2024-10-07 17:30:44

Java Thread系列(四)线程通信的相关文章

Java Thread系列(一)线程创建

Java Thread系列(一)线程创建 Java 中创建线程主要有三种方式:继承 Thread.实现 Runnable 接口.使用 ExecutorService.Callable.Future 实现由返回结果的多线程. 一.继承 Thread 类创建线程类 public class MyThread extends Thread { public void run() { for (int i = 0; i < 10000; i++) { System.out.println("线程一

Java Thread系列(三)线程安全

Java Thread系列(三)线程安全 一.什么是线程安全 线程安全概念:当多个线程访问某一个类(对象或方法)时,这个类始终都能表现出正确的行为,那么这个类(对象或方法)就是线程安全的. 线程安全来说,需要满足以下两个特性: 原子性 可见性 public class MyThread extends Thread { private int count = 5; //synchronized加锁 同步锁 public /*synchronized*/ void run () { System.

Java Thread系列(十)Future 模式

Java Thread系列(十)Future 模式 Future 模式适合在处理很耗时的业务逻辑时进行使用,可以有效的减少系统的响应时间,提高系统的吞吐量. 一.Future 模式核心思想 如下的请求调用过程时序图.当 call 请求发出时,需要很长的时间才能返回.左边的图需要一直等待,等返回数据后才能继续其他操作:而右边的 Future 模式的图中客户端则无需等到可以做其他的事情.服务器段接收到请求后立即返回结果给客户端,这个结果并不是真实的结果(是虚拟的结果),也就 是先获得一个假数据,然后

Java Thread系列(九)Master-Worker模式

Java Thread系列(九)Master-Worker模式 Master-Worker模式是常用的并行设计模式. 一.Master-Worker 模式核心思想 Master-Worker 系统由两个角色组成,Master 和 Worker,Master 负责接收和分配任务,Worker 负责处理子任务.任务处理过程中,Master 还负责监督任务进展和 Worker 的健康状态:Master 将接收 Client 提交的任务,并将任务的进展汇总反馈给 Client.各角色关系如下图: 二.M

Java Thread系列(五)synchronized

Java Thread系列(五)synchronized synchronized锁重入 关键字 synchronized 拥有锁重入的功能,也就是在使用 synchronized 时,当线程等到一个对象的锁后,再次请求此对象时可以再次得到该对象的锁.出现异常时释放锁. synchronized异常 synchronized代码块 使用 synchronized 声明的方法在某些情况下是有弊端的,比如A线程调用同步的方法执行一个很长时间的任务,那么B线程就必须等待比较长的时间才能执行,这样的情况

java多线程系列(四)

Lock的使用 前言:本系列将从零开始讲解java多线程相关的技术,内容参考于<java多线程核心技术>与<java并发编程实战>等相关资料,希望站在巨人的肩膀上,再通过我的理解能让知识更加简单易懂. 目录 认识cpu.核心与线程 java多线程系列(一)之java多线程技能 java多线程系列(二)之对象变量的并发访问 java多线程系列(三)之等待通知机制 java多线程系列(四)之ReentrantLock的使用 ReentrantLock(重入锁) public class

java并发系列(二)-----线程之间的协作(wait、notify、join、CountDownLatch、CyclicBarrier)

在java中,线程之间的切换是由操作系统说了算的,操作系统会给每个线程分配一个时间片,在时间片到期之后,线程让出cpu资源,由其他线程一起抢夺,那么如果开发想自己去在一定程度上(因为没办法100%控制它)让线程之间互相协作.通信,有哪些方式呢? wait.notify.notifyAll 1.void wait( ) 导致当前的线程等待,直到其他线程调用此对象的notify( ) 方法或 notifyAll( ) 方法 2.void wait(long timeout) 导致当前的线程等待,直到

Java入门系列-21-多线程

什么是线程 在操作系统中,一个应用程序的执行实例就是进程,进程有独立的内存空间和系统资源,在任务管理器中可以看到进程. 线程是CPU调度和分派的基本单位,也是进程中执行运算的最小单位,可完成一个独立的顺序控制流程,当然一个进程中可以有多个线程. 多线程:一个进程中同时运行了多个线程,每个线程用来完成不同的工作.多个线程交替占用CPU资源,并非真正的并行执行. 使用多线程能充分利用CPU的资源,简化编程模型,带来良好的用户体验. 一个进程启动后拥有一个主线程,主线程用于产生其他子线程,而且主线程必

Java多线程系列--“JUC线程池”05之 线程池原理(四)

拒绝策略介绍 线程池的拒绝策略,是指当任务添加到线程池中被拒绝,而采取的处理措施.当任务添加到线程池中之所以被拒绝,可能是由于:第一,线程池异常关闭.第二,任务数量超过线程池的最大限制. 线程池共包括4种拒绝策略,它们分别是:AbortPolicy, CallerRunsPolicy, DiscardOldestPolicy和DiscardPolicy. AbortPolicy -- 当任务添加到线程池中被拒绝时,它将抛出 RejectedExecutionException 异常. Calle