需求:要求两个线程轮流打印输出
方法一 synchronized
synchronized可以把任意非NULL的对象当做锁。
作用范围:
- synchronized作用于对象时,锁住的是所有以该对象为锁的代码块。
- 作用于方法时,锁住的是对象的实例(this)
- 作用于静态方法时,锁住的是class对象。class对象存储在方法区中,而方法区时共享的,因此锁静态方法相当于一个类的全局锁。
Object对象的方法中:
- void notify() 唤醒在此对象监视器上等待的单个线程
- void wait() 导致当前的线程等待,直到其他线程调用此对象的notify( ) 方法或 notifyAll( ) 方法
public class PrintAB {
private static Object obj;
public static void main(String[] args) {
obj = new Object();
Thread1 thread1 = new Thread1();
thread1.start();
Thread2 thread2 = new Thread2();
thread2.start();
}
static class Thread1 extends Thread{
@Override
public void run() {
super.run();
while(true) {
synchronized (obj)
{
System.out.println(this.getName()+"打印");
obj.notify();//通知另外一个线程继续执行。
try {
obj.wait();//强迫一个线程继续等待
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
static class Thread2 extends Thread{
@Override
public void run() {
super.run();
while(true){
synchronized (obj)
{
System.out.println(this.getName()+"打印");
obj.notify();
try {
obj.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
}
改为三个线程轮流打印:
public class PrintAB {
private static Object obj;
private static int id;
public static void main(String[] args) {
obj = new Object();
id = 1;
Thread1 thread1 = new Thread1();
thread1.start();
Thread2 thread2 = new Thread2();
thread2.start();
Thread3 thread3 = new Thread3();
thread3.start();
}
static class Thread1 extends Thread{
@Override
public void run() {
super.run();
while(true) {
synchronized (obj)
{
while (id!=1) {
try {
obj.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(this.getName()+"打印");
id = 2;
obj.notifyAll();//通知另外一个线程继续执行。
try {
obj.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
static class Thread2 extends Thread{
@Override
public void run() {
super.run();
while(true){
synchronized (obj)
{
while (id != 2) {
try {
obj.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(this.getName()+"打印");
id = 3;
obj.notifyAll();
try {
obj.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
static class Thread3 extends Thread{
@Override
public void run() {
super.run();
while(true){
synchronized (obj)
{
while (id!=3) {
try {
obj.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(this.getName()+"打印");
id = 1;
obj.notifyAll();
try {
obj.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
}
方法二 Semaphore信号量
信号量(英语:semaphore)又称为信号标,是一个同步对象,用于保持在0至指定最大值之间的一个计数值。当线程完成一次对该semaphore对象的等待(wait)时,该计数值减一;当线程完成一次对semaphore对象的释放(release)时,计数值加一。当计数值为0,则线程等待该semaphore对象不再能成功直至该semaphore对象变成signaled状态。semaphore对象的计数值大于0,为signaled状态;计数值等于0,为nonsignaled状态.
创建两个信号量,类似于红绿灯。
semaphore1初始化为1,因此可供线程1获取,线程池打印完后释放semaphore2. 轮到线程2执行,线程二执行完唤醒semaphore1.
import java.util.concurrent.Semaphore;
public class PrintAB {
private static Object obj;
private static Semaphore semaphore1;
private static Semaphore semaphore2;
public static void main(String[] args) {
semaphore1 = new Semaphore(1);
semaphore2 = new Semaphore(0);
Thread1 thread1 = new Thread1();
thread1.start();
Thread2 thread2 = new Thread2();
thread2.start();
}
static class Thread1 extends Thread{
@Override
public void run() {
super.run();
while(true) {
try {
semaphore1.acquire();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(this.getName()+"打印");
semaphore2.release();
}
}
}
static class Thread2 extends Thread{
@Override
public void run() {
super.run();
while(true){
try {
semaphore2.acquire();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(this.getName()+"打印");
semaphore1.release();
}
}
}
}
semaphore信号量的使用机制可以扩展为多个线程轮流打印。
方法三 Lock(公平锁)
公平锁(Fair):加锁前检查是否有排队等待的线程,优先排队等待的线程,先来先得
非公平锁(Nonfair):加锁时不考虑排队等待问题,直接尝试获取锁,获取不到自动到队尾等待
Lock默认为不公平锁,但这里只有两个线程,一个线程在执行时只有一个线程在等待,因此当线程1释放锁时,线程2一定可以进去。
import java.util.concurrent.Semaphore;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class PrintAB {
private static Lock lock;
private static boolean flag;
public static void main(String[] args) {
lock = new ReentrantLock(true);//将ReentrantLock设置为公平锁
Thread1 thread1 = new Thread1();
thread1.start();
Thread2 thread2 = new Thread2();
thread2.start();
}
static class Thread1 extends Thread{
@Override
public void run() {
super.run();
while(true) {
lock.lock();
System.out.println(this.getName()+"打印");
// flag = false;
lock.unlock();
}
}
}
static class Thread2 extends Thread{
@Override
public void run() {
super.run();
while(true){
lock.lock();
System.out.println(this.getName()+"打印");
// flag = true;
lock.unlock();
}
}
}
}
方法四 无锁打印
维护一个访问标记位,提供两个线程进行访问判断
出现问题:该方法运行循环几十次就自动停止
原因???
import java.util.concurrent.Semaphore;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class PrintAB {
private static Lock lock;
private static boolean flag;
public static void main(String[] args) {
flag = true;
Thread1 thread1 = new Thread1();
thread1.start();
Thread2 thread2 = new Thread2();
thread2.start();
}
static class Thread1 extends Thread {
@Override
public void run() {
super.run();
while (true) {
if (flag) {
System.out.println(this.getName() + "打印");
flag = false;
}
}
}
}
static class Thread2 extends Thread{
@Override
public void run() {
super.run();
while(true){
if (!flag) {
System.out.println(this.getName() + "打印");
flag = true;
}
}
}
}
}
方法五 BlockingQueue 阻塞队列
利用阻塞队列的阻塞特性也可实现交替打印。
实现过程:
初始两个阻塞队列blockingDeque1与blockingDeque2,容量为1,blockingDeque1用来存储线程1的信息,blockingDeque2用来存储线程2的信息。
线程1负责消费blockingDeque2中的信息,然后往blockingDeque1中添加信息。
线程2负责消费blockingDeque1中的信息,然后往blockingDeque2中添加信息。
一开始时,线程1往blockingDeque1中添加信息,这样线程2才可以消费。线程二消费完就往线程1中添加信息,轮到消除1消费。
线程1,循环内到blockingDeque2队列取数据,如为空则等待,取到信息打印。然后往blockingDeque1中增加信息,如果满则阻塞等待。
线程2,循环内到blockingDeque1队列取数据,如为空则等待,取到信息打印。然后往blockingDeque2中增加信息,如果满则阻塞等待。
其实原理还是两线程阻塞,互相轮流唤醒的过程。只不过阻塞和唤醒,线程间间通信交给了阻塞队列。
阻塞队列底层是用到 ReentrantLock,ReentrantLock 底层是 AQS,AQS 底层也应用到CAS + LockSupport。
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingDeque;
public class PrintAB{
private static ArrayBlockingQueue<String> blockingDeque1;
private static ArrayBlockingQueue<String> blockingDeque2;
public static void main(String[] args) {
blockingDeque1 = new ArrayBlockingQueue<String>(1);
blockingDeque2 = new ArrayBlockingQueue<String>(1);
Thread1 thread1 = new Thread1();
thread1.start();
Thread2 thread2 = new Thread2();
thread2.start();
}
static class Thread1 extends Thread{
@Override
public void run() {
super.run();
try {
blockingDeque1.put(this.getName());
} catch (InterruptedException e) {
e.printStackTrace();
}
while(true) {
try {
System.out.println(blockingDeque2.take());
} catch (InterruptedException e) {
e.printStackTrace();
}
try {
blockingDeque1.put(this.getName());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
static class Thread2 extends Thread{
@Override
public void run() {
while (true) {
try {
System.out.println(blockingDeque1.take());
} catch (InterruptedException e) {
e.printStackTrace();
}
try {
blockingDeque2.put(this.getName());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}