多线程通讯(wait、notify、Lock、ThreadLocal)
多线程之间通讯
什么是多线程通讯?
就是多个线程对同一个共享资源,进行不同的操作。
介绍两个API中的方法,这两个是Object里面的方法:
wait();等待,线程从运行状态变为休眠状态
notify();唤醒,线程从休眠状态变为运行状态
现在解决一下这样一个案例:
两个线程,面向一个仓库进行读写操作,仓库里面用一个用户类表示,里面包括姓名和性别这两个属性,A线程往里面写,然后B线程立马读出来,这样交替执行,该怎么设计?
分析一下这个题目:仓库里面是两个属性,两个线程同时对仓库进行操作,肯定要同步,不然会出现数据混乱问题,然后考虑的是让两个线程交替执行,A线程写完后要等待B线程读出以后在继续写,这时候要用到线程之间的通讯。wait和notify的使用必须与synchronized一起使用,wait包括释放锁,并进入阻塞队列这两个语义,这两步需要指定一个监视器来完成;notify是唤醒该线程,要想唤醒,首先需要知道该对象在哪儿,需要获取该对象的锁,才能去该对象对应的等待队列去唤醒一个线程,只有已经释放该对象锁的线程,才能被唤醒然后去竞争该对象锁。
为了更好的看出效果,我让写线程奇数和偶数是写入不同的姓名和性别,看是否打印会出现数据混乱。
代码如下:
class User { String name; String sex; boolean flag = true; } class Write extends Thread { User user; public Write(User user) { this.user = user; } @Override public void run() { int count = 2; while (true) { synchronized (user) { if (!user.flag) { try { user.wait(); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } if (count % 2 == 0) { user.name = "周瑜"; user.sex = "男"; } else { user.name = "小乔"; user.sex = "女"; } count = (count + 1) % 2; user.notify(); user.flag = false; } } } } class Read extends Thread { User user; public Read(User user) { this.user = user; } @Override public void run() { while (true) { synchronized (user) { if (user.flag) { try { user.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println(user.name + "," + user.sex); user.notify(); user.flag = true; } } } } public class OutInputDemo { public static void main(String[] args) { User user = new User(); Write write = new Write(user); Read read = new Read(user); write.start(); read.start(); } }
View Code
wait和sleep的区别:
wait位于同步中,需要释放锁的资源,需要被notify唤醒。
sleep不释放锁的资源,时间到自然醒。
Lock锁
jdk1.5以后,并发包中新增了Lock接口及其相应的实现类来实现锁的功能,提供了和synchronized一样的同步功能,但是也有区别。
Lock和synchronized的区别:
synchronized是从代码开始上锁,代码结束释放锁,完全自动化,这种锁的效率低、扩展性不高。
Lock锁属于手动的,手动上锁,手动释放锁,灵活性高
在Lock中,不能使用wait和notify,取而代之的为:
Condition 它的功能类似于Object.wait()和Object.notify()的功能。
Condition condition = lock.newCondition(); condition.await();//相当于wait condition.signal();//相当于notify
上面的案例用Lock锁修改为:
class User2 { String name; String sex; boolean flag = true; Lock lock = new ReentrantLock(); } class Write2 extends Thread { User2 user; Condition condition; public Write2(User2 user,Condition condition) { this.user = user; this.condition = condition; } @Override public void run() { int count = 2; while (true) { try { user.lock.lock(); if (!user.flag) { try { condition.await(); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } if (count % 2 == 0) { user.name = "周瑜"; user.sex = "男"; } else { user.name = "小乔"; user.sex = "女"; } count = (count + 1) % 2; condition.signal(); user.flag = false; } catch (Exception e) { e.printStackTrace(); } finally { user.lock.unlock(); } } } } class Read2 extends Thread { User2 user; Condition condition; public Read2(User2 user,Condition condition) { this.user = user; this.condition = condition; } @Override public void run() { while (true) { try { user.lock.lock(); if (user.flag) { try { condition.await(); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println(user.name + "," + user.sex); condition.signal(); user.flag = true; } catch (Exception e) { e.printStackTrace(); } finally { user.lock.unlock(); } } } } public class OutInputDemo2 { public static void main(String[] args) { User2 user = new User2(); Condition condition = user.lock.newCondition(); Write2 write = new Write2(user,condition); Read2 read = new Read2(user,condition); write.start(); read.start(); } }
View Code
怎么来停止线程???
stop()???
这个方法已经被弃用,不推荐使用,太暴力,不可恢复,就会导致不安全。
我么使用interrupt来停止线程,API中还有Thread.currentThread().isInterrupted()来进行判断是否中断了线程,案例如下:
class StopThreadDemo2 extends Thread{ @Override public synchronized void run() { while(!Thread.currentThread().isInterrupted()){ for (int i = 0; i < 30; i++) { System.out.println(i); } } System.out.println("Thread is interrupt!"); } } public class InterruptDemo { public static void main(String[] args) { StopThreadDemo2 stopThreadDemo = new StopThreadDemo2(); stopThreadDemo.start(); for (int i = 0; i < 10; i++) { if (i == 2) { stopThreadDemo.interrupt(); } System.out.println("主线程"+i); } } }
ThreadLocal
本地线程,为每一个线程提供一个局部变量。
定义的变量不会共享,是自己的本地局部变量。
看下面这个案例:
class Number { int count = 0; public int getNumber() { count = count + 1; return count; } } class ThreadLocalThread extends Thread { Number number; public ThreadLocalThread(Number number) { this.number = number; } @Override public void run() { for (int i = 0; i < 3; i++) { System.out.println(Thread.currentThread().getName() + ":" + number.getNumber()); } } } public class ThreadLocalDemo { public static void main(String[] args) { Number number1 = new Number(); Number number2 = new Number(); Number number3 = new Number(); ThreadLocalThread threadLocalThread1 = new ThreadLocalThread(number1); ThreadLocalThread threadLocalThread2 = new ThreadLocalThread(number2); ThreadLocalThread threadLocalThread3 = new ThreadLocalThread(number3); threadLocalThread1.start(); threadLocalThread2.start(); threadLocalThread3.start(); } }
这个案例是三个线程分别用来生成自己的数字number,我们定义了三个Number对象,如果有100个线程,是不是需要定义100个number对象,该怎么解决这个问题呢???
class Number { int count; public static ThreadLocal<Integer> threadLocal = new ThreadLocal<Integer>(){ protected Integer initialValue() {//初始化threadLocal.get()的值 return 0; }; }; public int getNumber() { count = threadLocal.get() + 1; threadLocal.set(count);//更新threadLocal里面的值 return count; } } class ThreadLocalThread extends Thread { Number number; public ThreadLocalThread(Number number) { this.number = number; } @Override public void run() { for (int i = 0; i < 3; i++) { System.out.println(Thread.currentThread().getName() + ":" + number.getNumber()); } } } public class ThreadLocalDemo { public static void main(String[] args) { Number number = new Number(); ThreadLocalThread threadLocalThread1 = new ThreadLocalThread(number); ThreadLocalThread threadLocalThread2 = new ThreadLocalThread(number); ThreadLocalThread threadLocalThread3 = new ThreadLocalThread(number); threadLocalThread1.start(); threadLocalThread2.start(); threadLocalThread3.start(); } }
通过get()和set()进行对本地局部变量的更新。
原理:Map集合存储
get()源码解析:
public T get() { Thread t = Thread.currentThread();//获取当前线程 ThreadLocalMap map = getMap(t);//获取当前线程的ThreadLocalMap集合, if (map != null) {//判断是否存在该线程的Map集合 ThreadLocalMap.Entry e = map.getEntry(this);//然后就判断该集合里面是否有该对象的值,有的话,就返回存在的值,没有就返回初始值 if (e != null) { @SuppressWarnings("unchecked") T result = (T)e.value; return result; } } return setInitialValue(); }
set()源码:
public void set(T value) {//获取当前线程,看是否存在ThreadLocalMap,存在就直接放里面放值,不存在就创建一个ThreadLocalMap Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null) map.set(this, value); else createMap(t, value); }