http://www.iteye.com/topic/1117703
一道经典线程面试题的4种解法
一个关于线程的经典面试题,要求用三个线程,按顺序打印1,2,3,4,5.... 71,72,73,74, 75.
线程1先打印1,2,3,4,5, 然后是线程2打印6,7,8,9,10, 然后是线程3打印11,12,13,14,15. 接着再由线程1打印16,17,18,19,20....以此类推, 直到线程3打印到75。
简单的分析一下题目要求。从题目考察的角度看,题目考的是线程同步知识,既然是顺序打印,那么也就是在某一个时刻只有一个线程处于运行状态,其他两个线程必须出于等待状态。一个线程在一轮打印中,打印5个数字后,必须转为等待状态。另一个线程接着打印下5个数字,如此类推,直到75个数字全部打完。如何判断某一个线程应该打印,这里给每一个线程分配一个id号,0,1,2,如果需要打印的数字c / 5 %3 == id,表示当前线程需要打印一轮。每个线程都必须要判断15次,采用这种方法,算法的复杂度是15*3,共45个循环。这是打印程序中的基本逻辑,那怎么实现同步呢?请看下面的四种解法。
解法一
既然是同步的打印,很自然的想到synchronized关键字,那么只要把线程的run方法声明为synchronized的,就可以实现线程的同步。这是这个程序的基本解法。具体的代码如下:
Java代码
public class NumberPrinter extends Thread {
static int c = 0;
private int id;
@Override
public synchronized void run() {
while (c < 75) {
if (c / 5 % 3 == id) {
for (int i = 0; i < 5; i++) {
c++;
System.out.format("Thread %d: %d %n", id, c);
}
}
}
}
public NumberPrinter(int id) {
super();
this.id = id;
}
public static void main(String[] args) {
// 启动3个带有id的线程,
for (int i = 0; i < 3; i++) {
new NumberPrinter(i).start();
}
}
}
解法二
解法一中,线程的run方法基本全是对变量c进行操作的,因此,使用synchronized关键字声明方法同步,系统的开销是比较大的,因此我们可以选择volatile关键字对c变量进行声明,让变量c具有CompareAndSet的特性,也就是对变量的引用和操作是同步的。但是这个关键字在JDK5才被很好的支持,如果是JDK4,会出现意象不到的结果。具体的实现看下面的代码。
Java代码
class VolatileNumberPrinter extends Thread {
volatile static int c = 0;
private int id;
@Override
public void run() {
while (c < 75) {
if (c / 5 % 3 == id) {
for (int i = 0; i < 5; i++) {
c++;
System.out.format("Thread %d: %d %n", id, c);
}
}
}
}
public VolatileNumberPrinter(int id) {
super();
this.id = id;
}
public static void main(String[] args) {
for (int i = 0; i < 3; i++) {
new VolatileNumberPrinter(i).start();
}
}
}
解法三
JDK5及以后的版本提供了并发锁的类,最常用的是ReentralLock,在多线程环境下可以保证被锁住的代码在某一个特定的运行时刻只有拿到锁的线程才可以运行该代码。锁的使用比synchronized关键要灵活,但必须保证该锁在需要的时候被释放。采用锁机制实现同步的代码如下。
Java代码
class ReentrantLockNumberPrinter extends Thread {
static int c;
private int id;
private ReentrantLock lock = new ReentrantLock();
public ReentrantLockNumberPrinter(int id) {
super();
this.id = id;
}
@Override
public void run() {
try {
lock.lock(); // 拿到锁的线程在运行,没有拿到的线程在等待
while (c < 75) {
if (c / 5 % 3 == id) {
for (int i = 0; i < 5; i++) {
c++;
System.out.format("Thread %d: %d %n", id, c);
}
}
}
} finally { //确保锁被释放
lock.unlock();
}
}
public static void main(String[] args) {
for (int i = 0; i < 3; i++) {
new ReentrantLockNumberPrinter(i).start();
}
}
}
解法四
JDK5的并发包除了提供锁机制的类外,还提供了用于原子操作的类,把需要打印的变量放入到一个原子类中去,调用该类的CompareAndSet操作是具有原子性的,那么在多线程的环境下,可以避免不一致的变量引用。
Java代码
class AtomicIntegerNumberPrinter extends Thread {
private int id;
static AtomicInteger c = new AtomicInteger(0); //声明一个原子整数,设置初始值为0
public AtomicIntegerNumberPrinter(int id) {
super();
this.id = id;
}
public void run() {
while (c.get() < 75) {
if (c.get() / 5 % 3 == id) {
for (int i = 0; i < 5; i++) {
c.getAndIncrement(); // 相当于 c++,但是具有原子性
System.out.format("Thread %d: %d %n", id, c.get());
}
}
}
}
public static void main(String args[]) {
for (int i = 0; i < 3; i++) {
new AtomicIntegerNumberPrinter(i).start();
}
}
}
本例中,AtomaticInteger.getAndIncrement()把原子整数中的变量加1,但是具有原子性,可以保证多线程下不会出现不一致的c变量引用。get方法不具有原子性,不过
Java代码
c.get() / 5 % 3 == id
这行代码能确保一个值是被期望的线程所打印。
从上面的四种解法重看,解法一把run方法声明为同步,其实是给该方法加了一把隐形的锁。解法三是采用并发锁的机制,本质上是和解法一是相同的,解法三需要JDK5及其以后的版本,解法一是最稳定的,适应性最广的,任何JDK版本都可以支持。解法二和解法四类似,但是volatile在jdk5后才被很好的支持,也只有jdk5及以后的版本才支持原子类。因此在实际的多线程编程中,首先还是synchronized关键字,wait()和notify()等传统的线程方法。
如果您发现有什么一些其它的方法,请分享出来,大家一起讨论,共同提高。
分享到:
相关推荐
NULL 博文链接:https://houfeng0923.iteye.com/blog/1251682
15个顶级Java多线程面试题及回答.docx
一道力学题的四种解法.docx
一道 Google 竞赛题的解法
C++经典面试题,多种解法,含完整可运行代码,包括性能比较,分析
面试必备书籍。 经典100题。关于数据结构,算法,各种大公司的面试题。
微软面试逻辑题C语言解法 请回答下面10个问题: 1。 第一个答案是b的问题是哪一个? (a)2;(b) 3;(c)4;(d)5;(e)6 2。唯一的连续两个具有相同答案的问题是: (a)2,3;(b)3,4;(c)4,5;(d...
[荐]限定条件的二元不等式压轴题五种解法.rar
一元二次方程解法练习题四种方法.pdf
【一线互联网大厂Java核心面试题库】Java基础、异常、集合、并发编程、JVM、Spring全家桶、MyBatis、Redis、数据库、中间件MQ、Dubbo、Linux、Tomcat、ZooKeeper、Netty等等..
微分方程数值解法 一书中主要习题解答 及其习题拓展,纯手工录入,公式清晰,步骤清晰
这是我个人对ACM题目中的多到经典算法题的解法,其中运用了动态规划,贪心算法……希望对你有用
新数通 HCIE3.0 LAB完整版【拓扑+TS+TAC+解法】解法题库新增论述题HCIE笔试题库,同时赠送ENSP软件,用于2022年6月底HCIE数通笔试及LAB实验考试。 1、HCIE数通笔试背过里面题库去考全部通过 2、HCIE LAB拓扑及解法,...
2016 年 杭电 笔试 第二题 Ruby解法
算法面试 leetcode 算法题集 程序员算法进阶必刷题库 1、基础算法必练题(含解法)) 2、DFS、回溯、分治法必练题(含答案) 3、动态规划必练题(含解法) ✔️ 这是第三部分 4、算法进阶自练题
ccie sec v4 过人版本解法
主元素问题解法4.rar 主元素问题解法4.rar 主元素问题解法4.rar 主元素问题解法4.rar 主元素问题解法4.rar 主元素问题解法4.rar 主元素问题解法4.rar 主元素问题解法4.rar 主元素问题解法4.rar 主元素问题解法4.rar ...
电磁学习题的MATLAB解法