一、今天有家电面(具体哪家就不提了)问了这样的问题:
如何实现有三个线程a,b,c,这三个线程都有一个方法分别打印A、B、C,问怎么能实现依次打印ABC的功能。当时想着使用优先级或者join方法(肯定不行,因为你定的优先级不太听话不会按照你定的去依次执行,join方法去抢占其它线程的时间片也不好使,但是,去抢占主线程的时间片是好使的),其实就是利用线程池去解决这个问题:
1.思路:其实就是让三个线程依次运行,运用的是线程池来完成的,创建一个只有一个线程的线程池来操作不限数量的队列,也就是把线程都放在一个队列中,队列我们都知道是先进先出的,也就是说这个线程池中的线程其实真正工作的只有一个,其它队列中的线程都处于休眠状态(也就是sleep状态),当这个线程结束之后,也就是run方法结束之后,又从队列中拿出一个休眠的线程来唤醒,这样依次把队列中的所有线程处理完毕,这样这个线程池的线程并没有真正结束,如果线程池中没有待处理的线程,这个线程池会一直等待,等待下一次任务的提交,除非把线程池给shutdown掉,这样这个线程池的生命周期才算结束。
代码如下:
/**
* 我们要做的就是有三个线程a,b,c,这三个线程都有一个方法分别打印A、B、C,问怎么能实现依次打印ABC的功能
* @author zhmm
*/
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class ThreadShunXu {
public static void main(String[] args) {
//创建三个线程,a b c分别打印A B C三个字符
Thread a = new Thread(new Runnable() {
public void run() {
System.out.println(Thread.currentThread().getName() + "A");
}
});
Thread b = new Thread(new Runnable() {
public void run() {
System.out.println(Thread.currentThread().getName() + "B");
}
});
Thread c = new Thread(new Runnable() {
public void run() {
System.out.println(Thread.currentThread().getName() + "C");
}
});
//创建一个线程池,把这个三个线程装进这个线程池里
//线程池的实现是用的队列这个数据结构,因此先进先出,且每次只能弹出一个线程
//其实就是利用线程池完成每次工作的线程只有一个,且是队头线程
ExecutorService executor = Executors.newSingleThreadExecutor();
executor.submit(a);
executor.submit(b);
executor.submit(c);
//结束该线程池的生命周期
executor.shutdown();
}
}
运行结果:
2.使用线程的join方法区强占mian方法的时间片,导致主线程只能等待当前线程运行完毕之后才能继续往下运行,其它线程才能执行,从而达到依次执行三个线程,并依次打印ABC的目的。
/**
* @author zhmm
*/
public class PrintABC_1 {
public static void main(String[] args) throws InterruptedException {
while(true) {
A a1 = new A();
B b1 = new B();
C c1 = new C();
Thread a = new Thread(a1);
Thread b = new Thread(b1);
Thread c = new Thread(c1);
a.start();
a.join();
b.start();
b.join();
c.start();
c.join();
}
}
}
class A implements Runnable {
@Override
public void run() {
System.out.println("A");
}
}
class B implements Runnable {
@Override
public void run() {
System.out.println("B");
}
}
class C implements Runnable {
@Override
public void run() {
System.out.println("C");
}
}
那有人说,在其它线程里去抢占别的线程的时间片行不行,在一轮打印,而且不限制线程必须依次执行的时候是行的。
/**
* @author zhmm
* 此种方法只能保证一次顺序打印ABC,无法做到线程ABC顺序执行和循环打印
* 无法做到线程ABC顺序执行显而易见,就是使用join方法去抢占其它线程的时间片,肯定其它线程已经运行了
* 无法做到循环打印是因为,左下每一组中的线程仍然相互依赖,依次打印,但是不同组线程之间并没有依赖关系,因此无法保证顺序执行
*
* 使用join方法去解决这个问题也是可以的,不过是抢占的主线程的时间片
*/
public class PrintShunXABC {
public static void main(String[] args) {
ThreadA threadA = new ThreadA();
ThreadB threadB = new ThreadB(threadA);
ThreadC threadC = new ThreadC(threadB);
threadA.start();
threadB.start();
threadC.start();
}
}
class ThreadA extends Thread {
@Override
public void run() {
System.out.println("A");
}
}
class ThreadB extends Thread {
private ThreadA threadA;
public ThreadB(ThreadA threadA) {
this.threadA = threadA;
}
@Override
public void run() {
try {
threadA.join();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println("B");
}
}
class ThreadC extends Thread {
private ThreadB threadB;
public ThreadC(ThreadB threadB) {
this.threadB = threadB;
}
@Override
public void run() {
// TODO Auto-generated method stub
try {
threadB.join();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println("C");
}
}
但是,当执行许多次的时候是不行的,读者在mian方法中加个循环即可验证。
二、有三个方法A、B、C分别打印A、B、C这三个字符串,怎样增做吧这三个字符串按ABC依次打印
其实这道题和上面的一样,也可以使用线程池中的堵塞队列来完成,但是也可以使用锁+标记数+ wait() 和 notify() 方法来完成对线程的进行等待和唤醒。
逻辑思路:
这里主要用到了两个内容
1)创建一个标记 flag,让程序进行判断:
当flag != 1 时,A方法 进入等待,否则,执行方法flag=2,唤醒所有正在等待的其它线程;
当flag != 2 时,A方法 进入等待,否则,执行方法flag=2,唤醒所有正在等待的其它线程;
当flag != 3 时,A方法 进入等待,否则,执行方法flag=2,唤醒所有正在等待的其它线程。
2)在 object 类中有 wait() 和 notifyAll() 方法,可以对线程进行等待和唤醒的操作
代码实现:
打印类Printer:
/**
* Printer类里面提供三个方法,ABC,分别打印ABC这三个字符串
* @author zhmm
*
*/
public class Printer {
//创建一个标记数,标记数为1,2,3时分别表示运行该线程
private int flag = 1;
//使用synchronized隐式锁来锁住当前对象,当当前线程运行A方法时,该对象的BC方法堵塞,处于等待状态
//使用wait()和notifyAll()是当前线程等待和唤醒其它线程
public void A() throws InterruptedException {
synchronized (this) {
while(flag != 1) {
this.wait();
}
System.out.println("A");
flag = 2;
this.notifyAll();
}
}
public void B() throws InterruptedException {
synchronized (this) {
while(flag != 2) {
this.wait();
}
System.out.println("B");
flag = 3;
this.notifyAll();
}
}
public void C() throws InterruptedException {
synchronized (this) {
while(flag != 3) {
this.wait();
}
System.out.println("C");
flag = 1;
this.notifyAll();
}
}
}
测试类ABCShunXuTest:
public class ABCShunXuTest {
public static void main(String[] args) {
final Printer p = new Printer();
new Thread() {
public void run() {
while(true) {
try {
p.A();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}.start();
new Thread() {
public void run() {
while(true) {
try {
p.B();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}.start();
new Thread() {
public void run() {
while(true) {
try {
p.C();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}.start();
}
}
测试结果:
扩展:也可以使用显示锁Lock来来完成,只不过加锁和解锁需要自己手动完成:
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
/**
* 使用显式锁来进行对当前对象进行加锁
* @author zhmm
*
*/
public class Printer1 {
//添加互斥锁
private ReentrantLock r = new ReentrantLock();
private Condition c1 = r.newCondition();
private Condition c2 = r.newCondition();
private Condition c3 = r.newCondition();
private int flag = 1;
//使用Lock显式锁来锁住当前对象,当当前线程运行A方法时,该对象的BC方法堵塞,处于等待状态
//使用await()和signal()是当前线程等待和唤醒其它线程
//要注意的是使用显式锁需要自己进行加锁和解锁操作
public void A() throws InterruptedException {
r.lock();
while(flag != 1) {
c1.await();
}
System.out.println("A");
flag = 2;
c2.signal();
r.unlock();
}
public void B() throws InterruptedException {
r.lock();
while(flag != 2) {
c2.await();
}
System.out.println("B");
flag = 3;
c3.signal();
r.unlock();
}
public void C() throws InterruptedException {
r.lock();
while(flag != 3) {
c3.await();
}
System.out.println("C");
flag = 1;
c1.signal();
r.unlock();
}
}