博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Java线程间的通信
阅读量:397 次
发布时间:2019-03-05

本文共 12475 字,大约阅读时间需要 41 分钟。

当需要多个线程之间相互协作的时候,就需要用到Java线程的通信方法。

Java中,锁的概念都是基于对象的,所以又称为对象锁。

1. 锁与同步

package chapter7;public class ObjectLock {    // 类属性只有一个[不管创建多少个实例],保证同一个锁    private static Object lock = new Object();     // 可尝试去掉static怎么保证线程安全    static class ThreadA implements Runnable{        @Override        public void run() {            synchronized (lock){                // 查看锁的内存地址                System.out.println(lock);                for (int i = 0; i < 100; i ++){                    System.out.println("Thread A " + i);                }            }        }    }    static class ThreadB implements Runnable{        @Override        public void run() {            synchronized (lock){                // 查看锁的内存地址                System.out.println(lock);                for (int i = 0; i < 100; i ++){                    System.out.println("Thread B " + i);                }            }        }    }    public static void main(String[] args) throws InterruptedException{        new Thread(new ThreadA()).start();        Thread.sleep(10); // 让threadA先执行        new Thread(new ThreadB()).start();    }}

在ThreadA 和ThreadB内需要同步的代码块里,都是用synchronized关键字加上一个对象锁lock;

2. 等待/通知机制(wait()、notify()和notifyAll())

基于锁的方式,线程需要不断地尝试获得锁,如果失败,会一直尝试。

Java有一个内建的等待机制(等待/通知机制)来允许线程在等待信号的时候变为非运行状态。java.lang.Object类定义了三个方法,wait()、notify()和notifyAll()来实现这个机制。

// Object中Java本地方法// 随机叫醒一个正在等待的线程public final native void notify();// 会叫醒所有正在等待的线程public final native void notifyAll();// 调用该方法的线程进入WAITING状态,释放对象的锁,一般用在同步方法或者同步代码块中public final native void wait(long timeout) throws InterruptedException;

具体解析移步:

 

package chapter7;public class WaitAndNotify {    // 保证多个线程使用同一个对象锁,如果多个线程使用的是不同的锁,那么    // 它们之间是不能使用等待/通知机制的。    private static Object lock = new Object(); // 对象锁    static class ThreadA implements Runnable{        @Override        public void run() {            synchronized (lock){                System.out.println(lock);                for (int i = 0; i < 5; i ++){                    try{                        System.out.println("ThreadA: " + i);                        lock.notify(); // 唤醒其它等待这个对象锁的线程                        lock.wait(); // 释放资源                    } catch (InterruptedException e){                        e.printStackTrace();                    }                }                // 当退出for循环时,唤醒了另外一个线程,该线程等待                // 这将导致两个线程都会陷入等待                // 没有它,主线程无法结束                lock.notify();            }        }    }    static class ThreadB implements Runnable{        @Override        public void run() {            synchronized (lock){                System.out.println(lock);                for (int i = 0; i < 5; i ++){                    try{                        System.out.println("ThreadB: " + i);                        lock.notify(); // 唤醒其它等待这个对象锁的线程                        lock.wait(); // 让出资源                    } catch (InterruptedException e){                        e.printStackTrace();                    }                }                lock.notify();            }        }    }    public static void main(String[] args) throws InterruptedException{        new Thread(new ThreadA()).start();        Thread.sleep(10);        new Thread(new ThreadB()).start();    }}

3.信号量

notify()和notifyAll()方法不会保存调用它们的方法,通知信号过后便丢弃了。在某些情况下,这可能使等待线程永远在等待,不再醒来,因为线程错过了唤醒信号。为了避免这个情况,用一个变量来保存是否被通知过。在notify前,设置自己已经被通知过了。在wait后,设置自己没有被通知过,需要等待通知。

public class MyWaitNotify2{    MonitorObject myMonitorObject = new MonitorObject();        boolean wasSignalled = false;        public void doWait(){        synchronized(myMonitorObject){            // 在wait与wait之间没有收到notify,那么只能调用wait            if (!wasSignalled){                try(    myMonitorObject.wait();) catch(InterruptedException e){ ...}            }                        wasSignalled = false;         }    }        public void doNotify(){        synchronized(myMonitorObject){            wasSignalled = true;            myMonitorObject.notify();        }    }}

volatile关键字可以保证指令不被重排序和内存的可见性。

public class MySignal {    private static volatile int signal = 0;    static class ThreadA implements Runnable{        @Override        public void run() {            while(signal < 5){                if (signal % 2 == 0){                    System.out.println("ThreadA: " + signal);                    synchronized (this){                        signal ++ ; // signal并不是原子操作                    }                }            }        }    }    static class ThreadB implements Runnable{        @Override        public void run() {            while(signal < 5){                if (signal % 2 == 1){                    System.out.println("ThreadB: " + signal);                    synchronized (this){                        signal ++ ;                    }                }            }        }    }    public static void main(String[] args) throws InterruptedException{        new Thread(new ThreadA()).start();        Thread.sleep(100);        new Thread(new ThreadB()).start();    }}

信号量

JDK提供了一个类似于“信号量”功能的类Semaphore。接下来介绍一种基于volatile关键字的自己实现的信号量通信。

volitile关键字能够保存内存的可见性,如果用volitile关键字声明了一个变量,在一个线程里面改变了这个变量的值,那其他线程立马可见更改后的值。

示例:我想让线程A输出0,然后线程B输出1,在然后A输出2...以此类推

 

package chapter7;/** *  由于signal++与signal = signal + 1并不是原子操作,所以需要加锁。 * 〈信号量〉 * * @author 我们 * @create 2021/1/26 * @since 1.0.0 */public class Signal {    private static volatile int signal = 0;    private static final Object lock = new Object();    static class ThreadA implements Runnable{        @Override        public void run() {            while(signal < 5){                if (signal % 2 == 0){                    System.out.println("lock :" + lock + ", " + Thread.currentThread().getName() + " : " + signal);                    synchronized (lock){                        signal ++;                    }                }            }        }    }    static class ThreadB implements Runnable{        @Override        public void run() {            while(signal < 5){                if (signal % 2 == 1){                    System.out.println("lock :" + lock + ", " + Thread.currentThread().getName() + " : " + signal);                    synchronized (lock){                        signal = signal + 1;                    }                }            }        }    }    public static void main(String[] args) throws InterruptedException {        new Thread(new ThreadA()).start();        Thread.sleep(12);        new Thread(new ThreadB()).start();    }}

 管道

管道是基于“管道流”的通信方式。JDK提供了PipedWriter、PipedReader、pipedOutputStream、PipedInputStream。其中,前面两个是基于字符的,后面两个是基于字节流的。

import java.io.IOException;import java.io.PipedReader;import java.io.PipedWriter;/** * 代码执行流程: *      1.线程ReaderThread开始执行 *      2.线程ReaderThread使用管道reader.read()进入阻塞 *      3.线程WriterThread开始执行 *      4.线程WriterThread用writer.write("test")往管道写入字符串 *      5.线程ReaderThread接受到管道输出的字符串并打印 *      6.线程ReaderThread执行完毕 */public class Pipe {    static class ReaderThread implements Runnable{        private PipedReader reader;        public ReaderThread(PipedReader reader){            this.reader = reader;        }        @Override        public void run() {            System.out.println("this is reader");            int receive = 0;            try{                while((receive = reader.read()) != -1){                    System.out.print((char) receive);                }            } catch (IOException e){                e.printStackTrace();            }        }    }    static class WriterThread implements Runnable{        private PipedWriter writer;        public WriterThread(PipedWriter writer){            this.writer = writer;        }        @Override        public void run() {            System.out.println("this is writer");            int receive = 0;            try{                writer.write("test");            } catch (IOException e){                e.printStackTrace();            } finally {                try{                    writer.close();                } catch (IOException e){                    e.printStackTrace();                }            }        }    }    public static void main(String[] args) throws IOException, InterruptedException{                PipedWriter writer = new PipedWriter();        PipedReader reader = new PipedReader();        writer.connect(reader);        new Thread(new ReaderThread(reader)).start();        Thread.sleep(10);        new Thread(new WriterThread(writer)).start();    }}/* output~:    this is reader    this is writer    test*/

管道通信的应用场景:

使用管道多半与I/O流相关。当我们一个线程需要另一个线程先发送一个信息(比如字符串)或者文件时,就需要使用管道通信。

join方法:

join方法是Thread类的一个实例方法。它的作用是让当前线程陷入“等待”状态,等join的这个线程执行完毕后,再继续执行当前线程。

有时候,主线程创建并启动了子线程,如果子线程中需要大量的耗时运算,主要线程往往将早于子线程结束之前结束。

如果主线程想等待子线程执行完毕之后,获得子线程中的处理完的某个数据,就要用到join方法。

public class Join {    static class ThreadA implements Runnable{        @Override        public void run() {            try{                System.out.println("我是子线程,我先睡一秒");                Thread.sleep(1000);                System.out.println("我是子线程,我睡了完了一秒");            } catch (InterruptedException e){                e.printStackTrace();            }        }    }    public static void main(String[] args) throws InterruptedException{        Thread thread = new Thread(new ThreadA());        thread.start();        thread.join();        System.out.println("如果不加join方法,我会先被打出来,加了就不一样了");    }}/*output~:不加thread.join():    如果不加join方法,我会先被打出来,加了就不一样了    我是子线程,我先睡一秒    我是子线程,我睡了完了一秒加thread.join():    我是子线程,我先睡一秒    我是子线程,我睡了完了一秒    如果不加join方法,我会先被打出来,加了就不一样了 */

join方法有两个重载方法,一个是join(long),一个是join(long, int)。实际上,通过源码发现,join()方法及其重载方法底层都是利用了wait(long)这个方法。对于join(long,int),通过查看源码(JDK 1.8)发现,底层并没有精确到纳秒,而是对第二个参数做了简答的判断和处理。

public final synchronized void join(long millis) throws InterruptedException {        long base = System.currentTimeMillis();        long now = 0;        if (millis < 0) {            throw new IllegalArgumentException("timeout value is negative");        }        if (millis == 0) {            while (isAlive()) {                wait(0);            }        } else {            while (isAlive()) {                long delay = millis - now;                if (delay <= 0) {                    break;                }                wait(delay);                now = System.currentTimeMillis() - base;            }        }    }

sleep方法

sleep方法是Thread类的一个静态方法。它的作用是让当前线程随眠一段时间,它有这样两个方法:

  • Thread.sleep(long)
  • Thread.sleep(long,int)

同样,查看源码(JDK 1.8)发现,第二个方法貌似只对第二个参数做了简单处理,没有精确到纳秒。实际上还是调用的第一个方法。

sleep方法是不会释放当前的锁的,而wait方法会。

wait与sleep方法的区别:

  • wait可以指定时间,也可以不指定;而sleep必须指定时间
  • wait释放cpu资源,同时释放锁;sleep释放cpu资源,但是不释放锁,所以易死锁
  • wait必须放在同步块或同步方法中,而sleep可以在任意位置

ThreadLocal类

ThreadLocal是一个本地线程副本变量类。内部是一个弱引用的Map来维护。这里不详细介绍它的原理,而是只是介绍它的使用,以后有独立章节来介绍ThreadLocal类的原理。

ThreadLocal为线程本地变量线程本地存储。严格来说,ThreadLocal类并不属于多线程间的通信,而是让每个线程有自己独立的变量,线程之间互不影响,它为每个线程都创建一个副本,每个线程都可以访问自己的内部副本变量。

package chapter7;public class ThreadLocalDemo {    static class ThreadA implements Runnable{        private  ThreadLocal
threadLocal; public ThreadA(ThreadLocal
threadLocal) { this.threadLocal = threadLocal; } @Override public void run() { threadLocal.set("A"); try{ Thread.sleep(1000); } catch (InterruptedException e){ e.printStackTrace(); } System.out.println("ThreadA out: " + threadLocal + "->content:" + threadLocal.get()); } } static class ThreadB implements Runnable{ private ThreadLocal
threadLocal; public ThreadB(ThreadLocal
threadLocal) { this.threadLocal = threadLocal; } @Override public void run() { threadLocal.set("B"); try{ Thread.sleep(1000); } catch (InterruptedException e){ e.printStackTrace(); } System.out.println("ThreadB out: " + threadLocal + "->content:" + threadLocal.get()); } } public static void main(String[] args) { ThreadLocal
threadLocal = new ThreadLocal<>(); new Thread(new ThreadA(threadLocal)).start(); new Thread(new ThreadB(threadLocal)).start(); }}/*output~:ThreadA out: java.lang.ThreadLocal@7e92ee0->content:AThreadB out: java.lang.ThreadLocal@7e92ee0->content:B */

ThreadLocal可以将某个静态变量(user ID或者ID)与线程状态关联,则可以使用ThreadLocal。常见的ThreadLocal使用场景为用来解决数据库连接、Session管理。这些都涉及多个复杂对象的初始化和关闭。如果在每个线程中声明一些私有变量来操作,那么这个线程就变得那么轻量了。

InheritableThreadLocal类与ThreadLocal类稍有不同,Inheritable是可继承的意思,它的子线程可以存取这个副本值。

转载地址:http://luczz.baihongyu.com/

你可能感兴趣的文章