Java多线程:用三个线程控制循环输出10次ABC


题目:有A,B,C三个线程, A线程输出A, B线程输出B, C线程输出C,要求, 同时启动三个线程, 按顺序输出ABC, 循环10次。

解题思路:要按顺序输出ABC, 循环10次,就要控制三个线程同步工作,也就是说要让三个线程轮流输出,直到10个ABC全部输出则结束线程。这里用一个Lock对象来控制三个线程的同步。用一个int型变量state标识由那个线程输出。

package com.thread;

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

public class ABC {
    private static int state = 0;

    public static void main(String[] args) {
        final Lock l = new ReentrantLock();
        
        Thread A = new Thread(new Runnable(){
            @Override
            public void run() {
                while (state<=30) {
                    l.lock();
                    if(state%3==0){
                        System.out.println("A");
                        state ++;
                    }
                    l.unlock();
                }
            }
        });
        Thread B = new Thread(new Runnable(){
            @Override
            public void run() {
                while (state<=30) {
                    l.lock();
                    if(state%3==1){
                        System.out.println("B");
                        state ++;
                    }
                    l.unlock();
                }
            }
        });
        Thread C = new Thread(new Runnable(){
            @Override
            public void run() {
                while (state<=30) {
                    l.lock();
                    if(state%3==2){
                        System.out.println("C");
                        state ++;
                    }
                    l.unlock();
                }
            }
        });
        A.start();
        B.start();
        C.start();
    }

}


有A,B,C三个线程, A线程输出A, B线程输出B, C线程输出C

要求, 同时启动三个线程, 按顺序输出ABC, 循环10次

这是一个多线程协同的问题, 本身多线程是没有执行顺序的, 顺序不一定, Java在concurrent里面提供了多线程同步的支持

使用ReentrantLock来解决, 还有个state整数用来判断轮到谁执行了

复制代码
 1 import java.util.concurrent.locks.Lock;
 2 import java.util.concurrent.locks.ReentrantLock;
 3 
 4 public class ABC {
 5     private static Lock lock = new ReentrantLock();//通过JDK5中的锁来保证线程的访问的互斥
 6     private static int state = 0;
 7     
 8     static class ThreadA extends Thread {
 9         @Override
10         public void run() {
11             for (int i = 0; i < 10;) {
12                 lock.lock();
13                 if (state % 3 == 0) {
14                     System.out.print("A");
15                     state++;
16                     i++;
17                 }
18                 lock.unlock();
19             }
20         }
21     }
22     
23     static class ThreadB extends Thread {
24         @Override
25         public void run() {
26             for (int i = 0; i < 10;) {
27                 lock.lock();
28                 if (state % 3 == 1) {
29                     System.out.print("B");
30                     state++;
31                     i++;
32                 }
33                 lock.unlock();
34             }
35         }
36     }
37     
38     static class ThreadC extends Thread {
39         @Override
40         public void run() {
41             for (int i = 0; i < 10;) {
42                 lock.lock();
43                 if (state % 3 == 2) {
44                     System.out.print("C");
45                     state++;
46                     i++;
47                 }
48                 lock.unlock();
49             }
50         }
51     }
52     
53     public static void main(String[] args) {
54         new ThreadA().start();
55         new ThreadB().start();
56         new ThreadC().start();
57     }
58     
59 }
复制代码

使用lock来保证只有一个线程在输出操作, 要保证了state不会被两个线程同时修改, 思路简单

还可以使用condition, condition的效率可能会更高一些, await会释放lock锁, condition的await和signal与object的wait和notify方法作用类似

signal() 代替了 notify(),await() 代替了 wait(),signalAll() 代替 notifyAll()。
await()  是等待的意思,调它 就是  阻塞写线程。
signal 是发出信号的意思,调它 就是 唤醒读线程。

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

import javax.xml.stream.events.StartDocument;

public class ABC2 {
	private static Lock lock = new ReentrantLock();
	private static int count = 0;
	private static Condition A = lock.newCondition();
	private static Condition B = lock.newCondition();
	private static Condition C = lock.newCondition();

	static class ThreadA extends Thread {

		@Override
		public void run() {
			lock.lock();
			try {
				for (int i = 0; i < 10; i++) {
					while (count % 3 != 0)
						A.await(); //如果不满足while条件,将本线程挂起
					System.out.print("A");
					count++;
					B.signal(); // A线程执行后,唤醒下一个线程B
				}
			} catch (InterruptedException e) {
				e.printStackTrace();
			} finally {
				lock.unlock();
			}
		}

	}

	static class ThreadB extends Thread {

		@Override
		public void run() {
			lock.lock();
			try {
				for (int i = 0; i < 10; i++) {
					while (count % 3 != 1)
						B.await();//如果不满足while条件, 将本线程挂起
					System.out.print("B");
					count++;
					C.signal();// B线程执行后,唤醒下一个线程C
				}
			} catch (InterruptedException e) {
				e.printStackTrace();
			} finally {
				lock.unlock();
			}
		}

	}

