面试必问!Java|面试必问!Java 多线程中两个线程交替执行,一个输出偶数,一个输出奇数
前言 楼主今天在面经上看到这个题,挺有意思,小小的题目对多线程的考量还挺多。大部分同学都会使用 synchronized 来实现。楼主今天带来另外两种优化实现,让你面试的时候,傲视群雄!
第一种 synchronized
class ThreadPrintDemo2 {
public static void main(String[] args) {
final ThreadPrintDemo2 demo2 = new ThreadPrintDemo2();
Thread t1 = new Thread(demo2::print1);
Thread t2 = new Thread(demo2::print2);
t1.start();
t2.start();
}public synchronized void print2() {
for (int i = 1;
i <= 100;
i += 2) {
System.out.println(i);
this.notify();
try {
this.wait();
Thread.sleep(100);
// 防止打印速度过快导致混乱
} catch (InterruptedException e) {
// NO
}
}
}public synchronized void print1() {
for (int i = 0;
i <= 100;
i += 2) {
System.out.println(i);
this.notify();
try {
this.wait();
Thread.sleep(100);
// 防止打印速度过快导致混乱
} catch (InterruptedException e) {
// NO
}
}
}
}
通过 synchronized 同步两个方法,每次只能有一个线程进入,每打印一个数,就释放锁,另一个线程进入,拿到锁,打印,唤醒另一个线程,然后挂起自己。循环反复,实现了一个最基本的打印功能。
但,如果你这么写,面试官肯定是不满意的。楼主将介绍一种更好的实现。
使用 CAS 实现
public class ThreadPrintDemo {static AtomicInteger cxsNum = new AtomicInteger(0);
static volatile boolean flag = false;
public static void main(String[] args) {Thread t1 = new Thread(() -> {
for (;
100 > cxsNum.get();
) {
if (!flag && (cxsNum.get() == 0 || cxsNum.incrementAndGet() % 2 == 0)) {
try {
Thread.sleep(100);
// 防止打印速度过快导致混乱
} catch (InterruptedException e) {
//NO
}System.out.println(cxsNum.get());
flag = true;
}
}
}
);
Thread t2 = new Thread(() -> {
for (;
100 > cxsNum.get();
) {
if (flag && (cxsNum.incrementAndGet() % 2 != 0)) {
try {
Thread.sleep(100);
// 防止打印速度过快导致混乱
} catch (InterruptedException e) {
//NO
}System.out.println(cxsNum.get());
flag = false;
}
}
}
);
t1.start();
t2.start();
}
}
我们通过使用 CAS,避免线程的上下文切换,然后呢,使用一个 volatile 的 boolean 变量,保证不会出现可见性问题,记住,这个 flag 一定要是 volatile 的,如果不是,可能你的程序运行起来没问题,但最终一定会出问题,而且面试官会立马鄙视你。
【面试必问!Java|面试必问!Java 多线程中两个线程交替执行,一个输出偶数,一个输出奇数】这样就消除了使用 synchronized 导致的上下文切换带来的损耗,性能更好。相信,如果你面试的时候,这么写,面试官肯定很满意。
但,我们还有性能更好的。
使用 volatile
class ThreadPrintDemo3{static volatile int num = 0;
static volatile boolean flag = false;
public static void main(String[] args) {Thread t1 = new Thread(() -> {
for (;
100 > num;
) {
if (!flag && (num == 0 || ++num % 2 == 0)) {try {
Thread.sleep(100);
// 防止打印速度过快导致混乱
} catch (InterruptedException e) {
//NO
}System.out.println(num);
flag = true;
}
}
}
);
Thread t2 = new Thread(() -> {
for (;
100 > num;
) {
if (flag && (++num % 2 != 0)) {try {
Thread.sleep(100);
// 防止打印速度过快导致混乱
} catch (InterruptedException e) {
//NO
}System.out.println(num);
flag = false;
}
}
}
);
t1.start();
t2.start();
}
}
我们使用 volatile 变量代替 CAS 变量,减轻使用 CAS 的消耗,注意,这里 ++num 不是原子的,但不妨碍,因为有 flag 变量控制。而 num 必须是 volatile 的,如果不是,会导致可见性问题。
到这里,如果你面试的时候这么写,那么,offer 就不远啦!哈哈?!!
彩蛋 如何翻转字符串?
class ReverseDemo {public static void main(String[] args) {String test = "abcdefg";
System.out.println(new StringBuilder(test).reverse());
char[] arr = test.toCharArray();
for (int i = arr.length - 1;
i >= 0;
i--) {
System.out.print(arr[i]);
}}
}
这个就比较简单了,两种方式,一个是 StringBuilder 的 reverse 方法,一个是转换成数组自己打印。自己转换性能更好,reverse 方法内部步骤更多。
好啦,希望大家面试成功!!
转载于:https://www.cnblogs.com/stateis0/p/9091254.html
推荐阅读
- JAVA(抽象类与接口的区别&重载与重写&内存泄漏)
- 事件代理
- Java|Java OpenCV图像处理之SIFT角点检测详解
- java中如何实现重建二叉树
- 数组常用方法一
- 【Hadoop踩雷】Mac下安装Hadoop3以及Java版本问题
- Java|Java基础——数组
- RxJava|RxJava 在Android项目中的使用(一)
- java之static、static|java之static、static final、final的区别与应用
- Java基础-高级特性-枚举实现状态机