一、具体问题:
启动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:用Synchronized
package anheng;
public class NewThreadSynchronized {
public static void main(String[] args) {
// 创建PrintNum对象
PrintNumSynchronized printNum = new PrintNumSynchronized();
// 创建线程1
new Thread(new Runnable() {
@Override
public void run() {
while (printNum.num <= 65) {
printNum.fun1();
}
}
},"线程1").start();
// 创建线程2
new Thread(new Runnable() {
@Override
public void run() {
while (printNum.num <= 70) {
printNum.fun2();
}
}
},"线程2").start();
// 创建线程3
new Thread(new Runnable() {
@Override
public void run() {
while (printNum.num <= 75) {
printNum.fun3();
}
}
},"线程3").start();
}
}
class PrintNumSynchronized {
// 从1开始打印
public int num = 1;
// 初始标识为1,代表线程1,后面的2和3代表线程2和线程3
public int PrintId = 1;
// synchronized修饰标识方法1
public synchronized void fun1() {
while (PrintId != 1) {
try {
wait();
} catch (Exception e) {
e.printStackTrace();
}
}
// 打印5个数
print();
// PrintId切换为2
PrintId = 2;
notifyAll();
}
// synchronized修饰标识方法2
public synchronized void fun2() {
while (PrintId != 2) {
try {
wait();
} catch (Exception e) {
e.printStackTrace();
}
}
// 打印5个数
print();
// PrintId切换为3
PrintId = 3;
notifyAll();
}
// synchronized修饰标识方法3
public synchronized void fun3() {
while (PrintId != 3) {
try {
wait();
} catch (Exception e) {
e.printStackTrace();
}
}
// 打印5个数
print();
// PrintId切换为1
PrintId = 1;
notifyAll();
}
private void print() {
// 依次打印5个数
System.out.println(Thread.currentThread().getName() + ":" );
for (int i = 0; i < 5; i++,num++) {
System.out.println(num);
}
}
}
法2:用ReentrantLock
package anheng;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
public class NewThreadLock {
public static void main(String[] args) {
PrintNumLock printNume = new PrintNumLock();
new Thread(new Runnable() {
@Override
public void run() {
while (printNume.getNum() <= 65) {
printNume.lock1();
}
}
}, "线程1").start();
new Thread(new Runnable() {
@Override
public void run() {
while (printNume.getNum() <= 70) {
printNume.lock2();
}
}
}, "线程2").start();
new Thread(new Runnable() {
@Override
public void run() {
while (printNume.getNum() <= 75) {
printNume.lock3();
}
}
}, "线程3").start();
}
}
class PrintNumLock {
// 数字从1开始
private int num = 1;
// 初始标识为1,代表线程1,后面的2和3代表线程2和线程3
private int PrintId = 1;
// 创建num的get和set方法来设置和获取参数
public int getNum() {
return num;
}
public void setNum(int num) {
this.num = num;
}
// 创建lock
final ReentrantLock lock = new ReentrantLock();
// 创建3个条件对象
final Condition condition1 = lock.newCondition();
final Condition condition2 = lock.newCondition();
final Condition condition3 = lock.newCondition();
// lock1
public void lock1() {
lock.lock();
try {
while (PrintId != 1)
try {
condition1.await();
} catch (Exception e) {
e.printStackTrace();
}
// 如果是线程1,则打印5个数
print();
// 打印完成后,把标识置为2
PrintId = 2;
// 唤醒lock2
condition2.signal();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
// lock2
public void lock2() {
lock.lock();
try {
while (PrintId != 2)
try {
condition2.await();
} catch (Exception e) {
e.printStackTrace();
}
// 如果是线程2,则打印5个数
print();
// 打印完成后,把标识置为3
PrintId = 3;
// 唤醒lock3
condition3.signal();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
// lock3
public void lock3() {
lock.lock();
try {
while (PrintId != 3)
try {
condition3.await();
} catch (Exception e) {
e.printStackTrace();
}
// 如果是线程3,则打印5个数
print();
// 打印完成后,把标识置为1
PrintId = 1;
// 唤醒lock1
condition1.signal();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
private void print() {
System.out.println(Thread.currentThread().getName() + ":" );
for (int i = 0; i < 5; i++,num++) {
System.out.println(num);
}
}
}
法3:把3个线程各自的方法合成一个,然后还是用Synchronized:
package anheng;
public class NewThread {
public static void main(String[] args) {
Object o = new Object();
new Thread(new PrintRunnable(o, 1), "线程1").start();
new Thread(new PrintRunnable(o, 2), "线程2").start();
new Thread(new PrintRunnable(o, 3), "线程3").start();
}
}
class PrintRunnable implements Runnable {
private final Object o;
private int threadId;
private static volatile int num = 1;
public PrintRunnable(Object o, int threadId) {
this.o = o;
this.threadId = threadId;
}
@Override
public void run() {
// 取前面三个方法条件的交集
while (num <= 65) {
synchronized (o) {
while (num / 5 % 3 + 1 != threadId) {
try {
o.wait();
} catch (Exception e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName() + ":");
for (int i = 0; i < 5; i++, num++) {
System.out.println(num);
}
o.notifyAll();
}
}
}
}
三种方法的输出结果都是一样的,如下所示:
三种方法的对比:
(1)比较同样是synchronized修饰的法1和法3:
A、法1和法3都是用synchronized实现,但不同的是法1修饰方法,法2修饰代码块;
B、法3是把法1中的三个方法合在一起,判断条件是三个方法的条件交集num<=65和num/5%3+1!=threadId,减少了大量冗余代码。
(2)比较synchronized修饰的法1和ReentrantLock实现的法2:
法1的中三个方法的判断条件都是!=表示当前方法的线程Id,那满足条件的是另外两个线程,让两个线程同时wait也一起notifyAll则会让大家再次竞争锁,不能保证线程第一时间获得自己的方法,从而效率比较低;而法2中的三个方法条件虽然也是一样的,但唤醒的线程是指定好的,不会同时唤醒两个,所以更精确。