	static class ThreadC extends Thread {

		@Override
		public void run() {
			lock.lock();
			try {
				for (int i = 0; i < 10; i++) {
					while (count % 3 != 2)
						C.await();//如果不满足while条件, 将本线程挂起
					System.out.println("C");
					count++;
					A.signal();// C线程执行后,唤醒下一个线程A
				}
			} catch (InterruptedException e) {
				e.printStackTrace();
			} finally {
				lock.unlock();
			}
		}

	}

	public static void main(String[] args) throws InterruptedException {
		ThreadA threadA =new ThreadA();
		ThreadB threadB=new ThreadB();
		ThreadC threadC = new ThreadC();
		threadA.start();
		threadB.start();
		threadC.start();
		threadC.join();//让C线程执行完后在输出cout值否则可能cout在ABC线程都未完成时就输出结果。
		System.out.println(count);
	}
}


使用信号量也可以, 这个思路最简单, 整个代码也比较简洁

复制代码
 1 import java.util.concurrent.Semaphore;
 2 
 3 public class ABC3 {
 4     private static Semaphore A = new Semaphore(1);
 5     private static Semaphore B = new Semaphore(1);
 6     private static Semaphore C = new Semaphore(1);
 7     
 8     static class ThreadA extends Thread {
 9 
10         @Override
11         public void run() {
12             try {
13                 for (int i = 0; i < 10; i++) {
14                     A.acquire();
15                     System.out.print("A");
16                     B.release();
17                 }
18             } catch (InterruptedException e) {
19                 e.printStackTrace();
20             }
21         }
22         
23     }
24     
25     static class ThreadB extends Thread {
26 
27         @Override
28         public void run() {
29             try {
30                 for (int i = 0; i < 10; i++) {
31                     B.acquire();
32                     System.out.print("B");
33                     C.release();
34                 }
35             } catch (InterruptedException e) {
36                 e.printStackTrace();
37             }
38         }
39         
40     }
41     
42     static class ThreadC extends Thread {
43 
44         @Override
45         public void run() {
46             try {
47                 for (int i = 0; i < 10; i++) {
48                     C.acquire();
49                     System.out.println("C");
50                     A.release();
51                 }
52             } catch (InterruptedException e) {
53                 e.printStackTrace();
54             }
55         }
56         
57     }
58     
59     public static void main(String[] args) throws InterruptedException {
60         B.acquire(); C.acquire(); // 开始只有A可以获取, BC都不可以获取, 保证了A最先执行
61         new ThreadA().start();
62         new ThreadB().start();
63         new ThreadC().start();
64     }
65 }
复制代码

注意:

lock是需要lock所有者去释放的, 即谁lock, 谁释放, 不可以跨线程, 会报java.lang.IllegalMonitorStateException;

semaphore是没有所有者的说法, 可以跨线程释放和获取.

 

这是一道java笔试题, 多线程的问题现在越来越多的出现在笔试中, 要好好学习.

水平有限, 如有错漏, 请指针, 欢迎拍砖, 共同探讨!

参考文献:

  1. 深入浅出Java Concurrency:http://www.blogjava.net/xylz/archive/2010/07/08/325587.html

Lock的await/singal 和 Object的wait/notify 的区别

在使用Lock之前,我们都使用Object 的wait和notify实现同步的。举例来说,一个producer和consumer,consumer发现没有东西了,等待,produer生成东西了,唤醒。

线程consumer 线程producer
synchronize(obj){ 
    obj.wait();//没东西了,等待
}
synchronize(obj){ 
    obj.notify();//有东西了,唤醒 
}

有了lock后,世道变了,现在是:

lock.lock(); 
condition.await(); 
lock.unlock();
lock.lock(); 
condition.signal(); 
lock.unlock();

为了突出区别,省略了若干细节。区别有三点:

  1. 1. lock不再用synchronize把同步代码包装起来;
  2. 2. 阻塞需要另外一个对象condition;
  3. 3. 同步和唤醒的对象是condition而不是lock,对应的方法是await和signal,而不是wait和notify。

为什么需要使用condition呢?简单一句话,lock更灵活。以前的方式只能有一个等待队列,在实际应用时可能需要多个,比如读和写。为了这个灵活性,lock将同步互斥控制和等待队列分离开来,互斥保证在某个时刻只有一个线程访问临界区(lock自己完成),等待队列负责保存被阻塞的线程(condition完成)。

通过查看ReentrantLock的源代码发现,condition其实是等待队列的一个管理者,condition确保阻塞的对象按顺序被唤醒。

在Lock的实现中,LockSupport被用来实现线程状态的改变,后续将更进一步研究LockSupport的实现机制。


你可能感兴趣的:(JAVA)