JAVA三个线程依次打印ABC

一、一些简单概述

       多线程情形下对共享资源的访问是需要互斥的,比如对一个变量的读写,一个线程读写这个变量的时候,其它线程就不能对这个变量进行读写。Java提供了synchronized关键字来支持互斥,它既可以修饰需要访问共享资源的方法(称为同步方法),也可以直接包裹访问共享资源的代码块(称为同步块)。两种书写形式分别如下:

//同步方法
public synchronized void foo(){/*....*/}

//同步块
synchronized(lockObj){//lockObj称为对象锁

}

        此外共享资源必须设置为private类型,否则其它线程便可以直接访问共享资源,而不是通过synchronized的方法或代码片段来访问,就破坏了互斥条件。这一点很好理解,就不举例子了。

        当一个线程调用同步方法或者执行同步块时,其它任何线程的同步方法和同步块都将被阻塞(前提是使用的是同一个对象锁)。为什么呢?其实每个对象都有一个内置的锁,使用synchronized修饰方法时,就会对该类的实例对象进行上锁,这样同一个实例的其它同步方法都将被阻塞。synchronized用于同步块时,需要显示的提供一个用于加锁的对象lockObj,这样其它使用同一个lockObj的同步块都将被阻塞。

        线程之间除了互斥,有时候还需要协作,例如著名的生产者-消费者问题。synchronized不仅可以做到线程互斥,也可以做到线程协作,只是还需要wait()、notify()、notifyAll()三个方法。这三个方法用于线程的通信,都是lockObj的方法,其中wait()允许线程放弃锁,将线程阻塞并等待唤醒(直到有线程调用notify()或者notifyAll())。notiyf()用于唤醒等待的线程,调用后jvm会在该方法或者代码块完成后,从等待该锁线程中随机选取一个线程进行唤醒,唤醒后的线程可以从wait()后面开始继续运行。notifyAll()顾名思义就是唤醒所有等待该锁的线程。根据《Effective Java》一书的说法,一般情况下建议使用notifyAll(),并将wait()置于循环中。原因是所有等待该对象锁的线程(数量应该>2)唤醒条件可能是不一样的,仅仅用notify(),那么唤醒的线程可能并不符合唤醒的条件。将wait()置于循环中,就可以让被错误唤醒的线程继续判断条件,而不是直接执行wait()后面的代码。

二、实例

        先从经典的生产者消费者问题入手。题目很简单:一个生产者线程Producer,一个消费者线程Comsumer,一个产品队列Queue,其中生产者生产产品并放入产品队列,消费者从产品队列中取出产品消费,产品队列是共享资源,需要互斥访问,而且产品队列满的时候,生产者线程需要等待消费者取出产品,产品队列空的时候,消费者线程需要等待生产者放入产品。代码如下:

package main;

import java.util.LinkedList; 
import java.util.Queue; 
import java.util.Random; 


public class test { 
	public static void main(String args[]) { 
		Queue<Integer> buffer = new LinkedList<Integer>(); //产品队列
		int maxSize = 10; //产品队列最大容量
		Thread producer = new Producer(buffer, maxSize, "PRODUCER"); 
		Thread consumer = new Consumer(buffer, maxSize, "CONSUMER"); 
		producer.start(); 
		consumer.start(); 
    } 
} 


class Producer extends Thread 
{ 
  private Queue<Integer> queue;
  private int maxSize; 
  public Producer(Queue<Integer> queue, int maxSize, String name){ 
   super(name); 
   this.queue = queue;
   this.maxSize = maxSize; 
  } 
  @Override public void run() 
  { 
   while (true) 
    { 
     synchronized (queue) { 
      while (queue.size() == maxSize) { //先在循环中判断条件,避免错误唤醒
       try { 
        System.out .println("队列满,等待消费者取出产品。"); 
        queue.wait(); 
       } catch (Exception ex) { 
        ex.printStackTrace(); } 
       } 
       Random random = new Random(); 
       int i = random.nextInt(); 
       System.out.println("产品编号 : " + i); 
       queue.add(i); 
       queue.notifyAll(); //唤醒消费者线程取产品
      } 
     } 
    } 
   } 

