V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
Cbdy
V2EX  ›  Java

求一个 Java 面试题的最佳实践

  •  
  •   Cbdy · 2021-01-25 12:24:37 +08:00 via Android · 4651 次点击
    这是一个创建于 1402 天前的主题,其中的信息可能已经有所发展或是发生改变。

    三个线程,分别输出 a 、b 、c,各输出 100 次,要求按 abc 的顺序输出

    期待输出为:abcabcabcabc...

    48 条回复    2021-02-02 11:26:02 +08:00
    Yi23
        1
    Yi23  
       2021-01-25 12:47:01 +08:00
    第一反应可以使用 lock.condition 实现,3 个线程每个线程打印自己的字符,然后唤醒下一个线程。
    线程 1 的方法类似如下
    类似这样
    ```
    lock.lock();
    while (n != a) {
    condition1.await(); // 线程 1 阻塞
    }
    // 输出 n
    n = b; // 修改 n=b 然后唤醒线程 2
    condition2.signal();
    lock.unlock();
    ```
    chenshun00
        2
    chenshun00  
       2021-01-25 13:00:29 +08:00
    public static void main(String[] args) {

    final Object xx1 = new Object();
    final Object xx2 = new Object();
    final Object xx3 = new Object();
    Thread a = new Thread(() -> {

    try {
    synchronized (xx1) {
    xx1.wait();
    }
    } catch (InterruptedException e) {
    e.printStackTrace();
    }
    int a1 = 0;
    while (a1 < 100) {
    System.out.print("a");
    a1++;

    synchronized (xx2) {
    xx2.notify();
    }
    try {
    synchronized (xx1) {
    xx1.wait();
    }
    } catch (InterruptedException e) {
    e.printStackTrace();
    }
    }
    });
    a.setDaemon(true);
    a.setName("aaa");
    Thread b = new Thread(() -> {
    try {
    synchronized (xx2) {
    xx2.wait();
    }
    } catch (InterruptedException e) {
    e.printStackTrace();
    }
    int b1 = 0;
    while (b1 < 100) {
    System.out.print("b");
    b1++;
    synchronized (xx3) {
    xx3.notify();
    }
    try {
    synchronized (xx2) {
    xx2.wait();
    }
    } catch (InterruptedException e) {
    e.printStackTrace();
    }
    }
    });
    b.setDaemon(true);
    b.setName("bbb");
    Thread c = new Thread(() -> {
    try {
    synchronized (xx3) {
    xx3.wait();
    }
    } catch (InterruptedException e) {
    e.printStackTrace();
    }
    int c1 = 0;
    while (c1 < 100) {
    System.out.println("c");
    c1++;
    synchronized (xx1) {
    xx1.notify();
    }
    try {
    synchronized (xx3) {
    xx3.wait();
    }
    } catch (InterruptedException e) {
    e.printStackTrace();
    }
    }
    });
    c.setDaemon(true);
    c.setName("ccc");
    a.start();
    b.start();
    c.start();

    synchronized (xx1) {
    xx1.notify();
    }

    try {
    Thread.sleep(100L);
    } catch (InterruptedException e) {
    e.printStackTrace();
    }
    }

    }
    mawerss1
        3
    mawerss1  
       2021-01-25 13:06:56 +08:00
    public class Volatile implements Runnable{


    private volatile int state = 0;

    public static void main(String[] args) throws InterruptedException {
    for (int i = 0; i < 3; i++) {
    Thread thread = new Thread(new Volatile());
    thread.start();
    thread.join();
    }
    }

    @Override
    public void run() {
    int count = 0;
    while (true) {
    if (count >= 100) {
    break;
    }
    if (state == 0) {
    System.out.println("a");
    state = 1;
    }
    if (state == 1) {
    System.out.println("b");
    state = 2;
    }
    if (state == 2) {
    System.out.println("c");
    state = 0;
    }
    count++;
    }
    }
    }
    chenshun00
        4
    chenshun00  
       2021-01-25 13:12:25 +08:00
    @mawerss1 好像偏题了
    mytudan
        5
    mytudan  
       2021-01-25 13:26:22 +08:00
    class Abc {
    private int n;
    // 标志位,控制执行顺序,0 执行 a,1 执行 b 2 执行 c
    private volatile int type;
    // 锁标志
    private final Object object;

    public Abc(int n) {
    this.n = n;
    object = new Object();
    type = 0;
    }

    public void a(Runnable a) throws InterruptedException {
    for (int i = 0; i < n; i++) {
    synchronized (object) {
    while (!(type == 0)) {
    object.wait();
    }
    a.run();
    type = 1;
    object.notifyAll();
    }
    }
    }

    public void b(Runnable b) throws InterruptedException {
    for (int i = 0; i < n; i++) {
    synchronized (object) {
    while (!(type == 1)) {
    object.wait();
    }
    b.run();
    type = 2;
    object.notifyAll();
    }
    }
    }

    public void c(Runnable c) throws InterruptedException {
    for (int i = 0; i < n; i++) {
    synchronized (object) {
    while (!(type == 2)) {
    object.wait();
    }
    c.run();
    type = 0;
    object.notifyAll();
    }
    }
    }

    }
    mawerss1
        6
    mawerss1  
       2021-01-25 13:34:29 +08:00
    @chenshun00 哈哈 全错了
    chendy
        7
    chendy  
       2021-01-25 13:35:06 +08:00
    生产者-消费者模型
    a 生产 b 消费,b 生产 c 消费,c 生产 a 消费
    leafre
        8
    leafre  
       2021-01-25 13:41:17 +08:00
    ReentrantLock
    hitmanx
        9
    hitmanx  
       2021-01-25 13:46:59 +08:00
    把它看成三个独立的生产者消费者,用独立的 sync objects
    woshiaha
        10
    woshiaha  
       2021-01-25 13:49:04 +08:00   ❤️ 1
    老题了 用一个原子数当锁 每次有输出原子数都加一 抢到锁才可以输出
    a 线程只在原子数%3 为 0 时输出
    b 线程只在原子数%3 为 1 时输出
    c 线程只在原子数%3 为 2 时输出
    mytudan
        11
    mytudan  
       2021-01-25 13:49:26 +08:00
    我上面那个是照着 leetcode 上的题改的
    mawerss1
        12
    mawerss1  
       2021-01-25 13:51:31 +08:00
    @chenshun00 state 变量改成 static,join 方法删掉,改成用 CountDownLatch
    yazinnnn
        13
    yazinnnn  
       2021-01-25 13:56:11 +08:00   ❤️ 2
    有个问题,这个多线程有什么意义?
    Vendettar
        14
    Vendettar  
       2021-01-25 14:09:30 +08:00
    ```java
    public static void main(String[] args) {
    ReentrantLock lock = new ReentrantLock(true);
    for (int i = 0; i < 3; i++) {
    int order = i;
    new Thread(()->{
    for (int j = 0; j < 100; j++) {
    lock.lock();
    System.out.println(Thread.currentThread().getName() + ":" + (char)('a'+order));
    lock.unlock();
    }
    }, "Thread-" + i).start();
    }
    }
    ```
    Vendettar
        15
    Vendettar  
       2021-01-25 14:10:16 +08:00
    直接一个公平锁 new ReentrantLock(true)按顺序来就可以
    Takamine
        16
    Takamine  
       2021-01-25 14:13:50 +08:00 via Android
    刚撸的一个,应该算是比较传统的解法吧。
    https://paste.ubuntu.com/p/zbDMK6qJQm/
    micean
        17
    micean  
       2021-01-25 14:14:46 +08:00
    每个线程调用这个类就行了
    class ABCPrinter{
    int[] arr = new int[]{0, 0, 0};
    int i = 0;

    public synchronized void addChar(char ch){
    arr[(int)(ch - 'a')]++;
    print();
    }

    void print(){
    while(arr[i] > 0){

    打印((char)(arr[i] + 'a'));
    arr[i]--;
    i = (i + 1)%3

    }
    }

    }
    ocean1477
        18
    ocean1477  
       2021-01-25 14:14:54 +08:00
    @Vendettar 直接不对。
    Rorysky
        19
    Rorysky  
       2021-01-25 14:19:28 +08:00
    synchronized 一把梭
    Vendettar
        21
    Vendettar  
       2021-01-25 14:20:52 +08:00
    @ocean1477 咋不对呢 我这里输出是对的啊..
    ocean1477
        22
    ocean1477  
       2021-01-25 14:24:08 +08:00
    @Vendettar 这个是无法保证顺序的,1,2,3 三个线程可以同时进,争抢一个锁。
    Vendettar
        23
    Vendettar  
       2021-01-25 14:28:08 +08:00
    @ocean1477 公平锁不会争抢 你输出一下试试 顺序不会乱
    ocean1477
        24
    ocean1477  
       2021-01-25 14:29:29 +08:00
    @Vendettar 我这会试了下,会乱的,多试几次。
    Thread-0:a
    Thread-0:a
    Thread-0:a
    Thread-0:a
    Thread-0:a
    Thread-1:b
    Thread-2:c
    Thread-0:a
    Thread-1:b
    Thread-2:c
    Thread-0:a
    Thread-1:b
    Thread-2:c
    Thread-0:a
    Thread-1:b
    ocean1477
        25
    ocean1477  
       2021-01-25 14:32:03 +08:00
    @Vendettar 看来老兄要重新看下公平锁了,公平锁≠顺序
    Vendettar
        26
    Vendettar  
       2021-01-25 14:33:34 +08:00
    @ocean1477 热 我试出来了 的确会乱
    ocean1477
        27
    ocean1477  
       2021-01-25 14:38:19 +08:00
    @Vendettar 哈哈,我无意指出,抱歉。
    kx5d62Jn1J9MjoXP
        28
    kx5d62Jn1J9MjoXP  
       2021-01-25 14:39:16 +08:00
    用三个 Semaphore 就行了吧
    Vendettar
        29
    Vendettar  
       2021-01-25 14:42:26 +08:00
    @ocean1477 感谢指出
    sczero
        30
    sczero  
       2021-01-25 14:43:56 +08:00
    public static void main(String[] args) throws InterruptedException {
    final AtomicReference<String> tag = new AtomicReference<>("a");
    final int total = 100;
    new Thread(() -> {
    int count = 0;
    while (count < total) {
    if (tag.get().equals("a")) {
    System.out.print("a");
    tag.set("b");
    count++;
    }
    }
    }).start();
    new Thread(() -> {
    int count = 0;
    while (count < total) {
    if (tag.get().equals("b")) {
    System.out.print("b");
    tag.set("c");
    count++;
    }
    }
    }).start();
    new Thread(() -> {
    int count = 0;
    while (count < total) {
    if (tag.get().equals("c")) {
    System.out.println("c");
    tag.set("a");
    count++;
    }
    }
    }).start();
    }
    kx5d62Jn1J9MjoXP
        31
    kx5d62Jn1J9MjoXP  
       2021-01-25 14:52:58 +08:00   ❤️ 1
    public static void main(String[] args) {
    Semaphore[] res = {
    new Semaphore(1),
    new Semaphore(0),
    new Semaphore(0),
    };

    for (int i = 0; i < 3; i++) {
    Semaphore current = res[i];
    Semaphore next = res[(i + 1) % 3];
    final char ch = (char) (i + 'a');
    new Thread(new Runnable() {
    @Override
    public void run() {
    for (int j = 0; j < 100; j++) {
    try {
    current.acquire();
    System.out.print(ch);
    next.release();
    } catch (InterruptedException e) {
    e.printStackTrace();
    }

    }
    }
    }).start();
    }

    }
    Cyron
        32
    Cyron  
       2021-01-25 15:01:38 +08:00
    简单公平锁实现
    ```
    public class ReentrantLockDemo implements Runnable {

    private static ReentrantLock lock = new ReentrantLock(true);

    private String content;

    public ReentrantLockDemo(String content) {
    this.content = content;
    }

    @Override
    public void run() {
    while (true) {
    try {
    lock.lock();
    System.out.println(content);
    Thread.sleep(1000);
    } catch (InterruptedException e) {
    e.printStackTrace();
    } finally {
    lock.unlock();
    }
    }
    }

    public static void main(String[] args) {
    ReentrantLockDemo a = new ReentrantLockDemo("a");
    ReentrantLockDemo b = new ReentrantLockDemo("b");
    ReentrantLockDemo c = new ReentrantLockDemo("c");
    Thread threadA = new Thread(a);
    Thread threadB = new Thread(b);
    Thread threadC = new Thread(c);
    threadA.start();
    threadB.start();
    threadC.start();
    }
    }
    ```
    zzh7982
        33
    zzh7982  
       2021-01-25 15:10:55 +08:00
    不用加锁,join 就行了,刚测试了一把 没问题

    https://gist.github.com/zzh7982/249883f379b50c5ae50eacd48736b5e2
    kikione
        34
    kikione  
       2021-01-25 15:17:07 +08:00
    我想到两个方案,第一个是加锁。 第二个是 join,b 等 a ,c 等 a 。
    sczero
        35
    sczero  
       2021-01-25 15:20:33 +08:00
    @zzh7982 新建了 300 个线程......至于吗....
    zzh7982
        36
    zzh7982  
       2021-01-25 15:24:55 +08:00   ❤️ 1
    @sczero 啊..这... 300 个线程都能控制顺序 这不显得水平高吗[dog]
    kikione
        37
    kikione  
       2021-01-25 15:26:17 +08:00
    for (int i=0;i<100;i++){
    Thread1 thread1 = new Thread1();
    Thread2 thread2 = new Thread2();
    Thread3 thread3 = new Thread3();
    thread1.start();
    thread1.join();
    thread2.start();
    thread2.join();
    thread3.start();
    }
    sampeng
        38
    sampeng  
       2021-01-25 15:34:23 +08:00   ❤️ 1
    都是有锁方案啊。。我提一个无锁和竞争的方案
    三个队列。分别收 a,b,c 。
    输出就是轮询这 3 个队列就好了。。一定都是有序的
    johnson666
        39
    johnson666  
       2021-01-25 16:05:58 +08:00
    原子变量

    public class Test {
    public static void main(String[] args) {
    AtomicInteger at = new AtomicInteger();
    for (int i = 0; i < 3; i++) {
    MyThread t = new MyThread(at, i);
    t.start();
    }
    }
    }

    class MyThread extends Thread {
    private AtomicInteger at;
    private int index;

    public MyThread(AtomicInteger at, int index) {
    this.at = at;
    this.index = index;
    }

    @Override
    public void run() {
    while(true) {
    if(at.get() % 3 == index) {
    System.out.print((char)('a' + index));
    at.incrementAndGet();
    }
    }
    }
    }
    Youen
        40
    Youen  
       2021-01-25 16:53:22 +08:00
    YoongRii
        41
    YoongRii  
       2021-01-25 17:14:22 +08:00
    用原子变量%3 这种解法比较常见,但是需要线程忙等,提供一种用线程池方式实现的思路:

    public static void main(String[] args) throws InterruptedException {
    ExecutorService executorService1 = Executors.newSingleThreadExecutor();
    ExecutorService executorService2 = Executors.newSingleThreadExecutor();
    ExecutorService executorService3 = Executors.newSingleThreadExecutor();

    Runnable[] rs = new Runnable[3];

    rs[0] = () -> {
    System.out.println("A");
    executorService2.submit(rs[1]);
    };

    rs[1] = () -> {
    System.out.println("B");
    executorService2.submit(rs[2]);
    };

    rs[2] = new Runnable() {
    private int a = 1;

    public void run() {
    System.out.println("C");
    if (a++ < 100)
    executorService3.submit(rs[0]);
    }
    };

    executorService1.submit(rs[0]);
    }
    donggexiongdi
        42
    donggexiongdi  
       2021-01-25 17:19:22 +08:00
    public void printAbc(int count) {
    for (int i = 0; i < count; i++) {
    CompletableFuture.runAsync(() -> {
    System.out.println("A");
    }).thenRun(() -> {
    System.out.println("B");
    }).thenRun(() -> {
    System.out.println("C");
    });
    System.out.println("-------");
    }
    }
    donggexiongdi
        43
    donggexiongdi  
       2021-01-25 17:22:06 +08:00
    我擦 不对
    cubecube
        44
    cubecube  
       2021-01-26 01:26:51 +08:00
    @sampeng
    1.队列也需要锁,无锁队列的话,如果某个线程输入太慢,跟不上,consumer 也得等待?
    2.另外,题目说得三个线程个,consumer 还得偷线程或者揉进到生产者线程的执行过程中。

    以上条件要全写对,比状态量的复杂。
    sampeng
        45
    sampeng  
       2021-01-26 09:34:19 +08:00
    @cubecube
    1.我说的无所锁方案是代码不需要明确的写锁的逻辑。也是一个比较自然的做法。而且队列的这种方式并没有资源竞争,只是 consumer 等待而已。
    2.三个线程难道就不能有主线程了?谁起的这 3 个线程呢?当然是主线程上直接循环啊。又不是生产环境。
    Vendettar
        46
    Vendettar  
       2021-01-26 11:04:01 +08:00
    @Cyron 三个线程首次启动抢锁的话(3 线程都没有 sleep 过),a 抢 1b 抢 2 就没问题,如果 b 抢 1 那后面 99 次都是 b 先输出了
    fantastM
        47
    fantastM  
       2021-01-27 14:30:52 +08:00
    #10 说的方案是可行的,并且是基于 CAS 无锁的

    https://gist.github.com/fantasticmao/f78ae0016a81877cf5019d9c22c81c73
    SkyLine7
        48
    SkyLine7  
       2021-02-02 11:26:02 +08:00
    @woshiaha 是的
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   4753 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 31ms · UTC 10:05 · PVG 18:05 · LAX 02:05 · JFK 05:05
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.