多线程需要通信的原因:对于系统中的各个子线程来说,如果要完成一个系统功能,同样需要各个线程的配合,这样就少不了线程之间的通信与协作。
多线程中的通信方式:有4种:while循环,通过synchronize配合final 和native修饰的wait(),notify(),notifyAll(), 管道流,工具类
1).while循环:通过循环判断是否达到某个条件,再执行代码:
import java.util.ArrayList;
import java.util.List;
public class MyList {
private List list = new ArrayList();
public void add() {
list.add("elements");
}
public int size() {
return list.size();
}
}
import mylist.MyList;
public class ThreadA extends Thread {
private MyList list;
public ThreadA(MyList list) {
super();
this.list = list;
}
@Override
public void run() {
try {
for (int i = 0; i < 10; i++) {
list.add();
System.out.println("添加了" + (i + 1) + "个元素");
Thread.sleep(1000);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
import mylist.MyList;
public class ThreadB extends Thread {
private MyList list;
public ThreadB(MyList list) {
super();
this.list = list;
}
@Override
public void run() {
try {
while (true) {
if (list.size() == 5) {
System.out.println("==5, 线程b准备退出了");
throw new InterruptedException();
}
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
import mylist.MyList;
import extthread.ThreadA;
import extthread.ThreadB;
public class Test {
public static void main(String[] args) {
MyList service = new MyList();
ThreadA a = new ThreadA(service);
a.setName("A");
a.start();
ThreadB b = new ThreadB(service);
b.setName("B");
b.start();
}
}
2).通过synchronize配合final 和native修饰的wait(),notify(),notifyAll():
package com.thread;
import java.util.concurrent.atomic.AtomicInteger;
/**
* 测试通过synchronize配合final 和native修饰的wait(),notify(),notifyAll():实现线程间的通信方式
*
* @author EdwardShen
*
* 2018年4月3日
*/
public class TestWaitAndNotify {
Object obj = new Object();// 创建一个全局变量,用来协调各个线程
ThreadLocal num = new ThreadLocal();// 设置一个线程wait和notify的触发条件,AtomicInteger比a++在高并发中更高效,可实现在自增和自减的情况下实现线程安全
public static void main(String[] args) {
TestWaitAndNotify test = new TestWaitAndNotify();
test.testWait();
}
private void testWait() {
MyRunner runner = new MyRunner();
new Thread(runner).start();
new Thread(runner).start();
AtomicInteger num03 = new AtomicInteger(0);
Thread th03 = new Thread(new Runnable() {
@Override
public void run() {
while (true) {
synchronized (obj) {// 调用notify/notifyAll和wait一样,同样需要持有该对象
if (num03.getAndIncrement() == 5) {
obj.notify();// 唤醒最先一个挂在obj上面的线程.每次只唤醒一个。这里是按照等待的先后顺序进行唤醒
}
}
sleep(1000);
}
}
});
th03.start();
}
class MyRunner implements Runnable {
@Override
public void run() {
num.set(new AtomicInteger(0));
while (true) {
System.out.println("当前线程的名字:"+Thread.currentThread().getName());
if (num.get().getAndIncrement() == 1) {
synchronized (obj) {// 如果要想调用wait方法,则必须持有该对象。否则将会抛出IllegalMonitorStateException
try {
System.out.println(Thread.currentThread().getName() + "挂起等待");
obj.wait();// 同一个线程可以wait多次,多个线程也可以使用同一个obj调用wait
System.out.println(Thread.currentThread().getName() + "唤醒!!!");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
sleep(1000);
}
}
}
private void sleep(int time) {
try {
Thread.sleep(time);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
注意:这里的synchronizeds的目的不是加锁控制线程的串行,而是为了持有锁来调用wait和notify对象。
AtomicInteger比a++在高并发中更高效,AtomicInteger提供原子操作,可实现在自增和自减的情况下实现线程安全,原因:其中含有volatile实现线程间的可见性。
notifyAll():会唤醒所有线程。建议使用notifyAll(),因为如果使用notify(),需要知道每个线程在做什么。
notify():退出synchronized范围后,当前线程才会释放锁,这样,wait()中的线程才会争取cpu资源获得锁
yield(),join()和sleep(long mills),都不会释放锁,但是join(long mills)会释放锁,new Thread().join(0)代表:该线程会永远等待下去,不会加入到代码中运行。
3).通过工具类java.util.concurrent包中的Lock和Condition的配合:从而实现多个阻塞队列,而且signalAll()只唤醒某个阻塞队列下的阻塞线程。比第二种方式更加靠谱。
package com.thread;
import java.util.Date;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
*
* @author EdwardShen
*
* 2018年4月3日
*/
public class LockAndConditionTest {
private final Lock lock;
private final Condition notFull;
private final Condition notEmpty;
private int maxSize;
private List storage;
LockAndConditionTest(int size) {
// 使用锁lock,并且创建两个condition,相当于两个阻塞队列
lock = new ReentrantLock();
notFull = lock.newCondition();
notEmpty = lock.newCondition();
maxSize = size;
storage = new LinkedList<>();
}
public static void main(String[] arg) {
LockAndConditionTest buffer = new LockAndConditionTest(10);
Producer producer = new Producer(buffer);
Consumer consumer = new Consumer(buffer);
for (int i = 0; i < 3; i++) {
new Thread(producer, "producer-" + i).start();
}
for (int i = 0; i < 3; i++) {
new Thread(consumer, "consumer-" + i).start();
}
}
public void put() {
lock.lock();
try {
while (storage.size() == maxSize) {// 如果队列满了
System.out.print(Thread.currentThread().getName() + ": wait \n");
notFull.await();// 阻塞生产线程
}
storage.add(new Date());
System.out.print(Thread.currentThread().getName() + ": put:" + storage.size() + "\n");
Thread.sleep(1000);
notEmpty.signalAll();// 唤醒消费线程
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void take() {
lock.lock();
try {
while (storage.size() == 0) {// 如果队列满了
System.out.print(Thread.currentThread().getName() + ": wait \n");
notEmpty.await();// 阻塞消费线程
}
Date d = ((LinkedList) storage).poll();
System.out.print(Thread.currentThread().getName() + ": take:" + storage.size() + "\n");
Thread.sleep(1000);
notFull.signalAll();// 唤醒生产线程
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
class Producer implements Runnable {
private LockAndConditionTest buffer;
Producer(LockAndConditionTest b) {
buffer = b;
}
@Override
public void run() {
while (true) {
buffer.put();
}
}
}
class Consumer implements Runnable {
private LockAndConditionTest buffer;
Consumer(LockAndConditionTest b) {
buffer = b;
}
@Override
public void run() {
while (true) {
buffer.take();
}
}
}
在Condition中,用await()替换了wait(),用signal()替换了notify(),用signalAll()替换notifyAll()即为用Condition替换了Object中的通信方法,用Lock替换了synchronized。
Lock与synchronized的区别:主要有2点
a.用法上:前者:lock.lock()后,必须要在finally中lock.unlock(),防死锁发生。
后者:不需理会这些事情
b.通信上:前者:用lock配合condition,可以实现多个阻塞队列,而且signalAll()只唤醒某个阻塞队列下的阻塞线程。
后者:synchronize配合final 和native修饰的wait(),notifyAll():会唤醒所有线程。
4).管道流:使用java.io.PipedInputStream 和 java.io.PipedOutputStream进行通信
前者:代表消费者:数据在管道中的输出端,即为线程从管道中读取数据
后者:代表生产者:数据在管道中的输入端,即为线程从管道中写入数据