首页 > 代码库 > Java多线程编程核心技术读书笔记(3)-线程通信
Java多线程编程核心技术读书笔记(3)-线程通信
线程是操作系统中独立的个体,但是这些个体如果无法经过特殊的处理就不能成为一个整体。线程间通信可以实现线程间的信息互换、相互唤起等功能,是系统的交互性更加强大,大大提高CPU的利用率,同时还能让开发者对各个线程任务有清晰的把控和监督,最常用的线程通信方法就是——等待/通知机制。
一、等待/通知机制
1、wait() / notify()
等待/通知机制在生活中比比皆是,例如:厨师/服务员的菜品传递台、生产者/消费者模式,JDK中通过Object里面的两个方法 wait() / notify() 来实现了等待/通知机制;
方法wait()的作用是使当前执行代码的线程进行等待,将其置入“预执行队列”,并且在wait()所在代码处停止执行,马上放开所持有的锁,直到接到通知或被中断为止。wait()方法必须置于同步方法或代码块内,否者运行时会抛出IllegalMonitorStateException;
方法wait(long)是等待某一段时间,如果超过这个时间则线程自动唤醒;
方法notify()也需要置于同步方法或代码块内执行,否者运行时会抛出IllegalMonitorStateException,该方法用于通知那些可能等待该对象所持的锁的其他线程,如果有多个等待线程,则随机挑选一个呈wait状态的线程,使其获得该对象的锁,但是并不会马上释放所持的锁,而是等到该线程执行完同步方法或者同步代码块之后,才会释放锁;
方法notifyAll()作用和notify()是一样的,只不过它会唤醒所有等待该资源的等待线程,进入到可运行状态,具体由哪个线程获得锁,取决于JVM实现,可能是优先级最高的或者随机;
用wait() / notify()实现 生产者/消费者模式(用操作栈存储共享资源):
操作栈实现:MyStack.java
public class MyStack { private List list = new ArrayList<>(); synchronized public void push() { try { //为防止多生产者而导致list.size()>1的情况,不应该使用if,而是while,下同 while(list.size() == 1) { System.out.println("push操作中的:" + Thread.currentThread().getName() + " 线程呈wait状态"); this.wait(); } list.add("antString=" + Math.random()); //为防止多生产者时,唤醒的是同类不是异类而导致的假死,应该用notifyAll(),下同 this.notifyAll(); System.out.println("push=" + list.size()); } catch (InterruptedException e) { e.printStackTrace(); } } synchronized public String pop() { String temp = ""; try { while(list.size() == 0) { System.out.println("pop操作中的:" + Thread.currentThread().getName() + " 线程呈wait状态"); this.wait(); } temp = "" + list.get(0); list.remove(0); this.notifyAll(); System.out.println("pop=" + list.size()); } catch (InterruptedException e) { e.printStackTrace(); } return temp; } }
生产者实现:Producer.java
public class Producer { private MyStack myStack; public Producer(MyStack myStack) { super(); this.myStack = myStack; } public void pushService() { myStack.push(); } }
生产者线程实现:ProducerThread.java
public class ProducerThread extends Thread { private Producer producer; public ProducerThread(Producer producer) { super(); this.producer = producer; } @Override public void run() { while(true) { producer.pushService(); } } }
消费者实现:Customer.java
public class Customer { private MyStack myStack; public Customer(MyStack myStack) { super(); this.myStack = myStack; } public void popService() { myStack.pop(); } }
消费者线程实现:CustomerThread.java
public class CustomerThread extends Thread { private Customer customer; public CustomerThread(Customer customer) { super(); this.customer = customer; } @Override public void run() { while(true) { customer.popService(); } } }
运行类:Run.java
//单生产者-单消费者 public class Run { public static void main(String[] args) { MyStack myStack = new MyStack(); Producer producer = new Producer(myStack); Customer customer = new Customer(myStack); ProducerThread producerThread1 = new ProducerThread(producer); CustomerThread customerThread1 = new CustomerThread(customer); producerThread1.start(); customerThread1.start(); } }
2、管道通信
Java中提供了各种各样的输入/输出流Stream,其中管道流(PipeStream)是一种特殊的流,可以用于在不同的线程间直接传送数据。
在Java的JDK中提供了两种流-4个类来使线程间可以进行通信:
1、字节流(PipedInputStream 和 PipedOutputStream)
2、字符流(PipedReader 和 PipedWriter)
代码Demo暂略。。。
二、join()的使用
1、join()
很多情况,主线程创建并启动子线程,如果子线程执行时间很长,主线程会早于子线程结束。如果想让主线程等待子线程结束后再结束,就可以用到join()方法了。
示例代码如下:Test.java
class MyThread3 extends Thread { @Override public void run() { try { System.out.println(System.currentTimeMillis() + " 我是子线程,我需要sleep一段时间!"); Thread.sleep(5000); } catch (InterruptedException e) { e.printStackTrace(); } } } public class Test { public static void main(String[] args) { try{ MyThread3 myThread = new MyThread3(); myThread.start(); myThread.join(); System.out.println(System.currentTimeMillis() + " 我是主线程,在子线程结束后才结束的!"); } catch (InterruptedException e) { e.printStackTrace(); } } }
输出:
1493215918686 我是子线程,我需要sleep一段时间!
1493215923705 我是主线程,在子线程结束后才结束的!
可以看出主线程确实是等待子线程sleep()了5s后,才结束的!
2、join(long) 和 sleep(long) 的区别
方法join(long)的功能是指当前线程至少等待该线程多久,如果该线程在时间前结束,那该方法和sleep气的作用是一样的。但是方法join(long)的功能在内部是使用wait(long)方法来实现的,所以join(long)方法具有释放锁的特点,可以看下join(long)的实现代码:
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; } } }
三、ThreadLocal / InheritableThreadLocal的使用
1、ThreadLocal
静态变量可以让所有线程共同使用,ThreadLocal可以让每个线程都维护一个自己的值。可以将ThreadLocal比喻成全局存放数据的盒子,盒子中可以存放每个线程的私有数据。
2、InheritableThreadLocal
功能类似于ThreadLocal,但是InheritableThreadLocal可以让子线程可以继承父线程放在其中的值,同时也可以进行修改,但不会影响父线程。
Java多线程编程核心技术读书笔记(3)-线程通信