三个线程轮流打印ABC

时间:2025-02-15 15:59:03


这是一道比较经典的面试题:

三个线程打印ABC的问题,对(),()应用的最好理解。题目要求如下:
建立三个线程,A线程打印10次A,B线程打印10次B,C线程打印10次C,要求线程同时运行,交替打印10次ABC。这个问题用Object的wait(),notify()就可以很方便的解决。代码如下:

1、多线程if和where的区别:

就是用if判断的话,唤醒后线程会从wait之后的代码开始运行,但是不会重新判断if条件,直接继续运行if代码块之后的代码,而如果使用while的话,也会从wait之后的代码运行,但是唤醒后会重新判断循环条件,如果不成立再执行while代码块之后的代码块,成立的话继续wait。

2、

一.概述 wait()方法可以使线程进入等待状态,而notify()可以使等待的状态唤醒
wait,notify和notifyAll方法是Object类的成员函数,所以Java的任何一个对象都能够调用这三个方法。这三个方法主要是用于线程间通信,协调多个线程的运行。
大家要知道,在JAVA中,是没有类似于PV操作、进程互斥等相关的方法的。JAVA的进程同步都是通过synchronized来实现的,一般来说多个线程互斥访问某个资源,用synchronized就够了,但如果需要在线程间相互唤醒的话就需要借助(), ()了。

二.wait函数
打个比方:wait函数表示:我累了,想休息一会儿(同时本线程休眠),对象的锁你们拿去用吧,CPU也给你们。
需要注意一下几点:
1.调用了wait函数的线程会一直等待(本线程休眠),直到有其他线程调用了同一个对象的notify或者notifyAll方法才能被唤醒。如果没有其他线程调用该对象的notify或者notifyAll方法,则该线程将会永远等下去…
2.被唤醒并不代表立即获得对象的锁。
也就是说
函数必须在同步代码块中调用(也就是当前线程必须持有对象的锁)
注意 wait和sleep不同,sleep是不会释放对象锁的,但是wait是会释放对象锁的。

三.notify和notifyAll方法
还是先打个比方:女士们,先生们请注意了,这个锁的对象我即将用完,请大家醒醒(这是在唤醒该加锁对象的线程),准备一下,马上你们就能使用锁了。
需要注意一下几点:
/notifyAll方法也必须在同步代码块中调用(也就是调用线程必须持有对象的锁)
方法只会唤醒一个正在等待的线程(至于唤醒谁,不确定!),而notifyAll方法会唤醒所有正在等待的线程
3.调用notify和notifyAll方法后,当前线程并不会立即放弃锁的持有权,而必须要等待当前同步代码块执行完才会让出锁。
4.如果一个对象之前没有调用wait方法,那么调用notify方法是没有任何影响的。

四、lock Condition

一般而言,java中锁是在多线程环境下访问共享资源的一种手段。
一般的锁可以防止多个线程同时访问共享资源,但是也有些锁可以允许多个线程并发的访问共享资源,比如读写锁。

在java5之前,要实现锁,只有依靠synchronized关键字来隐式的获取和释放锁。
在java5中,提供了另一种显式地获取和释放锁的技术。可以通过接口来实现。

是一种重入锁。也就是说该锁支持一个线程对资源的重复加锁。在调用lock()方法时,已经获取到锁的线程,能够再次调用lock()方法获取锁而不被阻塞。


五、信号量

在Java的并发包中,Semaphore类表示信号量。Semaphore内部主要通过AQS(AbstractQueuedSynchronizer)实现线程的管理。Semaphore有两个构造函数,参数permits表示许可数,它最后传递给了AQS的state值。线程在运行时首先获取许可,如果成功,许可数就减1,线程运行,当线程运行结束就释放许可,许可数就加1。

当调用new Semaphore(2) 方法时,默认会创建一个非公平的锁的同步阻塞队列。



 

public class MyThread implements Runnable {
    private String name;
    private String prev;
    private String self;

    public MyThread(String name, String prev, String self) {
         = name;
         = prev;
         = self;
    }

    // 先取得两个锁,打印name,打印完 唤醒下一个需要自己的线程,并释放锁。本次打印完毕,释放prev锁,并进入等待状态。
    // 每个线程最开始的 notify是无用的,后面每次对应一个wait  相当于第一组的notify没用,第二组的notify激活第一组的wait,第三组的notify激活第二组的wait、最后已一组没有wait。
    @Override
    public void run() {
        int count = 3;
        while (count > 0) {
            synchronized (prev) {
                synchronized (self) {
                    (name);
                    count--;
                    ("   " + self + " notify");               //用完了 释放self锁
                    ();
                }
                try {
                    if (count != 0) {                                           // 最后一次 不需要wait了 如果wait会导致卡在这
                        ("   " + prev + " wait");
                        ();
                    }
                } catch (InterruptedException e) {
                    ();
                }
            }
        }
    }

    public static void main(String[] args) throws InterruptedException {

        String a = new String("a");
        String b = new String("b");
        String c = new String("c");

        MyThread t1 = new MyThread("A", c, a);
        MyThread t2 = new MyThread("B", a, b);
        MyThread t3 = new MyThread("C", b, c);

        // 依赖线程启动的顺序
        new Thread(t1).start();
        (50);
        new Thread(t2).start();
        (50);
        new Thread(t3).start();
        (50);
    }
}
A   a notify
B   b notify
   c wait
