声明:文章内容全都是自己的学习总结,如有不对的地方请大家帮忙指出。有需要沟通交流的可加我QQ群:425120333
前面的博客说到了用加锁的方式来保证共享变量的安全,但这不是绝对的,
举一个简单点的生产者和消费者模型,面包师生产面包,放到盘子里(盘子容量有限,只能放一个),店家从盘子中拿到面包,然后卖掉;整个过程很简单,
但是要保证盘子中最多只能有一个面包;如果用加锁的方式来写的话,代码实例如下(只有一个生产者和一个消费者):
public class TestCommunicate {
public static void main(String[] args) {
Plate plate = new Plate();
Thread t2 = new Thread(new Salesman(plate));
Thread t1 = new Thread(new Baker(plate));
t2.start();
t1.start();
}
}
class Plate {
//容量为一的盘子
private List<String> plateList = new ArrayList<String>(1);
public List<String> getPlateList() {
return plateList;
}
public synchronized void getString() {
String str = plateList.get(0);
plateList.clear();
System.out.println("售货员获得面包,并卖出" + str);
}
public synchronized void putString() {
plateList.add("面包");
System.out.println("面包师生产面包,并放入盘子");
}
}
class Baker implements Runnable {
Plate plate;
public Baker(Plate plate) {
this.plate = plate;
}
@Override
public void run() {
for (;;) {
if (plate.getPlateList().size() == 0) {
plate.putString();
}
}
}
}
class Salesman implements Runnable {
Plate plate;
public Salesman(Plate plate) {
this.plate = plate;
}
@Override
public void run() {
for (;;) {
if (plate.getPlateList().size() == 1) {
plate.getString();
}
}
}
}
控制台输出:
面包师生产面包,并放入盘子
售货员获得面包,并卖出面包
面包师生产面包,并放入盘子
售货员获得面包,并卖出面包
面包师生产面包,并放入盘子
售货员获得面包,并卖出面包
面包师生产面包,并放入盘子
售货员获得面包,并卖出面包
面包师生产面包,并放入盘子
售货员获得面包,并卖出面包
面包师生产面包,并放入盘子
。。。
因为写的是死循环,所以会一直输出,不过每次输出都是先生产,再获取。从输出结果中也验证了synchronized使得该模型能够正常工作。
(可以去掉synchronized这个关键词看看输出,就没法保证盘子里只有一个面包)。
通过加上wait(),和notify()的方式实现,代码示例如下:
public class TestCommunicate {
public static void main(String[] args) {
Plate plate = new Plate();
Thread t1 = new Thread(new Baker(plate));
Thread t2 = new Thread(new Salesman(plate));
t2.start();
t1.start();
}
}
class Plate {
//容量为一的盘子
private List<String> plateList = new ArrayList<String>(1);
public synchronized void getString() {
while (plateList.size() == 0) {
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
String str = plateList.get(0);
plateList.clear();
notify();
System.out.println("售货员获得面包,并卖出" + str);
}
public synchronized void putString() {
while (plateList.size() == 1) {
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
plateList.add("面包");
notify();
System.out.println("面包师生产面包,并放入盘子");
}
}
class Baker implements Runnable {
Plate plate;
public Baker(Plate plate) {
this.plate = plate;
}
@Override
public void run() {
for (;;) {
plate.putString();
}
}
}
class Salesman implements Runnable {
Plate plate;
public Salesman(Plate plate) {
this.plate = plate;
}
@Override
public void run() {
for (;;) {
plate.getString();
}
}
}
控制台输出时一样的,就不贴出了。整个代码和原先的比较,看起来是没什么区别,不过使用了wait()这个的作用是什么呢?wait()是让线程等待,
和Thread.sleep()方法有点类似,不过这两个的最大区别是wait()的等待会让该线程释放对锁资源的占用(这也是wait()方法要写在同步块的原因),
而sleep方法不会,至于其他区别就不一一描述了。如果是通过线程要执行同步块的代码时,如果有一个线程已经占有了锁资源,
那其他线程是等待该线程释放锁资源,我把其他线程所处的位置是等待队列(等待线程释放锁资源),而通过wait()方法释放了锁资源的线程
进入的是阻塞队列(通过wait()进入等待的线程,该线程已经执行到同步块中),当然不管是等待队列里的线程,
还是阻塞队列里的线程如果要继续执行都是要先获取锁资源才能执行。
前面说的这些,估计还是没解开大伙的一个疑惑:既然synchronized 能
保证,整个模型的正常运行,为何还要用wait()方法,给自己制造麻烦。其实synchronized 只能保证同步,而同步并不等价于线程间的通信,
所谓的线程间的通信是指一个线程给另一个线程发消息。
上述第一种情况的能正常运行的前提是只有一个生产者和一个消费者,如果代码改为启动2个(或者更多)的生产者,
和2个(或者更多的消费者),上述代码就会运行出错了,但是第二种代码不会(更改的代码示例如下)。
public static void main(String[] args) {
Plate plate = new Plate();
Thread t2 = new Thread(new Salesman(plate));
Thread t1 = new Thread(new Baker(plate));
Thread t3 = new Thread(new Salesman(plate));
Thread t4 = new Thread(new Baker(plate));
t2.start();
t1.start();
t3.start();
t4.start();
}
说了这么多只是希望大家对线程间的通信,有一些认识,以及明白线程间通信的基本概念,因为多线程开发就离不开线程间的通信,很多同步队列(已经实现了线程通信的容器类),都是基于这个开发的,明白了线程间的通信对学好多线程时很有用的,希望说的这些对大家能有所帮助,更主要的是要去写,去理解,后面我还会写一篇线程间的通信更深入的理解。