口试题——并发环境下,次序打印ABC

[复制链接]
发表于 2025-10-24 15:07:43 | 显示全部楼层 |阅读模式

马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。

您需要 登录 才可以下载或查看,没有账号?立即注册

×
前言

太告急了,好久没有走出头试的阴影,脑筋一片空缺,明显许多方案,却只写出来一种信号量方法,这里做一个简单的纪录。
前置知识

死锁

线程组中的每一个线程都无法继续推进,都只能由其他线程举行叫醒。
死锁的须要条件

互斥、不剥夺、哀求与保持、循环等候。这里次序打印实在很好表现对循环等候的条件的粉碎,也就是按照编号举行次序上锁。
思绪

并发的环境下,次序打印,实在观察的就是对独占锁的使用。这里给出三种方法:信号量、Volatile、ReentrantLock。
Semaphore

口试的时间临场想出来的写法,只能说一点封装、面向对象的头脑看不到,只能说相称貌寝。这里实在是一种变相的生产者斲丧者模式。
  1.     @Test
  2.     public void testForSemaphore() {
  3.         Semaphore semaphoreA = new Semaphore (1);
  4.         Semaphore semaphoreB = new Semaphore (0);
  5.         Semaphore semaphoreC = new Semaphore (0);
  6.         new Thread (() -> {
  7.             for (int i = 0; i < 20; i++) {
  8.                 try {
  9.                     semaphoreA.acquire ();
  10.                     printf ('A');
  11.                 } catch (InterruptedException e) {
  12.                     throw new RuntimeException (e);
  13.                 } finally {
  14.                     semaphoreB.release ();
  15.                 }
  16.             }
  17.         }, "t1").start ();
  18.         new Thread (() -> {
  19.             for (int i = 0; i < 20; i++) {
  20.                 try {
  21.                     semaphoreB.acquire ();
  22.                     printf ('B');
  23.                 } catch (InterruptedException e) {
  24.                     throw new RuntimeException (e);
  25.                 } finally {
  26.                     semaphoreC.release ();
  27.                 }
  28.             }
  29.         }, "t2").start ();
  30.         new Thread (() -> {
  31.             for (int i = 0; i < 20; i++) {
  32.                 try {
  33.                     semaphoreC.acquire ();
  34.                     printf ('C');
  35.                 } catch (InterruptedException e) {
  36.                     throw new RuntimeException (e);
  37.                 } finally {
  38.                     semaphoreA.release ();
  39.                 }
  40.             }
  41.         }, "t3").start ();
  42.     }
复制代码
Volatile

通过Volatile对缓存的禁用,每次都去读取最新的token值,来决定放行哪一个活动。这实在有点CAS的味道。固然代码的简便性稍微强了一丢丢,一点封装的味道都没有。
  1.     volatile int token = 0;
  2.     @Test
  3.     public void testForVolatile() {
  4.         new Thread (() -> {
  5.             for (int i = 0; i < 20; i++) {
  6.                 while (token % 3 != 0) ;
  7.                 printf ('A');
  8.                 token++;
  9.             }
  10.         }, "t1").start ();
  11.         new Thread (() -> {
  12.             for (int i = 0; i < 20; i++) {
  13.                 while (token % 3 != 1) ;
  14.                 printf ('B');
  15.                 token++;
  16.             }
  17.         }, "t2").start ();
  18.         new Thread (() -> {
  19.             for (int i = 0; i < 20; i++) {
  20.                 while (token % 3 != 2) ;
  21.                 printf ('C');
  22.                 token++;
  23.             }
  24.         }, "t3").start ();
  25.     }
复制代码
这里附上使用Unsafe实现Volatile,实在就是手动举行缓存举行,克制指令重排,每次都去内存中读取最新值,监听变量的变革
  1.     @Test
  2.     public void testForUnsafe() throws NoSuchFieldException, IllegalAccessException {
  3.         Field field = Unsafe.class.getDeclaredField ("theUnsafe");
  4.         field.setAccessible (true);
  5.         Unsafe unsafe = (Unsafe) field.get (null);
  6.         ExecutorService threadPool = Executors.newFixedThreadPool (3);
  7.         for (int i = 0; i < 3; i++) {
  8.             int finalI = i;
  9.             threadPool.execute (() -> {
  10.                 for (int j = 0; j < 20; j++) {
  11.                     unsafe.loadFence ();
  12.                     while (num % 3 != finalI);
  13.                     char c = (char) ('A' + num % 3);
  14.                     printf (c);
  15.                     num++;
  16.                 }
  17.             });
  18.         }
  19.     }
复制代码
ReentrantLock

通过ReentrantLock,并对打印任务举行封装得以复用。每个线程都必要去抢占Lock,之后通过num的来决定打印的是什么。代码看起来舒服了许多,但是毕竟哪个才是打印A的线程?哪个才是B?哪个才是C呢?实在职责就不明白了,这里是面向结果编程,丢失了详细类的职责,以是也不是很美满。
  1.     private int num = 0;
  2.     class PrintTask implements Runnable {
  3.         @Override
  4.         public void run() {
  5.             for (int i = 0; i < 20; i++) {
  6.                 try {
  7.                     lock.lock ();
  8.                     char c = (char) ('A' + num % 3);
  9.                     num++;
  10.                     printf (c);
  11.                 } finally {
  12.                     lock.unlock ();
  13.                 }
  14.             }
  15.         }
  16.     }
  17.     @Test
  18.     public void testForLock() {
  19.         ExecutorService threadPool = Executors.newFixedThreadPool (3);
  20.         for (int i = 0; i < 3; i++) {
  21.             threadPool.execute (new PrintTask ());
  22.         }
  23.     }
复制代码
总结

只面过两次厂,看到大佬们实属告急,盼望以后能有所改善!真的很难熬,这么简单的题,却写出了最令人难以继承的代码,固然另有许多方式,只是不再写了

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
回复

使用道具 举报

登录后关闭弹窗

登录参与点评抽奖  加入IT实名职场社区
去登录
快速回复 返回顶部 返回列表