前段时间遇到一个题目,关于两个线程分别打印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();
}
}
控制台打印结果如下:
此段代码的结果虽能正确打印出结果,但是请注意Console中的红色标注处,此时的控制台并未结束,这是为什么呢?
可以打开控制jvisualvm 监控界面,打开应用程序,可以发现到此时有一个用户现场一直处于waiting 状态
为什么会等待也是因为偶数线程打印完后,并未唤醒奇数线程,导致奇数线程一直等待。
其实还有另外一种实现方式,不需要进行任何加锁,利用并发包中的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方法,结果如下所示:
注意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的值,观察两个方法分别耗时多少。