关于java:最近遇到个Java面试题还有点意思呢

33次阅读

共计 7758 个字符,预计需要花费 20 分钟才能阅读完成。

最近看到一个面试题目,感觉挺有意思的,粗心如下:

ok,大家看到这个题,能够先了解下,这里启动了两个线程,a 和 b,然而尽管说 a 在 b 之前 start,不肯定就能够保障线程 a 的逻辑,能够先于线程 b 执行。

所以,这里的意思是,线程 a 和 b,执行程序互不烦扰,咱们不应该假设其中一个线程能够先于另外一个执行。

另外,既然是面试题,那惯例做法天然是不必上了,比方让 b 先 sleep 几秒钟之类的,如果真这么答,那可能面试就完结了吧。

好,咱们上面开始剖析解法。

可见性保障

程序里定义了一个全局变量,var = 1。

线程 a 会批改这个变量为 2,线程 b 则在变量为 2 时,执行本人的业务逻辑。

那么,这里首先,咱们要做的是,先讲 var 应用 volatile 润饰,保障多线程操作时的可见性。

public static volatile int var = 1;

解法剖析

通过后面的可见性保障的剖析,咱们晓得,要想达到目标,其实就是要保障:

a 中的对 var+ 1 的操作,须要先于 b 执行。

然而,当初的问题是,两个线程同时启动,不晓得谁先谁后,怎么保障 a 先执行,b 后执行呢?

让线程 b  先不执行,大略有两种思路:一种是阻塞该线程,一种是不阻塞该线程。阻塞的话,咱们能够想想,怎么阻塞一个线程。

大略有上面这些办法:

  • synchronized,取不到锁时,阻塞
  • java.util.concurrent.locks.ReentrantLock#lock,取不到锁时,阻塞
  • object.wait,取到 synchronized 了,然而因为一些条件不满足,执行不上来,调用 wait,将开释锁,并进入期待队列,线程暂停运行
  • java.util.concurrent.locks.Condition.await,和 object.wait 相似,只不过 object.wait 在 jvm 层面,应用 c ++ 实现,Condition.await 在 jdk 层面应用 java 语言实现
  • threadA.join(),期待对应的线程 threadA 执行实现后,本线程再持续运行;threadA 没完结,则以后线程阻塞;
  • CountDownLatch#await,在对应的 state 不为 0 时,阻塞
  • Semaphore#acquire(),在 state 为 0 时(即残余令牌为 0 时),阻塞
  • 其余阻塞队列、FutureTask 等等

如果不让线程进入阻塞,则个别能够让线程进入一个 while 循环,循环的退出条件,能够由线程 a 来批改,线程 a 批改后,线程 b 跳出循环。

比方:

volatile boolean stop = false;
while (!stop){...}

下面也说了这么多了,咱们实际上手写一写吧。

谬误解法 1 – 基于 wait

上面的思路是基于 wait、notify。

线程 b 间接 wait,线程 a 在批改了变量后,进行 notify。

public class Global1 {
    public static volatile int var = 1;
    public static final Object monitor = new Object();

    public static void main(String[] args) {Thread a = new Thread(() -> {
            // 1
            Global1.var++;
            // 2
            synchronized (monitor) {monitor.notify();
            }
        });
        Thread b = new Thread(() -> {
            // 3
            synchronized (monitor) {
                try {monitor.wait();
                } catch (InterruptedException e) {e.printStackTrace();
                }
            }
            // 4
            if (Global1.var == 2) {
                //do something;
                System.out.println(Thread.currentThread().getName() + "good job");
            }
        });
        a.start();
        b.start();}
}

大家感觉这个代码能行吗?

理论是不行的。因为理论的程序可能是:

 线程 a --1
线程 a --2
线程 b --1
线程 b --2

在线程 a-2 时,线程 a 去 notify,然而此时线程 b 还没开始 wait,所以此时的 notify 是没有任何成果的:

没人在等,notify 个锤子。

怎么批改,本计划才行得通呢?

那就是,批改线程 a 的代码,不要急着 notify,先等等。

Thread a = new Thread(() -> {
    Global1.var++;
    try {TimeUnit.SECONDS.sleep(2);
    } catch (InterruptedException e) {e.printStackTrace();
    }
    synchronized (monitor) {monitor.notify();
    }
});

