线程同步是个老生常谈的问题了,在这里我将通过一个Java多线程程序,来说明控制相互交互的线程之间的运行进度,使程序运行总是既高效又稳定。这个多线程程序将采用生产者---消费者模型,来说明怎么样实现多线程的同步。
如果让我定义一下什么是消费者、生产者:我觉得可以把系统中使用某种资源的线程称为消费者,产生该种资源的线程称为生产者。在下面的Java的应用程序中,生产者线程向一个线程安全的堆栈缓冲区中写(PUSH)数据,消费者从该堆栈缓冲区中读(POP)数据,这样,这个程序中同时运行的两个线程共享同一个堆栈缓冲区资源。
类Producer是生产者模型,其中的run方法中定义了生产者线程所做的操作,循环调用push()方法,将生产的100个字母送入堆栈中,每次执行完push操作后,调用sleep方法睡眠一段随机时间。
类Consumer是消费者模型,循环调用pop方法,从堆栈取出一个字母,一共取100次,每次执行完push操作后,调用sleep方法睡眠一段随机时间。
先看下同步堆栈类的源码:
public class SyncStack {
private int index = 0;
private char[] data;
private int size = 100;
public SyncStack(int size){
System.out.println("栈被创建");
this.size = size;
data = new char[size];
}
//加上互斥锁
public synchronized void push(char c){
while( index == size ){
try{
System.out.println("栈满了!");
this.wait(); //等待,直到有数据出栈
}catch(InterruptedException e){
}
}
data[index] = c;
index++;
this.notify(); //通知其它线程把数据出栈
}
//加上互斥锁
public synchronized char pop(){
while(index == 0){
try{
System.out.println("栈空了!");
this.wait();//等待其它线程把数据入栈
}catch(InterruptedException e){
}
}
index--; //指针向下移动
char ch = data[index];
this.notify(); //通知其它线程把数据入栈
return ch;
}
//显示堆栈内容
public synchronized void print(){
for(int i=0; i<index; i++){
System.out.print(data[i]);
}
System.out.println();
this.notify(); //通知其它线程把显示堆栈内容
}
}
public class SyncStack {
private int index = 0;
private char[] data;
private int size = 100;
public SyncStack(int size){
System.out.println("栈被创建");
this.size = size;
data = new char[size];
}
//加上互斥锁
public synchronized void push(char c){
while( index == size ){
try{
System.out.println("栈满了!");
this.wait(); //等待,直到有数据出栈
}catch(InterruptedException e){
}
}
data[index] = c;
index++;
this.notify(); //通知其它线程把数据出栈
}
//加上互斥锁
public synchronized char pop(){
while(index == 0){
try{
System.out.println("栈空了!");
this.wait();//等待其它线程把数据入栈
}catch(InterruptedException e){
}
}
index--; //指针向下移动
char ch = data[index];
this.notify(); //通知其它线程把数据入栈
return ch;
}
//显示堆栈内容
public synchronized void print(){
for(int i=0; i<index; i++){
System.out.print(data[i]);
}
System.out.println();
this.notify(); //通知其它线程把显示堆栈内容
}
}
有了同步堆栈类,接着看看我们的生产者类的源码:
//生产者类
public class Producer implements Runnable {
private SyncStack theStack;
public Producer(SyncStack s){
theStack = s;
}
public void run() {
char ch;
for(int i=0; i<100; i++){
//随机产生100个字符
ch = (char)(Math.random()*26 +'A');
theStack.push(ch);
System.out.println("Produced:"+ch);
try{
//每产生一个字符线程就睡眠一下
Thread.sleep((int)(Math.random()*1000));
}catch(InterruptedException e){
}
}
}
}
//生产者类
public class Producer implements Runnable {
private SyncStack theStack;
public Producer(SyncStack s){
theStack = s;
}
public void run() {
char ch;
for(int i=0; i<100; i++){
//随机产生100个字符
ch = (char)(Math.random()*26 +'A');
theStack.push(ch);
System.out.println("Produced:"+ch);
try{
//每产生一个字符线程就睡眠一下
Thread.sleep((int)(Math.random()*1000));
}catch(InterruptedException e){
}
}
}
}
接着看下消费者类的源码:
//消费者类
public class Consumer implements Runnable {
private SyncStack theStack;
public Consumer(SyncStack s){
theStack = s;
}
public void run() {
char ch;
for(int i=0; i<100; i++){
ch = theStack.pop();
System.out.println("Consumed:"+ch);
try{
//每产生一个字符线程就睡眠一下
Thread.sleep((int)(Math.random()*1000));
}catch(InterruptedException e){
}
}
}
}
//消费者类
public class Consumer implements Runnable {
private SyncStack theStack;
public Consumer(SyncStack s){
theStack = s;
}
public void run() {
char ch;
for(int i=0; i<100; i++){
ch = theStack.pop();
System.out.println("Consumed:"+ch);
try{
//每产生一个字符线程就睡眠一下
Thread.sleep((int)(Math.random()*1000));
}catch(InterruptedException e){
}
}
}
}
最后让我们来看看主程序main所在类的源码:
public class SyncTest {
public static void main(String[] args){
//下面的消费者类对象和生产者类对象所操作的是同一个同步堆栈对象
SyncStack stack = new SyncStack(5);
Runnable source = new Producer(stack);
Runnable sink = new Consumer(stack);
Thread t1 = new Thread(source);
Thread t2 = new Thread(sink);
t1.start();
t2.start();
}
}
public class SyncTest {
public static void main(String[] args){
//下面的消费者类对象和生产者类对象所操作的是同一个同步堆栈对象
SyncStack stack = new SyncStack(5);
Runnable source = new Producer(stack);
Runnable sink = new Consumer(stack);
Thread t1 = new Thread(source);
Thread t2 = new Thread(sink);
t1.start();
t2.start();
}
}
在本例中,使用了一个生产者线程和一个消费者线程,当生产者线程往堆栈中添加字符时,如果堆栈已满,通过调用this.wait方法,(这里,this就是互斥锁)把自己加入到互斥锁对象(SyncStack)的锁等待队列中,如果该堆栈不满,则该生产者线程加入到互斥锁对象(SyncStack)的锁申请队列中,并且很快就被JVM取出来执行。当生产者线程在执行添加字符操作的时候,消费者是不能从中取出字符的,只能在等待队列中等待,当生产者添加完字符的时候,使用this.notify()(这里,this就是互斥锁)把等待队列中的第一个消费者唤醒,把它加入到锁申请队列中,很快该消费者线程就会获得CPU时间。此时的生产者线程已经无法再次添加字符,因为消费者线程正在synchronized代码块中运行,JVM把生产者线程加入锁等待队列中。当消费者线程从堆栈中取完字符后,再使用this.notify()方法把生产者从等待进程中唤醒,添加字符,如此循环往复,直到生产者线程和消费者线程都运行结束。
下面是该程序的某次运行结果:
栈被创建
Produced:Z
Consumed:Z
栈空了!
Consumed:G
Produced:G
栈空了!
Consumed:J
Produced:J
栈空了!
Consumed:O
Produced:O
栈空了!
Consumed:A
Produced:A
Produced:T
Consumed:T
Produced:M
Produced:H
Consumed:H
Consumed:M
Produced:D
Produced:B
Consumed:B
Produced:W
Consumed:W
Produced:C
Produced:P
Consumed:P
Consumed:C
Produced:P
Consumed:P
Produced:N
Consumed:N
Consumed:D
Produced:V
Consumed:V
栈空了!
Consumed:V
Produced:V
Produced:Z
Consumed:Z
Produced:J
Consumed:J
Produced:O
Consumed:O
栈空了!
Consumed:B
Produced:B
Produced:N
Consumed:N
Produced:H
Consumed:H
栈空了!
Consumed:G
Produced:G
栈空了!
Consumed:F
Produced:F
Produced:W
Consumed:W
Produced:F
Produced:W
Consumed:W
Consumed:F
Produced:X
Consumed:X
栈空了!
Consumed:D
Produced:D
栈空了!
Consumed:H
Produced:H
栈空了!
Consumed:Q
Produced:Q
Produced:A
Produced:X
Produced:B
Produced:B
Consumed:B
Consumed:B
Consumed:X
Produced:M
Produced:J
Consumed:J
Consumed:M
Produced:G
Consumed:G
Produced:I
Consumed:I
Consumed:A
Produced:B
Produced:Q
Consumed:Q
Consumed:B
Produced:D
Consumed:D
栈空了!
Consumed:X
Produced:X
栈空了!
Consumed:B
Produced:B
Produced:S
Consumed:S
Produced:N
Produced:O
Produced:L
Consumed:L
Consumed:O
Produced:B
Consumed:B
Produced:N
Produced:G
Consumed:G
Consumed:N
Produced:M
Produced:R
Consumed:R
Consumed:M
Consumed:N
Produced:O
Consumed:O
Produced:A
Consumed:A
Produced:N
Produced:D
Consumed:D
Produced:P
Consumed:P
Produced:W
Produced:Z
Produced:M
Consumed:M
Consumed:Z
Produced:C
Consumed:C
Produced:H
Consumed:H
Consumed:W
Produced:X
Produced:J
Consumed:J
Produced:P
Consumed:P
Produced:S
Consumed:S
Produced:Y
Consumed:Y
Produced:O
Consumed:O
Produced:U
Produced:H
Consumed:H
Consumed:U
Produced:C
Consumed:C
Consumed:X
Produced:K
Produced:F
Produced:S
Consumed:S
Consumed:F
Consumed:K
Produced:K
Consumed:K
Consumed:N
Produced:L
Produced:M
Consumed:M
Produced:I
Consumed:I
Produced:Y
Produced:L
Consumed:L
Consumed:Y
Produced:N
Produced:W
Consumed:W
Consumed:N
Produced:J
Consumed:J
Produced:L
Consumed:L
Produced:Y
Produced:D
Consumed:D
Produced:D
Consumed:D
Produced:R
Produced:J
Consumed:J
Produced:G
Consumed:G
Produced:W
Produced:T
栈满了!
Consumed:T
Produced:Y
Consumed:Y
Produced:E
栈满了!
Produced:O
Consumed:E
Consumed:O
Produced:J
Consumed:J
Produced:M
栈满了!
Produced:I
Consumed:M
Consumed:I
Consumed:W
Consumed:R
Consumed:Y
Consumed:L
下面是我对wait(),notify()和notifyAll()等几个同步方法的一个总节:
(1)wait,notify,notifyAll必须在已经持有锁的情况下执行,所以它们只能出现在synchronized作用范围内,也就是出现在用synchronized修饰的方法或代码块中
(2)wait的作用:释放已持有的锁,把当前的线程加入到锁等待队列中
(3)notify的作用:唤醒等待队列中的第一个线程并把它移入锁申请队列中
(4)notifyAll的作用:唤醒等待队列中的所有的线程并把它们移入锁申请队列中
本文来自优快云博客,转载请标明出处:http://blog.youkuaiyun.com/loomman/archive/2009/03/11/3982085.aspx