class Consumer extends Thread { 
	 private Queue<Integer> queue;
	 private int maxSize; 
  public Consumer(Queue<Integer> queue, int maxSize, String name){ 
   super(name); 
   this.queue = queue; 
   this.maxSize = maxSize; 
  } 
  @Override public void run() { 
   while (true) { 
    synchronized (queue) { 
     while (queue.isEmpty()) {  //先在循环中判断条件,避免错误唤醒
      System.out.println("产品队列空,等待生产者生产产品。"); 
      try { 
       queue.wait(); 
      } catch (Exception ex) { 
       ex.printStackTrace(); 
      } 
     } 
     System.out.println("消费的产品编号 : " + queue.remove()); 
     queue.notifyAll(); //唤醒生产者线程生产产品
    } 
   } 
  } 
} 

        回到打印ABC的主题,在讲三个线程顺序打印ABC之前,先讲两个线程打印AB,线程A打印A,线程B打印B,并且顺序是ABABAB...。很明显A、B线程需要协作,A打印完毕后需要等待B打印完毕才能继续打印,同样B打印完毕后需要等待A打印完毕才能继续打印。代码如下:

package main;
class Print{
	private static boolean flag = false;//判断条件
	private static Object lockObj = new Object();//这里使用的是static,保证线程使用的是同一个对象锁。也可以仿照上面的例子中buffer,将对象锁通过构造函数传进来。
	public void printA(){
		int count = 10;
		while((count --) >0){
			synchronized(lockObj){
				try {
					while(flag == true){//在循环中判断条件,避免错误唤醒。
						lockObj.wait();
					}
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
					System.out.print('A');
					flag = true;//改变条件并唤醒等待线程
					lockObj.notifyAll();
			}	
		}
	}
    public void printB(){
    	int count =10;
    	while((count --) >0){
    		synchronized(lockObj){
    			try {
    				while(flag == false){
    					lockObj.wait();
    				}
    			} catch (InterruptedException e) {
    				// TODO Auto-generated catch block
    				e.printStackTrace();
    			}
    				System.out.print('B');
    				flag = false;
    				lockObj.notifyAll();
    		}
    	}
    	} 	
}
class PrintA implements Runnable{
	@Override
	public void run() {
		// TODO Auto-generated method stub
		new Print().printA();
	}
}
class PrintB implements Runnable{
	@Override
	public void run() {
		// TODO Auto-generated method stub
		new Print().printB();
	}
}
public class test {
	public static void main(String[] args) {
		// TODO Auto-generated method stub
          new Thread(new PrintA()).start();
          new Thread(new PrintB()).start();
	}
}

        实现了两个线程顺序打印AB,现在来看三个线程顺序打印ABC。两个线程,我们使用了一个对象锁,一个条件变量。三个线程可以增加一个布尔条件变量就可以了,不过这里条件变量我们用int表示,其中1表示该打印A,2表示该打印B,3表示该打印C,代码如下:

package main;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

class Print{
	private static int flag = 1;//条件变量
	private static Object synObj = new Object();//对象锁
	
	public  void printA() throws InterruptedException{
		for(int i =0;i<10;i++){
				synchronized(synObj){
				while(flag != 1){
					synObj.wait();
				}
						System.out.print('A');
						flag = 2;//改变条件并唤醒等待线程
						synObj.notifyAll();
				}
		}
	}
	public   void printB() throws InterruptedException{
		for(int i =0;i<10;i++){
			synchronized(synObj){
			while(flag != 2){
				synObj.wait();
			}
					System.out.print('B');
					flag = 3;
					synObj.notifyAll();
			}
	}
	}
	public   void printC() throws InterruptedException{
		for(int i =0;i<10;i++){
			synchronized(synObj){
			while(flag != 3){
				synObj.wait();
			}
					System.out.print('C');
					flag = 1;
					synObj.notifyAll();
			}
	}
    }
}
class ThreadA implements Runnable {
    @Override
    public void run() {
             try {
				new Print().printA();
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
        		}
    }
class ThreadB implements Runnable {
    @Override
    public void run() {
    	try {
			new Print().printB();
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
        	}
}
class ThreadC implements Runnable {
    @Override
    public void run() {
    	try {
			new Print().printC();
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
        	}
}
public class test {
	public static void main(String[] args) throws InterruptedException {
		// TODO Auto-generated method stub
		new Thread(new ThreadA()).start();
		new Thread(new ThreadB()).start();
		new Thread(new ThreadC()).start();
	}  
}
另外,网上有使用AutomaticInteger来实现的,可以参考下面的文章:基于AutomaticInteger实现三个线程打印ABC

猜你喜欢

转载自blog.csdn.net/gameloft9/article/details/51840654