多个线程操作一个资源的情况下,导致资源数据前后不一致。这样就需要协调线程的调度,即线程同步。 解决多个线程使用共通资源的方法是:线程操作资源时独占资源,其他线程不能访问资源。使用锁可以保证在某一代码段上只有一条线程访问共用资源。
synchronized关键字可以放在方法的前面、对象的前面、类的前面。synchronized关键字用作锁定当前对象。这种锁又称为"互斥锁"。
synchronized 可以修饰方法,代码块, 不能修饰构造器,成员变量等。
锁逻辑步骤: 加锁 - 修改 - 释放锁。
Java的多线程支持引入了同步监视器, 使用同步监视器的通用方法就是同步代码块。
synchronized(obj){
}
synchronized 后面的 obj 就是同步监视器,
线程开始执行同步代码块之前,必须获得对同步监视器的锁定。
只能有一个线程可以获得对同步监视器的锁定,当同步代码块执行完之后,该线程会释放对该同步监视器的锁定。
Java允许对任何对象作为同步监视器, 通常使用可能被并发访问的共享资源充当同步监视器。
使用 synchronized 修饰的方法,称为同步方法。
对于 synchronized 的修饰的实例方法,同步方法的同步监视器就是 this, 调用该方法的对象,无需显式指定。
public synchronized void test(){
}
使用同步方法可以方便的实现线程安全的类,
不可变类是线程安全的, 它的对象状态不可改变。
可变类需要额外的方法保证其线程安全, 不用对所有方法同步,只对竞争资源的方法进行同步。
线程进入同步代码块,同步方法之前,必须先获得对同步监视器的锁定,
下面情况会释放锁:
下面情况不会释放同步监视器:
Lock提供了比synchronized 更广泛的锁定操作,Lock允许实现更灵活的结构,支持多个相关的Condition对象。
通常, 锁提供了对共享资源的独占访问。
某些锁可能允许对共享资源并发访问, 如 ReadWriteLock(读写锁)
public interface Lock {
/**
* Acquires the lock.
*
* If the lock is not available then the current thread becomes
* disabled for thread scheduling purposes and lies dormant until the
* lock has been acquired.
*
*
Implementation Considerations
*
*
A {@code Lock} implementation may be able to detect erroneous use
* of the lock, such as an invocation that would cause deadlock, and
* may throw an (unchecked) exception in such circumstances. The
* circumstances and the exception type must be documented by that
* {@code Lock} implementation.
*/
void lock();
}
public class ReentrantLock implements Lock, java.io.Serializable {
}
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();
}
public class ReentrantReadWriteLock
implements ReadWriteLock, java.io.Serializable {
}
Lock, ReadWriteLock 是两个根接口。
ReentrantLock, ReentrantReadWriteLock 是他俩的实现类
ReentrantLock,是可重入锁,
StampedLock 是 8新增, 可以替代传统的ReentrantReadWriteLock
public class TestClass {
private final ReentrantLock lock = new ReentrantLock();
public void test(){
lock.lock();
try {
}
finally {
lock.unlock();
}
}
}
使用 finally 确保释放锁。
Lock提供了 tryLock()方法, 试图获取可中断锁的 lockInterruptibly(), 获取超时失效锁 tryLock(long, TimeUnit).
ReentrantLock()具有可重入性, 一个已被加锁的 ReentrantLock()可以再次加锁,ReentrantLock对象会维持一个计数器来追踪lock()方法的嵌套使用, 每次调用lock()加锁后,必须显式调用 unlock() 来释放锁。
两个线程相互等待对方释放同步监视器时就会发生死锁, Java虚拟机没有检测,也没有采取措施处理死锁, 因此编程时要避免死锁出现。
Thread类的 suspend()容易导致死锁, 不推荐使用,
一旦进入死锁, 线程进入阻塞状态,无法继续。
使用 Object类的 wait(), notify(), notifyAll() 三个方法 ,进行线程协调通信运行。
这三个方法属于Object类, 不熟悉Thread类。
synchronized同步方法,使用 this 为同步监视器,可以直接调用
synchronized同步代码块, 同步监视器为后面对象, 必须使用该对象调用这三个方法。
wait() : 导致线程等待, 直到notify()方法唤醒, 可以设置时间
notify():唤醒等待的线程, 多个时唤醒其中一个线程, 选择具有任意性
notifyAll(): 唤醒此同步监视器上等待的所有线程。
public class Account {
private String accountNo; //账号
private double balance; // 余额
private boolean flag = false; //是否有存款标记
public String getAccountNo() {
return accountNo;
}
public void setAccountNo(String accountNo) {
this.accountNo = accountNo;
}
//余额不允许随便修改,去掉了 balance 的setter方法
public double getBalance() {
return balance;
}
public synchronized void draw(double drawAmount){
try {
//flag false: 没有存款, 阻塞
if(!flag){
wait();
}
else {
//取钱
balance -= drawAmount;
flag = false;
//唤醒其他线程
notifyAll();
}
}
catch (InterruptedException e){
e.printStackTrace();
}
}
public synchronized void deposit(double depositAmount){
try {
// flag true : 已有存款, 阻塞
if(flag){
wait();
}
else {
//存钱
balance += depositAmount;
flag = true;
//唤醒其他
notifyAll();
}
}
catch (InterruptedException e){
e.printStackTrace();
}
}
}
当使用Lock对象时, 不能使用 wait(), notify(), notifyAll()。
使用 Condition类保持协调。
Condition类提供下面方法:
public class Account {
private String accountNo; //账号
private double balance; // 余额
private boolean flag = false; //是否有存款标记
private final Lock lock = new ReentrantLock(); //定义Lock
private final Condition condition = lock.newCondition(); //获得lock对应的Condition
public String getAccountNo() {
return accountNo;
}
public void setAccountNo(String accountNo) {
this.accountNo = accountNo;
}
//余额不允许随便修改,去掉了 balance 的setter方法
public double getBalance() {
return balance;
}
public void draw(double drawAmount){
lock.lock();
try {
//flag false: 没有存款, 阻塞
if(!flag){
condition.await();
}
else {
//取钱
balance -= drawAmount;
flag = false;
//唤醒其他线程
condition.signalAll();
}
}
catch (InterruptedException e){
e.printStackTrace();
}
finally {
lock.unlock();
}
}
public void deposit(double depositAmount){
lock.lock();
try {
// flag true : 已有存款, 阻塞
if(flag){
condition.await();
}
else {
//存钱
balance += depositAmount;
flag = true;
//唤醒其他
condition.signalAll();
}
}
catch (InterruptedException e){
e.printStackTrace();
}
finally {
lock.unlock();
}
}
}
阻塞队列BlockingQueue, 是 Queue 的子接口。用作线程同步的工具。
当生产者线程试图向BlockingQueue 放入元素时,如果已满,则阻塞。
当消费者获取元素时,如果已空,则阻塞。
BlockingQueue 提供两个支持阻塞的方法:
put(): 放入队列, 已满, 阻塞线程
take(): 头部取出元素, 已空, 阻塞线程。
BlockingQueue 继承了 Queue 接口的方法
队尾插入元素时:队列已满时:
add() : 抛出异常
offer(): 返回false
put():阻塞队列
取出元素, 队列为空时:
remove():抛出异常
poll(): 返回 false
take(): 阻塞队列
BlockingQueue的实现类
ArrayBlockingQueue : 基于数组实现
LinkedBlockingQueue: 基于链表实现
PriorityBlockingQueue: 类似 PriorityQueue, 按排序大小返回
SynchronousQueue: 同步队列, 按照存、取操作交替进行
DelayQueue: 基于 PriorityBlockingQueue 实现, 根据getDelay() 返回值排序
//大小为2
BlockingQueue<String> blockingQueue = new ArrayBlockingQueue<>(2);
blockingQueue.put("a");
blockingQueue.put("b");
blockingQueue.put("c"); //线程阻塞