问题描述:两个线程交替打印输出数字0~100,一个线程只打印偶数,另一个只打印奇数
方案一:使用synchronized关键字
-
创建两个线程,一个线程处理偶数,一个线程处理奇数,两个线程之间通过synchronized进行同步,保证count++每次只有一个线程进行操作
-
为什么两个线程能交替执行,这里很巧的是
count从0123...自增过程就是一个奇偶数交替的过程
,实际上两个线程都是在不停的尝试(while循环)进入synchronized代码块,如果满足相对应的条件(偶数或是奇数)就打印输出。
package com.hs.demo.code;
/**
* 两个线程交替打印0~100的奇偶数,用synchroized关键字实现
*
* 这种写法有很多多余操作.就是同一个线程会出现多次抢到了锁,但是不满足条件跳过了if语句并不会执行
* count++,这种写法效率并不高效
*/
public class SynchroizedPrintOddEven
{
private static int count;
//临界资源
private static final Object lock = new Object();
//新建两个线程,第1个只处理偶数,第二个只处理奇数(用位运算),用synchronized来通讯
public static void main(String[] args)
{
new Thread(new Runnable() {
@Override
public void run() {
while (count < 100)
{
synchronized (lock){
//count & 1 一个数字把它和1做位与的操作,1再二进制就是1,count转换位二进制,和1去与,就是取出count二进制的最低位,最低位是1代表奇数,0代表是偶数,比count%2 == 0 效率高
//因为线程是随机抢锁的,可能会出现同一个线程多次进入,但是不满足条件,并不会执行count++.
if((count & 1) == 0){
System.out.println(Thread.currentThread().getName() + ":" + count++);
}
}
}
}
},"偶数").start();
new Thread(new Runnable() {
@Override
public void run() {
while (count < 100){
synchronized (lock){
//count & 1 一个数字把它和1做位与的操作,1再二进制就是1,count转换位二进制,和1去与,就是取出count二进制的最低位,最低位是1代表奇数,0代表是偶数,比count%2 == 0 效率高
if((count & 1) == 1){
System.out.println(Thread.currentThread().getName() + ":" + count++);
}
}
}
}
},"奇数").start();
}
}
输出结果:
结果看起来没问题,奇偶数有交替运行。但实际上并不代表着这两个线程在交替运行,因为线程是随机抢锁的,有可能连续十次都是偶数线程好运抢到了锁,只是因为不满足条件,没有对 count 进行 +1,白白浪费一次占用资源的机会。
因此,这种方式虽然能实现,但存在资源浪费,只需要在两个线程拿到锁时输出语句,即可看到存在大量的资源浪费。
方案二:使用wait/notify关键字(推荐)
-
这个相对于上面那个好理解很多,直接走流程,偶数线程拿到锁打印输出同时count++,然后进行休眠,因为wait()方法的特性,休眠的同时会释放monitor锁,奇数线程就可以进来了,进来后打印输出,同时notify唤醒偶数线程继续下一轮,奇数线程往下执行wait方法休眠,就这样,偶数线程唤醒奇数线程,奇数线程唤醒偶数线程,直到满足count<100条件后,线程不再休眠,直接退出程序。
-
这个要点一个在于wait/notify的等待唤醒机制,一个在于wait()方法的特性,休眠后会释放锁。
-
这种方式和上面那种方式不同点在于,这种方式是被动唤醒的机制,而上面那个是线程不断重试的机制(一直while重试,直到满足条件就打印),很明显这种方式优于上面那种!
public class WaitNotifyPrintOddEven
{
private static int count = 0;
//当前线程必须拥有此对象的锁,才能调用某个对象的wait()方法能让当前线程阻塞,
private static final Object lock = new Object();
public static void main(String[] args)
{
new Thread(new TurningRunner(),"偶数").start();
new Thread(new TurningRunner(),"奇数").start();
}
//拿到锁,我们就打印,一旦打印完唤醒其他线程就休眠
static class TurningRunner implements Runnable
{
@Override
public void run()
{
while (count <= 100)
{
synchronized (lock)
{
System.out.println(Thread.currentThread().getName()+":"+ count++);
lock.notify();
if(count<=100)
{
try {
//如果任务没结束,唤醒其他线程,自己休眠
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
}
}
输出结果:
这种方式,每次抢到资源都是有意义的,效率更高