`
尚将军
  • 浏览: 34476 次
  • 性别: Icon_minigender_1
  • 来自: 南京
社区版块
存档分类
最新评论

多线程轮流打印递增的数字

 
阅读更多
问题的描述

启动3个线程打印递增的数字, 线程1先打印1,2,3,4,5, 然后是线程2打印6,7,8,9,10, 然后是线程3打印11,12,13,14,15. 接着再由线程1打印16,17,18,19,20....以此类推, 直到打印到75. 程序的输出结果应该为:



线程1: 1

线程1: 2

线程1: 3

线程1: 4

线程1: 5



线程2: 6

线程2: 7

线程2: 8

线程2: 9

线程2: 10

...



线程3: 71

线程3: 72

线程3: 73

线程3: 74

线程3: 75





解法一: 采用原始的synchronized, wait(), notify(), notifyAll()等方式控制线程.

public class NumberPrintDemo {  
    // n为即将打印的数字  
    private static int n = 1;  
    // state=1表示将由线程1打印数字, state=2表示将由线程2打印数字, state=3表示将由线程3打印数字  
    private static int state = 1;  
  
    public static void main(String[] args) {  
        final NumberPrintDemo pn = new NumberPrintDemo();  
        new Thread(new Runnable() {  
            public void run() {  
                // 3个线程打印75个数字, 单个线程每次打印5个连续数字, 因此每个线程只需执行5次打印任务. 3*5*5=75  
                for (int i = 0; i < 5; i++) {  
                    // 3个线程都使用pn对象做锁, 以保证每个交替期间只有一个线程在打印  
                    synchronized (pn) {  
                        // 如果state!=1, 说明此时尚未轮到线程1打印, 线程1将调用pn的wait()方法, 直到下次被唤醒  
                        while (state != 1)  
                            try {  
                                pn.wait();  
                            } catch (InterruptedException e) {  
                                e.printStackTrace();  
                            }  
                        // 当state=1时, 轮到线程1打印5次数字  
                        for (int j = 0; j < 5; j++) {  
                            // 打印一次后n自增  
                            System.out.println(Thread.currentThread().getName()  
                                    + ": " + n++);  
                        }  
                        System.out.println();  
                        // 线程1打印完成后, 将state赋值为2, 表示接下来将轮到线程2打印  
                        state = 2;  
                        // notifyAll()方法唤醒在pn上wait的线程2和线程3, 同时线程1将退出同步代码块, 释放pn锁.   
                        // 因此3个线程将再次竞争pn锁  
                        // 假如线程1或线程3竞争到资源, 由于state不为1或3, 线程1或线程3将很快再次wait, 释放出刚到手的pn锁.   
                        // 只有线程2可以通过state判定, 所以线程2一定是执行下次打印任务的线程.  
                        // 对于线程2来说, 获得锁的道路也许是曲折的, 但前途一定是光明的.  
                        pn.notifyAll();  
                    }  
                }  
            }  
        }, "线程1").start();  
  
        new Thread(new Runnable() {  
            public void run() {  
                for (int i = 0; i < 5; i++) {  
                    synchronized (pn) {  
                        while (state != 2)  
                            try {  
                                pn.wait();  
                            } catch (InterruptedException e) {  
                                e.printStackTrace();  
                            }  
                        for (int j = 0; j < 5; j++) {  
                            System.out.println(Thread.currentThread().getName()  
                                    + ": " + n++);  
                        }  
                        System.out.println();  
                        state = 3;  
                        pn.notifyAll();  
                    }  
                }  
            }  
        }, "线程2").start();  
  
        new Thread(new Runnable() {  
            public void run() {  
                for (int i = 0; i < 5; i++) {  
                    synchronized (pn) {  
                        while (state != 3)  
                            try {  
                                pn.wait();  
                            } catch (InterruptedException e) {  
                                e.printStackTrace();  
                            }  
                        for (int j = 0; j < 5; j++) {  
                            System.out.println(Thread.currentThread().getName()  
                                    + ": " + n++);  
                        }  
                        System.out.println();  
                        state = 1;  
                        pn.notifyAll();  
                    }  
                }  
            }  
        }, "线程3").start();  
    }  
} 
4

解法二: 采用JDK1.5并发包提供的Lock, Condition等类的相关方法控制线程.

   
public class NumberPrint implements Runnable {  
        private int state = 1;  
        private int n = 1;  
        // 使用lock做锁  
        private ReentrantLock lock = new ReentrantLock();  
        // 获得lock锁的3个分支条件  
        private Condition c1 = lock.newCondition();  
        private Condition c2 = lock.newCondition();  
        private Condition c3 = lock.newCondition();  
      
        @Override  
        public void run() {  
            new Thread(new Runnable() {  
                public void run() {  
                    for (int i = 0; i < 5; i++) {  
                        try {  
                            // 线程1获得lock锁后, 其他线程将无法进入需要lock锁的代码块.  
                            // 在lock.lock()和lock.unlock()之间的代码相当于使用了synchronized(lock){}  
                            lock.lock();  
                            while (state != 1)  
                                try {  
                                    // 线程1竞争到了lock, 但是发现state不为1, 说明此时还未轮到线程1打印.   
                                    // 因此线程1将在c1上wait  
                                    // 与解法一不同的是, 三个线程并非在同一个对象上wait, 也不由同一个对象唤醒  
                                    c1.await();  
                                } catch (InterruptedException e) {  
                                    e.printStackTrace();  
                                }  
                            // 如果线程1竞争到了lock, 也通过了state判定, 将执行打印任务  
                            for (int j = 0; j < 5; j++) {  
                                System.out.println(Thread.currentThread().getName()  
                                        + ": " + n++);  
                            }  
                            System.out.println();  
                            // 打印完成后将state赋值为2, 表示下一次的打印任务将由线程2执行  
                            state = 2;  
                            // 唤醒在c2分支上wait的线程2  
                            c2.signal();  
                        } finally {  
                            // 打印任务执行完成后需要确保锁被释放, 因此将释放锁的代码放在finally中  
                            lock.unlock();  
                        }  
                    }  
                }  
            }, "线程1").start();  
      
            new Thread(new Runnable() {  
                public void run() {  
                    for (int i = 0; i < 5; i++) {  
                        try {  
                            lock.lock();  
                            while (state != 2)  
                                try {  
                                    c2.await();  
                                } catch (InterruptedException e) {  
                                    e.printStackTrace();  
                                }  
                            for (int j = 0; j < 5; j++) {  
                                System.out.println(Thread.currentThread().getName()  
                                        + ": " + n++);  
                            }  
                            System.out.println();  
                            state = 3;  
                            c3.signal();  
                        } finally {  
                            lock.unlock();  
                        }  
                    }  
                }  
            }, "线程2").start();  
      
            new Thread(new Runnable() {  
                public void run() {  
                    for (int i = 0; i < 5; i++) {  
                        try {  
      
                            lock.lock();  
                            while (state != 3)  
                                try {  
                                    c3.await();  
                                } catch (InterruptedException e) {  
                                    e.printStackTrace();  
                                }  
                            for (int j = 0; j < 5; j++) {  
                                System.out.println(Thread.currentThread().getName()  
                                        + ": " + n++);  
                            }  
                            System.out.println();  
                            state = 1;  
                            c1.signal();  
                        } finally {  
                            lock.unlock();  
                        }  
                    }  
                }  
            }, "线程3").start();  
        }  
          
        public static void main(String[] args) {  
            new NumberPrint().run();  
        }  
    }  
4

总结: 对比解法一和解法二, 显然解法二是更好的解决方案. 解法一的问题在于无法进行精确唤醒, 比如线程1执行完打印任务并调用pn.notifyAll()方法后, 3个线程将再次竞争锁, 而不是精确唤醒线程2. 虽然线程2最终将赢得锁, 下一次的打印任务也肯定会由线程2执行, 但是竞争的持续时间是不可预知的, 只能看线程2的人品.

最糟糕的情形可以是: 线程3竞争到了锁, 紧接着wait. 接下来线程1也竞争到了锁, 然后线程1也wait. 此时就再也没有其他线程跟线程2竞争了, 线程2终于艰难的赢得了锁...



留下3个问题供有兴趣的朋友思考:

1. 解法一和解法二中的while (state != xx)是否可以换成if(state != xx), 为什么?

2. 解法一的中的pn.notifyAll()是否可以换成pn.notify(), 为什么?

3. 是否可以用wait(), notify(), notifyAll()等方法完成类似解法二的精确唤醒, 请给出方案或代码.--这个问题我思考了很久, 却没有头绪. 关键的困难在于必须调用pn的wait()方法和notifyAll()方法, 而不能是其他对象的wait()和notifyAll()方法. 如果你有思路, 还望在博客中留言, 不甚感激!
分享到:
评论

相关推荐

    Java实现多线程轮流打印1-100的数字操作

    总结来说,Java实现多线程轮流打印数字需要考虑线程同步和数据一致性问题。使用`volatile`关键字和并发工具类能有效解决这些问题,同时,`jstack`工具是调试并发问题的有力助手。理解这些概念和技巧,对于编写高效、...

    java多线程每个线程挨着打印ABC的4种实现方式

    java多线程每个线程挨着打印ABC的4种实现方式,有4个线程t1、t2、t3、t4,t1打印A后t2打印A再t3打印A再t4打印A,然后从新回到t1打印B再t2打印B...t4打印B... 4个线程轮流打印abc... 一个线程可以理解为一个人,打印...

    多线程调试打印日志类

    "多线程调试打印日志类"是一个专门设计用于在多线程环境中记录和打印日志的C++类。此类实现了一个单例模式,确保在整个应用程序中只有一个实例存在,从而避免了资源竞争和日志混乱的问题。 首先,单例模式是一种...

    Qt案例之利用QThread类实现简单多线程案例循环打印数字.zip

    Qt案例之利用QThread类实现简单多线程案例循环打印数字,可参考文章:https://blog.csdn.net/didi_ya/article/details/122661092

    c#多线程顺序打印1-100数字-源码.rar

    在本例中,`AutoResetEvent`被用来控制线程打印数字的顺序,确保每个线程在正确的时间打印数字。 下面详细解释这个程序的工作原理: 1. **线程创建**:程序首先创建多个线程,每个线程负责打印一定范围内的数字。...

    java多线程实现轮流打印ABC

    使用多线程和阻塞队列实现了ABC字母按顺序轮流打印

    多线程 打印1-99,100-199

    根据实验需求,我们需要创建两个子线程,分别打印从 1 到 99 和从 100 到 199 的数字,并且这两个线程需要交替打印数字。 **3.1 创建打印线程** 首先,我们创建两个类 `Test1` 和 `Test2` 来代表两个子线程,它们...

    用C#实现的多线程同步打印文章(windows操作系统实验)

    在本文中,我们将深入探讨如何使用C#编程语言在Windows操作系统环境下实现多线程同步打印文章的实验。这个实验的核心是创建两个独立的线程,一个用于文章的下载,另一个用于文章的打印,同时利用线程同步机制确保...

    C#中利用多线程控制打印的暂停和继续

    在处理耗时操作如打印时,多线程尤其有用,可以确保用户界面不会被阻塞,提供更好的用户体验。本文将深入探讨如何在C#中利用多线程来控制打印的暂停和继续。 首先,我们需要了解C#中的线程基础。`System.Threading`...

    java10个线程按照顺序打印1-100

    本主题聚焦于如何使用Java实现10个线程按照顺序打印数字1到100。这种问题通常通过线程间通信和同步机制来解决,如`synchronized`关键字、`wait()`、`notify()`或`notifyAll()`方法,以及`Semaphore`、`CyclicBarrier...

    文件打印问题 (c++多线程实现)

    本文将深入探讨“文件打印问题”的多线程实现,这涉及到线程同步和互斥访问资源,以及如何在VC++6.0环境下运行和调试代码。 首先,让我们了解线程的基本概念。线程是程序执行的最小单位,每个线程都有自己的执行...

    log4j2异步多线程打印

    在`asnc-print-different-logfile`这个压缩包中,可能包含了一个示例项目,用于演示如何配置和使用Log4j2实现异步多线程打印。项目可能包含以下元素: 1. `pom.xml`:Maven项目的配置文件,定义了依赖项和构建指令...

    C++11用两个线程轮流打印整数的实现方法

    这样,两个线程就可以交替打印数字,保证了顺序的正确性。 在`main()`函数中,我们创建了两个线程`t1`和`t2`,分别执行`printeven()`和`printodd()`,最后使用`join()`等待两个线程执行完毕,确保程序不会提前结束...

    实现Runnable接口创建多线程.docx

    运行`Example02`类,我们将看到类似图10-2所示的结果,其中`蜘蛛侠`和`钢铁侠`线程以及主线程`main`交替打印数字。这是因为线程的执行顺序是不确定的,取决于操作系统的调度,这正是多线程并发执行的特点。 总结...

    MFC打印消息控件【支持多线程】

    本文将详细讲解如何利用MFC实现一个支持多线程打印调试日志的控件,并探讨其中涉及的关键技术,如内存池、锁机制以及多线程编程。 1. **MFC打印日志** MFC中的COutputWnd或CDocument等类通常用于处理输出,但它们...

    C#多线程实现调用外部程序并获取打印结果

    一个简单的例子,C#多线程实现调用外部程序并获取打印结果 一个简单的例子,C#多线程实现调用外部程序并获取打印结果 一个简单的例子,C#多线程实现调用外部程序并获取打印结果 一个简单的例子,C#多线程实现调用...

    Qt 多线程及简单实例 demo

    Qt 多线程及简单实例 demo。 多线程的几大特点: 1.多线程的执行顺序无法保证,...本例中的线程(workthread类)实现的功能是,从0到9循环打印,0至9各占一排。 则该线程的具体实现详见demo。 demo环境为qt5.9 64位

    C#多线程互斥实例 多线程获取同一变量

    在编程领域,多线程是实现并发执行任务的重要机制,特别是在现代计算机系统中,多核处理器使得多线程成为提高程序性能的关键手段。C#语言提供了丰富的多线程支持,让我们能够编写出高效的多线程应用程序。在这个"多...

    多线程顺序打印.cpp

    多线程顺序打印

    vc++分别用单-多线程读取数字

    本项目标题为“vc++分别用单-多线程读取数字”,这意味着我们将探讨如何在Visual C++(简称VC++)环境下,通过单线程和多线程的方式实现数字的读取。以下是对这一主题的详细阐述。 1. **单线程编程**: 在单线程...

Global site tag (gtag.js) - Google Analytics