然而这样的话,显著不适合,有舞弊嫌疑,也不优雅。

谬误解法 2 – 基于 condition 的 signal

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

public class Global1 {
    public static volatile int var = 1;
    public static final ReentrantLock reentrantLock = new ReentrantLock();
    public static final Condition condition = reentrantLock.newCondition();

    public static void main(String[] args) {Thread a = new Thread(() -> {
            Global1.var++;
            final ReentrantLock lock = reentrantLock;
            lock.lock();
            try {condition.signal();
            } finally {lock.unlock();
            }
        });
        Thread b = new Thread(() -> {
            final ReentrantLock lock = reentrantLock;
            lock.lock();
            try {condition.await();
            } catch (InterruptedException e) {e.printStackTrace();
            } finally {lock.unlock();
            }

            if (Global1.var == 2) {
                //do something;
                System.out.println(Thread.currentThread().getName() + "good job");
            }
        });
        a.start();
        b.start();}
}

这个计划应用了 Condition 对象来实现 object 的 notify、wait 成果。当然,这个也有同样的问题。

正确解法 1 – 基于谬误解法 2 进行改良

咱们看看,后面问题的本源在于,咱们线程 a,在去告诉线程 b 的时候,有可能线程 b 还没开始 wait,所以此时告诉生效。

那么,咱们是不是能够先等等,等线程 b 开始 wait 了,再去告诉呢?

Thread a = new Thread(() -> {
    Global1.var++;
    final ReentrantLock lock = reentrantLock;
    lock.lock();
    try {
        // 1
        while (!reentrantLock.hasWaiters(condition)) {Thread.yield();
        }
        condition.signal();} finally {lock.unlock();
    }
});

1 处代码,就是这个思维,在 signal 之前,判断以后 condition 上是否有 waiter 线程,如果没有,就死循环;如果有,才去执行 signal。

这个办法实测是可行的。

正确解法 2

 对正确解法 1,换一个 api,就变成了正确解法 2.

Thread a = new Thread(() -> {
    Global1.var++;
    final ReentrantLock lock = reentrantLock;
    lock.lock();
    try {
        // 1
        while (reentrantLock.getWaitQueueLength(condition) == 0) {Thread.yield();
        }
        condition.signal();} finally {lock.unlock();
    }
});

1 这里,获取 condition 上期待队列的长度,如果为 0,阐明没有期待者,则死循环。

正确解法 3 – 基于 Semaphore

刚开始,咱们初始化一个信号量,state 为 0。

线程 b 去获取信号量的时候,就会阻塞。

而后咱们线程 a 再去开释一个信号量,此时线程 b 就能够继续执行。

public class Global1 {
    public static volatile int var = 1;
    public static final Semaphore semaphore = new Semaphore(0);

    public static void main(String[] args) {Thread a = new Thread(() -> {
            Global1.var++;
            semaphore.release();});
        a.setName("thread a");
        Thread b = new Thread(() -> {
            try {semaphore.acquire();
            } catch (InterruptedException e) {e.printStackTrace();
            }

            if (Global1.var == 2) {
                //do something;
                System.out.println(Thread.currentThread().getName() + "good job");
            }
        });
        b.setName("thread b");
        a.start();
        b.start();}
}

正确解法 4 – 基于 CountDownLatch

public class Global1 {
    public static volatile int var = 1;
    public static final CountDownLatch countDownLatch = new CountDownLatch(1);

    public static void main(String[] args) {Thread a = new Thread(() -> {
            Global1.var++;
            countDownLatch.countDown();});
        a.setName("thread a");
        Thread b = new Thread(() -> {
            try {countDownLatch.await();
            } catch (InterruptedException e) {e.printStackTrace();
            }

            if (Global1.var == 2) {
                //do something;
                System.out.println(Thread.currentThread().getName() + "good job");
            }
        });
        b.setName("thread b");
        a.start();
        b.start();}
}

正确解法 5 – 基于 BlockingQueue

这里应用了 ArrayBlockingQueue,其余的阻塞队列也是能够的。

public class Global1 {
    public static volatile int var = 1;
    public static final ArrayBlockingQueue arrayBlockingQueue = new ArrayBlockingQueue<Object>(1);

