`

一道经典线程面试题的4种解法

阅读更多
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()等传统的线程方法。

如果您发现有什么一些其它的方法,请分享出来,大家一起讨论,共同提高。
分享到:
评论
1 楼 winston1991 2011-11-20  

相关推荐

    答复: 一道经典线程面试题的4种解法

    标题中的“答复: 一道经典线程面试题的4种解法”暗示了这是一个关于多线程编程的问题,通常在面试中出现,用于评估候选人的并发处理能力。在这个问题中,可能涉及到同步、线程安全、锁机制等关键概念。 在Java中,...

    多线程面试题及回答

    ### 多线程面试题及回答 #### 一、题目概览 本文档汇集了15个顶级Java多线程面试题及其解答思路,旨在帮助求职者更好地准备涉及多线程与并发技术的相关面试。多线程是Java面试中一个不可或缺的部分,特别是在面向...

    一道力学题的四种解法.docx

    ### 一道力学题的四种解法 #### 题目背景 题目要求分析一架飞机在空中以恒定速度沿水平圆形轨道转弯时,螺旋桨尖端与螺旋桨中心连线与竖直线成特定角度时,该点的速度及加速度。已知螺旋桨长度以及螺旋桨自身的旋转...

    一道 Google 竞赛题的解法

    一道 Google 竞赛题的解法,看看

    微软面试逻辑题C语言解法.rar

    微软面试逻辑题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...

    sql经典面试题

    ### SQL经典面试题解析 #### 1. 查询分数高于80分的学生姓名 **问题:** 需要找出所有分数超过80分的学生姓名。 **解法:** - 使用子查询排除低于80分的记录。 - 或者使用`GROUP BY`与`HAVING`来过滤结果。 **SQL...

    [荐]限定条件的二元不等式压轴题五种解法.rar

    本资源"[荐]限定条件的二元不等式压轴题五种解法.rar"提供了一种深入学习和掌握这类问题的方法,特别适合于准备高考或其他高级数学考试的学生。 首先,我们来看第一种解法:图形法。在解决二元不等式时,我们可以...

    一元二次方程解法练习题四种方法.pdf

    这篇文档主要涵盖了一元二次方程的四种解法——直接开平方法、配方法、公式法和因式分解法,并提供了大量的练习题供学习者进行练习。此外,文档还涉及了选择最佳解法的策略以及解答题,进一步检验对一元二次方程的...

    微分方程数值解法习题答案.pdf

    2. 差分格式:差分格式是数值解法中常用的一种方法,通过离散化微分方程来近似求解。例如,(3)中使用了有限体积法,通过积分和格林第一公式推导出差分方程。 3. 傅里叶变换与达朗贝尔公式:傅里叶变换在解决偏微分...

    剑指Offer(第二版)面试题java版本解法.zip

    【一线互联网大厂Java核心面试题库】Java基础、异常、集合、并发编程、JVM、Spring全家桶、MyBatis、Redis、数据库、中间件MQ、Dubbo、Linux、Tomcat、ZooKeeper、Netty等等..

    新数通HCIE3.0 LAB完整版【拓扑+TS+TAC+解法】解法题库新增论述题HCIE笔试题库

    新数通 HCIE3.0 LAB完整版【拓扑+TS+TAC+解法】解法题库新增论述题HCIE笔试题库,同时赠送ENSP软件,用于2022年6月底HCIE数通笔试及LAB实验考试。 1、HCIE数通笔试背过里面题库去考全部通过 2、HCIE LAB拓扑及解法,...

    电磁学习题的Matlab解法

    电磁学习题的Matlab解法 Matlab是一种高级的数值计算语言和开发环境,广泛应用于电磁学、信号处理、图像处理、控制系统等领域。本资源摘要信息将从电磁学习题的Matlab解法入手,介绍如何使用Matlab解决电磁学习题,...

    C++经典面试题

    《C++经典面试题》涉及了多个C++编程和数据结构相关的知识点,下面将详细解析题目及提供的代码示例。 1. **数组的最大子数组问题**:这是一个经典的算法问题,目标是从一个整数数组中找到具有最大和的连续子数组。...

    一种解法.txt

    一种解法.txt

    ccie sec v4 过人版本解法

    ccie sec v4 过人版本解法

    偏微分方程数值解法习题

    3. 第三题的解法可能涉及到混合差分格式,考虑到边界条件,需要设计合适的差分公式,以确保算法的稳定性和收敛性。 4. 第四题中,紧差分格式用于处理空间导数,通常结合时间积分方法如Euler方法。编程时,需注意...

    主元素问题解法4.rar 主元素问题解法4.rar

    主元素问题解法4.rar 主元素问题解法4.rar 主元素问题解法4.rar 主元素问题解法4.rar 主元素问题解法4.rar 主元素问题解法4.rar 主元素问题解法4.rar 主元素问题解法4.rar 主元素问题解法4.rar 主元素问题解法4.rar ...

    电磁学习题的MATLAB解法

    电磁学习题的MATLAB解法

    经典数据库面试题

    下面,我们将基于题目描述中的SQL面试题,深入解析其中涉及的知识点。 ### SQL嵌套查询 #### 题目一:查询选修课程名称为‘税收基础’的学员学号和姓名 - **解法一**:采用标准的SQL嵌套查询方式,首先从`C`表和`...

    历年全国数学建模试题及解法归纳

    历年全国数学建模试题及解法归纳及06~08年全国赛题评阅要点

Global site tag (gtag.js) - Google Analytics