高并发中多线程的通信方式

多线程需要通信的原因:对于系统中的各个子线程来说,如果要完成一个系统功能,同样需要各个线程的配合,这样就少不了线程之间的通信与协作。

多线程中的通信方式:有4种:while循环,通过synchronize配合final 和native修饰的wait(),notify(),notifyAll(),  管道流,工具类

1).while循环:通过循环判断是否达到某个条件,再执行代码:

import java.util.ArrayList;
import java.util.List;

public class MyList {

    private List<String> list = new ArrayList<String>();
    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<AtomicInteger> num = new ThreadLocal<AtomicInteger>();// 设置一个线程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<Date> 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<Date>) 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进行通信

前者:代表消费者:数据在管道中的输出端,即为线程从管道中读取数据

后者:代表生产者:数据在管道中的输入端,即为线程从管道中写入数据


猜你喜欢

转载自blog.csdn.net/sx1119183530/article/details/79810130