C   c notify
   a wait
A   a notify
   b wait
B   b notify
   c wait
C   c notify
   a wait
A   a notify
   b wait
B   b notify
C   c notify

我们的首要目的是要求:按照ThreadA->ThreadB->ThreadC->ThreadA循环执行三个线程。
那好:
ThreadA打印的条件:获取a,b
ThreadB打印的条件:获取b,c
ThreadC打印的条件:获取c,a
第一步:一开始ThreadA先获取a,然后获取b,打印完毕后,通知TheadB:“b资源我要用完了,你快醒醒吧!”。先释放b,再释放a.
第二步:因为b优先a被释放,所以ThreadB先执行(a被占用,ThreadC无法执行),先获取b,再获取c,打印b,先释放c,再释放b.
第三步:因为c优先b被释放,所以ThreadC先执行(b被占用,ThreadA无法执行),先获取c,再获取a,打印c,先释放a,再释放c.
就这样一步一步的按照顺序执行。

// 先取得两个锁,打印name,打印完 唤醒下一个需要自己的线程,并释放锁。本次打印完毕,释放prev锁,并进入等待状态。
// 每个线程最开始的 notify是无用的,后面每次对应一个wait  相当于第一组的notify没用,第二组的notify激活第一组的wait,第三组的notify激活第二组的wait、最后已一组没有wait。

上述方式会依赖线程的启动顺序。(借助于两个Synchronize/wait/notify实现)

方法2: 借助一个Synchronize锁 和 一个count 变量实现

public class MyThread implements Runnable {
    Object obLock;
    static int count = 3 * 2;
    String name;
    int threadId;

    public MyThread(Object o, String na, int id) {
        obLock = o;
        name = na;
        threadId = id;
    }


    public void run() {
        while (count > 0) {
            synchronized (obLock) {
                //(count + " " + threadId + " ");
                while (count % 3 == threadId) {
                    (name);
                    //(count);
                    count--;
                    ();
                }
                try {
                    if (count != 0)
                        ();
                } catch (InterruptedException e) {
                    ();
                }

            }
        }
    }

    public static void main(String[] args) throws InterruptedException {
        Object o = new Object();

        new Thread(new MyThread(o, "B", 2)).start(); //如果是0开始逐渐++  这里就是1
        new Thread(new MyThread(o, "C", 1)).start();
        (500);
        new Thread(new MyThread(o, "A", 0)).start();

    }
}

方法3:借助一个lock 和一个count变量实现。(不太推荐,会进行大量的无用尝试,拿到锁不符合解锁无限尝试)

public class MyThread implements Runnable {
    Lock lock;
    static int count = 0;
    String name;
    int threadId;

    public MyThread(Lock locko, String na, int id) {
        lock = locko;
        name = na;
        threadId = id;
    }

    public void run() {
        for (int i = 0; i < 3;  ){
            ();
            //(count);
            while (count % 3 == threadId) {
                (name);
                count++;
                i++;
            }
            ();
        }
    }

    public static void main(String[] args) throws InterruptedException {
        Lock o = new ReentrantLock();
        new Thread(new MyThread(o, "B", 1)).start(); //如果是0开始逐渐++  这里就是1
        new Thread(new MyThread(o, "C", 2)).start();
        (500);
        new Thread(new MyThread(o, "A", 0)).start();

    }
}

 方法3:(借助一个lock和condition实现,原理同方法二)

public class MyThread implements Runnable {
    static Lock lock = new ReentrantLock();
    static Condition condition = ();
    static int count = 3 * 2;
    String name;
    int threadId;

    public MyThread(String na, int id) {
        name = na;
        threadId = id;
    }

    public void run() {
        while (count > 0) {
            ();
            while (count % 3 == threadId) {
                (name);
                count--;
                ();
            }
            try {
                if (count != 0)
                    ();
            } catch (InterruptedException e) {
                ();
            }
            ();
        }
    }

    public static void main(String[] args) throws InterruptedException {
        Lock o = new ReentrantLock();
        new Thread(new MyThread("B", 2)).start(); //如果是0开始逐渐++  这里就是1
        new Thread(new MyThread("C", 1)).start();
        (500);
        new Thread(new MyThread("A", 0)).start();

    }
}

方法四,信号量(有点像方法1,很容易理解)

public class MyThread implements Runnable {
    Semaphore pre, self;
    String name;

    public MyThread(Semaphore pre, Semaphore self, String name) {
         = pre;
         = self;
         = name;
    }

    public void run() {
        for (int i = 0; i < 2; i++) {
            try {
                ();
                (name);
                ();

            } catch (InterruptedException e) {
                ();
            }

        }
    }

    public static void main(String[] args) throws InterruptedException {
        Semaphore semaphore1 = new Semaphore(0);
        Semaphore semaphore2 = new Semaphore(0);
        Semaphore semaphore3 = new Semaphore(1);
        new Thread(new MyThread(semaphore1, semaphore2, "B")).start();
        new Thread(new MyThread(semaphore2, semaphore3, "C")).start();
        (500);
        new Thread(new MyThread(semaphore3, semaphore1, "A")).start();

    }
}