    public static void main(String[] args) {Thread a = new Thread(() -> {
            Global1.var++;
            arrayBlockingQueue.offer(new Object());
        });
        a.setName("thread a");
        Thread b = new Thread(() -> {
            try {arrayBlockingQueue.take();
            } catch (InterruptedException e) {e.printStackTrace();
            }

            if (Global1.var == 2) {
                //do something;
                System.out.println(Thread.currentThread().getName() + "good job");
            }
        });
        b.setName("thread b");
        a.start();
        b.start();}
}

正确解法 6 – 基于 FutureTask

咱们也能够让线程 b 期待一个 task 的执行后果。

而线程 a 在执行完批改 var 为 2 后,执行该工作,工作执行实现后,线程 b 就会被告诉继续执行。

public class Global1 {
    public static volatile int var = 1;
    public static final FutureTask futureTask = new FutureTask<Object>(new Callable<Object>() {
        @Override
        public Object call() throws Exception {System.out.println("callable task");
            return null;
        }
    });

    public static void main(String[] args) {Thread a = new Thread(() -> {
            Global1.var++;
            futureTask.run();});
        a.setName("thread a");
        Thread b = new Thread(() -> {
            try {futureTask.get();
            } catch (InterruptedException | ExecutionException e) {e.printStackTrace();
            }

            if (Global1.var == 2) {
                //do something;
                System.out.println(Thread.currentThread().getName() + "good job");
            }
        });
        b.setName("thread b");
        a.start();
        b.start();}
}

正确解法 7 – 基于 join

这个可能是最简洁直观的解法:

public class Global1 {
    public static volatile int var = 1;

    public static void main(String[] args) {Thread a = new Thread(() -> {Global1.var++;});
        a.setName("thread a");
        Thread b = new Thread(() -> {
            try {a.join();
            } catch (InterruptedException e) {e.printStackTrace();
            }

            if (Global1.var == 2) {
                //do something;
                System.out.println(Thread.currentThread().getName() + "good job");
            }
        });
        b.setName("thread b");
        a.start();
        b.start();}
}

正确解法 8 – 基于 CompletableFuture

这个和第 6 种相似。都是基于 future。

public class Global1 {
    public static volatile int var = 1;
    public static final CompletableFuture<Object> completableFuture =
            new CompletableFuture<Object>();

    public static void main(String[] args) {Thread a = new Thread(() -> {
            Global1.var++;
            completableFuture.complete(new Object());
        });
        a.setName("thread a");
        Thread b = new Thread(() -> {
            try {completableFuture.get();
            } catch (InterruptedException | ExecutionException e) {e.printStackTrace();
            }

            if (Global1.var == 2) {
                //do something;
                System.out.println(Thread.currentThread().getName() + "good job");
            }
        });
        b.setName("thread b");
        a.start();
        b.start();}
}

非阻塞 – 正确解法 9 – 忙期待

这种代码量也少,只有线程 b 在变量为 1 时,死循环就行了。

public class Global1 {
    public static volatile int var = 1;

    public static void main(String[] args) {Thread a = new Thread(() -> {Global1.var++;});
        a.setName("thread a");
        Thread b = new Thread(() -> {while (var == 1) {Thread.yield();
            }

            if (Global1.var == 2) {
                //do something;
                System.out.println(Thread.currentThread().getName() + "good job");
            }
        });
        b.setName("thread b");
        a.start();
        b.start();}
}

非阻塞 – 正确解法 10– 忙期待

忙期待的计划很多,反正就是某个条件不满足时,不阻塞本人,阻塞了会开释 cpu,咱们就是不心愿开释 cpu 的。

比方像上面这样也能够:

public class Global1 {
    public static volatile int var = 1;
    public static final AtomicInteger atomicInteger =
            new AtomicInteger(1);

    public static void main(String[] args) {Thread a = new Thread(() -> {
            Global1.var++;
            atomicInteger.set(2);
        });
        a.setName("thread a");
        Thread b = new Thread(() -> {while (true) {boolean success = atomicInteger.compareAndSet(2, 1);
                if (success) {break;} else {Thread.yield();
                }
            }

            if (Global1.var == 2) {
                //do something;
                System.out.println(Thread.currentThread().getName() + "good job");
            }
        });
        b.setName("thread b");
        a.start();
        b.start();}
}

临时想了这么些,计划还是比拟多的,大家能够开动脑筋,头脑风暴吧。

看看你还有什么骚操作,能够在评论区留言。

正文完
 0