线程间通信
前言:学习笔记,以供参考
线程是操作系统中独立的个体,但是这些个体如果不经过特殊处理就不能成为一个整体,线程间通信就成为整体的必用方式之一。
使用wait/notify方法实现线程间的通信。这两个方法都是Object类的方法。
1.必须配合关键字synchronized使用;
2.wait方法释放锁,notify方法不释放锁。
1.以下是关于阿里的一道面试题,通过这道题,我们理解线程间通信。
示例:阅读以下代码,通过wait和notify方式对这个代码进行重构
package com.wp.test; import java.util.ArrayList; import java.util.List; public class ListAdd1 { private volatile static List list = new ArrayList<>(); public void add(){ list.add("wp"); } public int size(){ return list.size(); } public static void main(String args[]){ final ListAdd1 l = new ListAdd1(); Thread t1 = new Thread(new Runnable() { @Override public void run() { try { for(int i=0;i<10;i++){ l.add(); System.out.println("当前线程:"+Thread.currentThread().getName()+"添加了一个元素"); Thread.sleep(500); } } catch (InterruptedException e) { e.printStackTrace(); } } },"t1"); Thread t2 = new Thread(new Runnable() { @Override public void run() { while(true){ if(list.size()==5){ System.out.println("当前线程收到通知:"+Thread.currentThread().getName()+" list.size()==5 线程停止"); throw new RuntimeException(); } } } },"t2"); t1.start(); t2.start(); } }
结果:
当前线程:t1添加了一个元素 当前线程:t1添加了一个元素 当前线程:t1添加了一个元素 当前线程:t1添加了一个元素 当前线程:t1添加了一个元素 当前线程收到通知:t2 list.size()==5 线程停止 Exception in thread "t2" java.lang.RuntimeException at com.wp.test.ListAdd1$2.run(ListAdd1.java:36) at java.lang.Thread.run(Thread.java:745) 当前线程:t1添加了一个元素 当前线程:t1添加了一个元素 当前线程:t1添加了一个元素 当前线程:t1添加了一个元素 当前线程:t1添加了一个元素
重构,改成wait和notify方式:
package com.wp.test; import java.util.ArrayList; import java.util.List; public class ListAdd2 { private volatile static List list = new ArrayList<>(); final static Object lock = new Object(); public void add(){ list.add("wp"); } public int size(){ return list.size(); } public static void main(String args[]){ final ListAdd2 l = new ListAdd2(); Thread t1 = new Thread(new Runnable() { @Override public void run() { synchronized(lock){ try { for(int i=0;i<10;i++){ l.add(); System.out.println("当前线程:"+Thread.currentThread().getName()+"添加了一个元素"); Thread.sleep(500); if(list.size()==5){ System.out.println("已发出通知!"); lock.notify(); } } } catch (InterruptedException e) { e.printStackTrace(); } } } },"t1"); Thread t2 = new Thread(new Runnable() { @Override public void run() { synchronized(lock){ if(list.size()!=5){ try { lock.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println("当前线程收到通知:"+Thread.currentThread().getName()+" list.size()==5 线程停止"); throw new RuntimeException(); } } },"t2"); t2.start(); t1.start(); } }
结果:
当前线程:t1添加了一个元素 当前线程:t1添加了一个元素 当前线程:t1添加了一个元素 当前线程:t1添加了一个元素 当前线程:t1添加了一个元素 已发出通知! 当前线程:t1添加了一个元素 当前线程:t1添加了一个元素 当前线程:t1添加了一个元素 当前线程:t1添加了一个元素 当前线程:t1添加了一个元素 当前线程收到通知:t2 list.size()==5 线程停止 Exception in thread "t2" java.lang.RuntimeException at com.wp.test.ListAdd2$2.run(ListAdd2.java:47) at java.lang.Thread.run(Thread.java:745)
问题:有什么弊端?操作不是实时的。 解决方式:使用CountDownLatch实现实时通知。
package com.wp.test; import java.util.ArrayList; import java.util.List; import java.util.concurrent.CountDownLatch; public class ListAdd2 { private volatile static List list = new ArrayList<>(); final static Object lock = new Object(); final static CountDownLatch countDownLatch = new CountDownLatch(1); public void add(){ list.add("wp"); } public int size(){ return list.size(); } public static void main(String args[]){ final ListAdd2 l = new ListAdd2(); Thread t1 = new Thread(new Runnable() { @Override public void run() { //synchronized(lock){ try { for(int i=0;i<10;i++){ l.add(); System.out.println("当前线程:"+Thread.currentThread().getName()+"添加了一个元素"); Thread.sleep(500); if(list.size()==5){ System.out.println("已发出通知!"); //lock.notify(); countDownLatch.countDown(); } } } catch (InterruptedException e) { e.printStackTrace(); } // } } },"t1"); Thread t2 = new Thread(new Runnable() { @Override public void run() { //synchronized(lock){ if(list.size()!=5){ try { countDownLatch.await(); //lock.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println("当前线程收到通知:"+Thread.currentThread().getName()+" list.size()==5 线程停止"); throw new RuntimeException(); // } } },"t2"); t2.start(); t1.start(); } }
结果:是实时的操作。
当前线程:t1添加了一个元素 当前线程:t1添加了一个元素 当前线程:t1添加了一个元素 当前线程:t1添加了一个元素 当前线程:t1添加了一个元素 已发出通知! 当前线程:t1添加了一个元素 当前线程收到通知:t2 list.size()==5 线程停止 Exception in thread "t2" java.lang.RuntimeException at com.wp.test.ListAdd2$2.run(ListAdd2.java:51) at java.lang.Thread.run(Thread.java:745) 当前线程:t1添加了一个元素 当前线程:t1添加了一个元素 当前线程:t1添加了一个元素 当前线程:t1添加了一个元素
2.使用wait和notify模拟LinkedBlockingQueue
BlockingQueue:是一个队列,并且支持阻塞机制,阻塞的放入和得到数据。我们要实现LinkedBlockingQueue下面的两个简单的方法put和take。
Put(anObject):把anObject加到BlockingQueue里,如果BlockQueue没有空间,则调用此方法的线程被阻塞,直到BlockingQueue里面有空间再继续。
Take:取走BlockingQueue里排在首位的对象,若BlockingQueue为空,阻塞进入等待状态,直到BlockingQueue有新的数据被加入。
package com.wp.test; import java.util.LinkedList; import java.util.concurrent.atomic.AtomicInteger; public class MyQueue { private final LinkedList<Object> list = new LinkedList<Object>(); private final AtomicInteger count = new AtomicInteger(0); private final int maxSize; private final int minSize = 0; private final Object lock = new Object(); public MyQueue (int maxSize){ this.maxSize = maxSize; } public void put (Object obj) { synchronized(lock){ while(count.get() == maxSize){ try { lock.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } list.add(obj); count.getAndIncrement(); System.out.println(" 元素 " + obj + " 被添加 "); lock.notify(); } } public Object take(){ Object temp = null; synchronized (lock) { while(count.get() == minSize){ try { lock.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } count.getAndDecrement(); temp = list.removeFirst(); System.out.println(" 元素 " + temp + " 被消费 "); lock.notify(); } return temp; } public int size(){ return count.get(); } public static void main(String[] args) throws Exception { final MyQueue m = new MyQueue(5); m.put("a"); m.put("b"); m.put("c"); m.put("d"); m.put("e"); System.out.println("当前元素个数:" + m.size()); Thread t1 = new Thread(new Runnable() { @Override public void run() { m.put("h"); m.put("i"); } }, "t1"); Thread t2 = new Thread(new Runnable() { @Override public void run() { try { Thread.sleep(1000); Object t1 = m.take(); //System.out.println("被取走的元素为:" + t1); Thread.sleep(1000); Object t2 = m.take(); //System.out.println("被取走的元素为:" + t2); } catch (InterruptedException e) { e.printStackTrace(); } } }, "t2"); t1.start(); Thread.sleep(1000); t2.start(); } }
结果:
元素 a 被添加 元素 b 被添加 元素 c 被添加 元素 d 被添加 元素 e 被添加 当前元素个数:5 元素 a 被消费 元素 h 被添加 元素 b 被消费 元素 i 被添加
3.ThreadLocal
ThreadLocal概念:线程局部变量,是一种多线程间并发访问变量的解决方案。与其synchronized等加锁的方式不同,ThreadLocal完全不提供锁,而是用以空间换时间的手段,为每个线程提供变量的独立副本,以保障线程安全。
从性能上说,ThreadLocal不具有绝对优势,在并发不是很高的时候,加锁的性能会更好,但作为一套与锁完全无关的线程安全解决方案,在高并发量或者竞争激烈的场景,使用ThreadLocal可以在一定程度上减少锁竞争。
package com.wp.test; public class ConnThreadLocal { public static ThreadLocal<String> th = new ThreadLocal<String>(); public void setTh(String value){ th.set(value); } public void getTh(){ System.out.println(Thread.currentThread().getName() + ":" + this.th.get()); } public static void main(String[] args) throws InterruptedException { final ConnThreadLocal ct = new ConnThreadLocal(); Thread t1 = new Thread(new Runnable() { @Override public void run() { ct.setTh("张三"); ct.getTh(); } }, "t1"); Thread t2 = new Thread(new Runnable() { @Override public void run() { try { Thread.sleep(1000); //ct.setTh("李四"); ct.getTh(); } catch (InterruptedException e) { e.printStackTrace(); } } }, "t2"); t1.start(); t2.start(); } }
结果分析:线程之间通过ThreadLocal使用属于自己的变量。ThreadLocal中有一个map容器,key是当前线程t,value是当前线程对应变量的值。
4.单例&多线程
单例模式,最常见的就是饥饿模式和懒汉模式,一个是在声明的同时直接实例化对象,一个在调用方法时进行实例化对象。在多线程模式中,考虑到性能和线程安全的问题,我们一般选择下面两种比较经典的单例模式,在性能体高的同时,要保证了线程安全。
dubble check instance
static inner class
1. Double check instance(属于懒汉模式)
package com.wp.test; public class DoubleSingleton { private static DoubleSingleton ds; public static DoubleSingleton getDs(){ if(ds == null){ try { //模拟初始化对象的准备时间... Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } synchronized (DoubleSingleton.class) { if(ds == null){ ds = new DoubleSingleton(); } } } return ds; } public static void main(String[] args) { Thread t1 = new Thread(new Runnable() { @Override public void run() { System.out.println(DoubleSingleton.getDs().hashCode()); } },"t1"); Thread t2 = new Thread(new Runnable() { @Override public void run() { System.out.println(DoubleSingleton.getDs().hashCode()); } },"t2"); Thread t3 = new Thread(new Runnable() { @Override public void run() { System.out.println(DoubleSingleton.getDs().hashCode()); } },"t3"); t1.start(); t2.start(); t3.start(); } }
结果分析:输出结果一样。如果在生成对象方法中,去掉ds == null 判断,那么输出结果不一样。原因?
2.Static inner class(类似饿汉模式)
package com.wp.test; public class InnerSingleton { private static class Singletion { private static Singletion single = new Singletion(); } public static Singletion getInstance(){ return Singletion.single; } }