13.4 线程的同步
实例,生产者和消费者。
分析:
生产者-消费者问题是多线程同步处理的典型问题
有一块生产者和消费者共享的有界缓冲区,生产者往缓冲区放入产品,消费者从缓冲区取走产品,这个过程可以无休止的执行,不能因缓冲区满生产者放不进产品而终止,也不能因缓冲区空消费者无产品可取而终止。
解决生产者消费者问题的方法
一种是采用某种机制保持生产者和消费者之间的同步(有较高的效率并且可控制性较好,比较常用) 以下示例为这种方法
一种是在生产者和消费者之间建立一个管道(由于管道缓冲区不易控制及被传输数据对象不易封装等原因,比较少用)
线程的同步解决生产者-消费者问题
限制公共缓冲区不能被两个线程同时访问,需要使用互斥锁,即用synchronized来标识同步资源。
但加了互斥锁以后有可能会造出死锁。这时需要wait()方法和notify()方法--当前线程被阻塞并释放该对象的互斥锁。
package com.hbsi.thread;
public class SyncStack {
private int index=0;
private char[] data=new char[6];
public synchronized void push(char ch){
while(index==data.length){
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
this.notify();
data[index]=ch;
index++;
System.out.println("produced:"+ch);
}
public synchronized char pop(){
while(index==0){
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
this.notify();
index--;
System.out.println("custom:"+data[index]);
return data[index];
}
}
测试:
package com.hbsi.thread;
public class SyncTest {
public static void main(String[] args) {
// TODO Auto-generated method stub
SyncStack s=new SyncStack();
Producer p=new Producer(s);
Consumer c=new Consumer(s);
Thread t1=new Thread(p);
Thread t2=new Thread(c);
t1.start();
t2.start();
}
}
class Producer implements Runnable{
SyncStack stack;
public Producer(SyncStack stack) {
this.stack = stack;
}
@Override
public void run() {
// TODO Auto-generated method stub
for(int i=0;i<20;i++){
char ch=(char)(Math.random()*26+'A');
stack.push(ch);
try {
Thread.sleep((int)(Math.random()*300));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
class Consumer implements Runnable{
SyncStack stack;
public Consumer(SyncStack stack) {
this.stack = stack;
}
@Override
public void run() {
for(int i=0;i<20;i++){
char ch=stack.pop();
try {
Thread.sleep((int)(Math.random()*800));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
公共缓冲区 |
线程A取数据 |
线程B写数据 |
线程B写一次,线程A读一次
在某个时候线程B运行速度比较快,在线程A未读取上一个数据之前,B就写了第二次数据,造成数据遗漏。
在某个时候线程A运行速度比较快,它读完一次数据之后,线程B还没来得及写,线程A又来读第二次。结果线程A读不到数据,导致运行出错。
线程B正在写数据时,线程A也来读取数据,这时可能线程B还没将数据写完,线程A将数据读走,导致程序出错。
定时器:Timer和TimerTask
java.util包中的Timer和TimerTask类也可实现多线程
Timer类实现的是类似闹钟的功能,也就是定时或者每隔一定时间间隔触发一次线程。
TimerTask类是一个抽象类,该类实现了Runnable接口,具备多线程的能力。
通过继承TimerTask类创建子类,使该子类获得多线程的能力,将需要多线程执行的代码书写在run方法内部,然后通过Timer类启动线程的执行。
实例,使用Timer和TimerTask组合实现的多线程。
class TimerTask1 extends TimerTask {
public void run() {
try{
for(int i = 0;i < 5;i++){
TimerTask1具有多线程功能
|
System.out.println("Run" + i);
}
}catch(Exception e){}
}
}
public class TestTimer {
public static void main(String[] args) {
Timer t = new Timer();//定时器
TimerTask1 tt1 = new TimerTask1();
t.schedule(tt1, 0);//启动线程,tt1是需要启动的线程对象,参数0,表示立刻启动
try{
for(int i = 0;i < 5;i++){
Thread.sleep(1000);
System.out.println("Main:" + i);
}
}catch(Exception e){}
}
}
一、schedule()方法
public void schedule(TimerTask task,Date time):该方法的作用是在到达time指定的时间或已经超过该时间时执行线程task。
Date d = new Date(2009-1900,10-1,1,10,0,0);
t. schedule(task,d);
public void schedule(TimerTask task, Date firstTime, long period):在时间到达firstTime开始,每隔period毫秒就启动一次task指定的线程,这种方式会重复启动线程。
Date d = new Date(2009-1900,10-1,1,10,0,0);
t. schedule(task,d,20000);
public void schedule(TimerTask task,long delay)在执行schedule方法delay毫秒以后启动线程task。
t.schedule(task,1000);//在执行该行启动代码1000毫秒后启动一次线程task
public void schedule(TimerTask task,long delay,long period):在执行schedule方法delay毫秒以后启动线程task,然后每隔period毫秒重复启动线程task。