两个线程分别打印0-100的之间的奇偶数(两种方式对比)

业务描述

前段时间遇到一个题目,关于两个线程分别打印0-100的之间的奇偶数,当时自己的写法是通过synchronized同步锁结合wait 和notify 的方式进行编写。主要逻辑的代码如下所示:

package com.xyq.maventest.alibaba;

import java.util.concurrent.CountDownLatch;

/****
 * 两个线程分别打印0-100之间的奇偶数
 * @author youqiang.xiong
 * 

TODO 简单描述此类的用途

* 2018年2月25日下午6:18:34 */
public class ThreadPrintData { public static CountDownLatch latch = new CountDownLatch(2); private static Object lock = new Object(); private static volatile Integer i = 0; private static final int TOTAL = 100; public static void main(String[] args) { Thread thread1 = new Thread() { @Override public void run() { while (i <= TOTAL) { synchronized (lock) { if (i % 2 == 1) { System.out.println(Thread.currentThread().getName() + "打印: " + i++); } else { lock.notifyAll(); try { if(i <= TOTAL){ lock.wait(); } } catch (InterruptedException e) { e.printStackTrace(); } } } } } }; thread1.setName("奇数线程"); Thread thread2 = new Thread() { @Override public void run() { while (i <= TOTAL) { synchronized (lock) { if (i % 2 == 0) { System.out.println(Thread.currentThread().getName()+ "打印: " + i++); } else { lock.notifyAll(); try { if(i < TOTAL){ lock.wait(); } } catch (InterruptedException e) { e.printStackTrace(); } } } } } }; thread2.setName("偶数线程"); thread1.start(); thread2.start(); } }

控制台打印结果如下:

两个线程分别打印0-100的之间的奇偶数(两种方式对比)_第1张图片

此段代码的结果虽能正确打印出结果,但是请注意Console中的红色标注处,此时的控制台并未结束,这是为什么呢?

可以打开控制jvisualvm 监控界面,打开应用程序,可以发现到此时有一个用户现场一直处于waiting 状态

两个线程分别打印0-100的之间的奇偶数(两种方式对比)_第2张图片

为什么会等待也是因为偶数线程打印完后,并未唤醒奇数线程,导致奇数线程一直等待。

其实还有另外一种实现方式,不需要进行任何加锁,利用并发包中的AtomicInteger和volidate修饰符组合,详细代码如下所示:

package com.xyq.maventest.alibaba;

import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicInteger;

/****
 * 两个线程分别打印奇偶数(优化后的策略)
* @ClassName: AlibabaTest
* @Description: TODO(这里用一句话描述这个类的作用)
* @author youqiang.xiong
* @date 2018年3月11日
*
 */
public class AlibabaTest {

    private static volatile Boolean flag = true;

    private static AtomicInteger num = new AtomicInteger();

    private static final Integer TOTAL = 100;

    public static CountDownLatch latch = new CountDownLatch(2);

    public static void main(String[] args) throws InterruptedException {

        long start =  System.currentTimeMillis();


        Thread jsThread = new Thread(new Runnable() {

            @Override
            public void run() {

                while(num.get() <= TOTAL-1){
                    if(!flag){
                        System.out.println(Thread.currentThread().getName()+"打印: " + num.getAndIncrement());
                        flag = true;
                    }
                }

                latch.countDown();

            }
        });

        jsThread.setName("奇数线程");

        Thread osThread = new Thread(new Runnable() {

            @Override
            public void run() {

                while(num.get() <= TOTAL){
                    if(flag){
                        System.out.println(Thread.currentThread().getName()+ "打印: " + num.getAndIncrement());
                        flag = false;
                    }
                }

                latch.countDown();
            }
        });

        osThread.setName("偶数线程");

        osThread.start();
        jsThread.start();

        latch.await();
        long end =  System.currentTimeMillis();

        System.out.println("一共耗时:"+(end - start) + "ms");

    }

}

运行main方法,结果如下所示:

两个线程分别打印0-100的之间的奇偶数(两种方式对比)_第3张图片

注意console的的结果,首先是可以正常满足业务需求,其次程序运行结束后,也意味着整个程序结束,并未出现线程等待的情况,原因是因为这个程序并未有任何加锁的地方,从这一点上其实比第一种方法的体验会更好。

性能对比

第一种方法通过加锁方式控制,假如需要打印的0-100万,或者更大,线程之间的切换肯定会导致性能比较差,为了更加直观地比较两种实现方法的性能差别,可以利用CountDownLatch 的类,可以控制两个线程执行结束后,主线程再继续执行,由于方法二代码已加CountDownLatch 统计时间,此处对方法一的代码进行改进下,代码如下所示:

package com.xyq.maventest.alibaba;

import java.util.concurrent.CountDownLatch;

/****
 * 两个线程分别打印0-100之间的奇偶数
 * @author youqiang.xiong
 * 

TODO 简单描述此类的用途

* 2018年2月25日下午6:18:34 */
public class ThreadPrintData { public static CountDownLatch latch = new CountDownLatch(2); private static Object lock = new Object(); private static volatile Integer i = 0; private static final int TOTAL = 1000000; public static void main(String[] args) throws InterruptedException { long start = System.currentTimeMillis(); Thread thread1 = new Thread() { @Override public void run() { while (i <= TOTAL) { synchronized (lock) { if (i % 2 == 1) { if(i<=TOTAL){ System.out.println(Thread.currentThread().getName() + "打印: " + i++); } } else { lock.notifyAll(); try { if(i < TOTAL){ lock.wait(); } } catch (InterruptedException e) { e.printStackTrace(); } } } } latch.countDown(); } }; thread1.setName("奇数线程"); Thread thread2 = new Thread() { @Override public void run() { while (i <= TOTAL) { synchronized (lock) { if (i % 2 == 0) { System.out.println(Thread.currentThread().getName()+ "打印: " + i++); } else { lock.notifyAll(); try { if(i < TOTAL){ lock.wait(); } } catch (InterruptedException e) { e.printStackTrace(); } } } } latch.countDown(); } }; thread2.setName("偶数线程"); thread1.start(); thread2.start(); latch.await(); long end = System.currentTimeMillis(); System.out.println("共耗时:"+(end-start) + "ms"); } }

测试时,不断掉大TOTAL的值,观察两个方法分别耗时多少。

你可能感兴趣的:(多线程)