线程间通信的模型有两种:
- 共享内存
- 消息传递
通信模型模拟场景
两个线程,一个线程对当前数值加 1,另一个线程对当前数值减1,要求用线程间通信
synchronized方案
/**
* synchronized方案
*/
public class CommunicatBySync {
//加减对象 volatile关键字实现线程交替加减
private volatile int number = 0;
/**
* 加 1
*/
public synchronized void increment() {
try {
while (number != 0) {
this.wait();
}
number++;
System.out.println("--------" + Thread.currentThread().getName() + "加一成功----------, 值为:" + number);
notifyAll();
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 减一
*/
public synchronized void decrement() {
try {
while (number == 0) {
this.wait();
}
number--;
System.out.println("--------" + Thread.currentThread().getName() + "减一成功----------, 值为:" + number);
notifyAll();
} catch (Exception e) {
e.printStackTrace();
}
}
}
Lock方案
/**
* Lock方案
*/
public class CommunicatByLock {
//加减对象
private int number = 0;
//声明锁
private Lock lock = new ReentrantLock();
//声明钥匙
private Condition condition = lock.newCondition();
/**
* 加 1
*/
public void increment() {
lock.lock();
try {
while (number != 0) {
condition.await();
}
number++;
System.out.println("--------" + Thread.currentThread().getName() + "加一成功----------, 值为:" + number);
condition.signalAll();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
/**
* 减一
*/
public void decrement() {
lock.lock();
try {
while (number == 0) {
condition.await();
}
number--;
System.out.println("--------" + Thread.currentThread().getName() + "减一成功----------, 值为:" + number);
condition.signalAll();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
/**
* 测试
*/
public class CommunicatTest {
/**
* 交替加减
*/
public static void main(String[] args) {
// CommunicatBySync test = new CommunicatBySync();
CommunicatByLock test = new CommunicatByLock();
new Thread(() -> {
for (int i = 0; i < 5; i++) {
test.increment();
}
}, "线程A").start();
new Thread(() -> {
for (int i = 0; i < 5; i++) {
test.decrement();
}
}, "线程B").start();
}
}
//返回结果
--------线程A加一成功----------, 值为:1
--------线程B减一成功----------, 值为:0
--------线程A加一成功----------, 值为:1
--------线程B减一成功----------, 值为:0
--------线程A加一成功----------, 值为:1
--------线程B减一成功----------, 值为:0
--------线程A加一成功----------, 值为:1
--------线程B减一成功----------, 值为:0
--------线程A加一成功----------, 值为:1
--------线程B减一成功----------, 值为:0
wait()、notify、notifyAll()方法
wait()、notify()、notifyAll()是三个定义在Object类里的方法,可以用来控制线程的状态。这三个方法最终调用的都是jvm级的native方法。随着jvm运行平台的不同可能有些许差异。
对象调用wait方法(等待):使持有该对象的线程把该对象的控制权交出去,然后处于等待状态。对象调用notify方法(运行):通知某个正在等待这个对象控制权的线程可以继续运行。对象调用notifyAll方法(全部运行):通知所有等待这个对象控制权的线程继续运行。注意:一定要在线程同步中使用,并且是同一个锁的资源
wait与sleep区别
sleep()方法:属于Thread类中的静态方法。sleep()方法使程序暂停,在执行指定的时间中,让出cpu给其他线程,但依然保持监控状态,当指定的时间到了,便自动恢复运行状态。在调用sleep()方法的过程中,线程不会释放对象锁。
wait()方法:属于Object类中的方法。当调用wait()方法的时候,线程会放弃对象锁,进入等待此对象的锁定池,只有针对此对象调用notify()方法后,该线程才进入对象锁定池准备获取对象锁进入运行状态。
它两均会被interrupted方法中断。
Lock
在 JDK1.5 之后,并发包(java.util.concurrent,简称JUC)中新增了 Lock 接口。Lock锁实现提供了比使用同步方法和语句可以获得的更广泛的锁操作。它们允许更灵活的结构,可能具有非常不同的属性,并且可能支持多个关联的条件对象。Lock提供了比synchronized更多的功能。
Lock 与 synchronized 关键字的区别
- Lock是一个接口,而synchronized是Java中的关键字,synchronized是内置的语言实现;
- synchronized在发生异常时,会自动释放线程占有的锁,因此不会导致死锁现象发生;而Lock在发生异常时,如果没有主动通过unLock()去释放锁,则很可能造成死锁现象,因此使用Lock时需要在finally块中释放锁;
- Lock可以让等待锁的线程响应中断,而synchronized却不行,使用synchronized时,等待的线程会一直等待下去,不能够响应中断;
- 通过Lock可以知道有没有成功获取锁,而synchronized却无法办到。
- Lock可以提高多个线程进行读操作的效率。
在性能上来说,如果竞争资源不激烈,两者的性能是差不多的,而当竞争资源非常激烈时(即有大量线程同时竞争),此时Lock的性能要远远优于synchronized。
Lock接口
public interface Lock {
void lock();
void lockInterruptibly() throws InterruptedException;
boolean tryLock();
boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
void unlock();
Condition newCondition();
}
lock()方法
lock()方法是平常使用得最多的一个方法,就是用来获取锁。如果锁已被其他线程获取,则进行等待。采用Lock,必须主动去释放锁,并且在发生异常时,不会自动释放锁。因此一般来说,使用Lock必须在try{}catch{}块中进行,并且将释放锁的操作放在finally块中进行,以保证锁一定被被释放,防止死锁的发生。通常使用Lock来进行同步的话,是以下面这种形式去使用的:
Lock lock = ...;
lock.lock();
try{
//处理任务
}catch(Exception e){
}finally{
lock.unlock(); //释放锁
}
Condition
关键字synchronized与wait()/notify()这两个方法一起使用可以实现等待/通知模式, Lock锁的newContition()方法返回Condition对象,Condition类也可以实现等待/通知模式。用notify()通知时,JVM会随机唤醒某个等待的线程, 使用Condition类可以进行选择性通知。
Condition比较常用的两个方法:
- await()会使当前线程等待,同时会释放锁,
- signal()时,线程会重新获得锁并继续执行。用于唤醒一个等待的线程。
注意:在调用Condition的await()/signal()方法前,也需要线程持有相关的Lock锁,调用await()后线程会释放这个锁,在singal()调用后会从当前Condition对象的等待队列中,唤醒 一个线程,唤醒的线程尝试获得锁, 一旦获得锁成功就继续执行。
public class LockConditionTest {
public static void main(String[] args) {
Lock lock = new ReentrantLock();
lock.lock();
try {
Condition condition = lock.newCondition();
//先持有锁
condition.await();//线程等待
//释放锁
condition.signal();//线程唤醒
condition.signalAll();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
ReentrantLock
ReentrantLock,意思是“可重入锁”,是唯一实现了Lock接口的类,并且ReentrantLock提供了更多的方法。
ReadWriteLock
ReadWriteLock也是一个接口,在它里面只定义了两个方法:
public interface ReadWriteLock {
/**
Returns the lock used for reading.
@return the lock used for reading.
*/
Lock readLock();
/**
Returns the lock used for writing.
@return the lock used for writing.
*/
Lock writeLock();
}
一个用来获取读锁,一个用来获取写锁。也就是说将文件的读写操作分开,分成 2 个锁来分配给线程,从而使得多个线程可以同时进行读操作。ReentrantReadWriteLock 实现了ReadWriteLock接口。ReentrantReadWriteLock里面提供了很多丰富的方法,不过最主要的有两个方法:readLock()和writeLock()用来获取读锁和写锁。
涉及到写的操作都会阻塞等待:无论是写在前还是在后,读读则不会
- 如果有一个线程已经占用了读锁,则此时其他线程如果要申请写锁,则申请写锁的线程会一直等待释放读锁。
- 如果有一个线程已经占用了写锁,则此时其他线程如果申请写锁或者读锁,则申请的线程会一直等待释放写锁。
停止线程
停止线程思路
- 使用退出标志,使线程正常退出,也就是当run方法完成后线程终止。
- 使用stop方法强行终止线程(这个方法不推荐使用,因为stop和suspend、resume一样,也可能发生不可预料的结果)。
- 使用interrupt方法中断线程。