Java线程同步

线程同步

  • 线程同步
    • 同步代码块
    • 同步方法
    • 释放同步监视器的锁定
  • 同步锁Lock
  • 死锁
  • 线程通信
    • wait(), notify()
    • 使用Condition控制线程
    • BlockingQueue

线程同步

多个线程操作一个资源的情况下,导致资源数据前后不一致。这样就需要协调线程的调度,即线程同步。 解决多个线程使用共通资源的方法是:线程操作资源时独占资源,其他线程不能访问资源。使用锁可以保证在某一代码段上只有一条线程访问共用资源。

synchronized关键字可以放在方法的前面、对象的前面、类的前面。synchronized关键字用作锁定当前对象。这种锁又称为"互斥锁"。
synchronized 可以修饰方法,代码块, 不能修饰构造器,成员变量等。
锁逻辑步骤: 加锁 - 修改 - 释放锁。

同步代码块

Java的多线程支持引入了同步监视器, 使用同步监视器的通用方法就是同步代码块。

  synchronized(obj){
      
  }

synchronized 后面的 obj 就是同步监视器,
线程开始执行同步代码块之前,必须获得对同步监视器的锁定。
只能有一个线程可以获得对同步监视器的锁定,当同步代码块执行完之后,该线程会释放对该同步监视器的锁定。
Java允许对任何对象作为同步监视器, 通常使用可能被并发访问的共享资源充当同步监视器。

同步方法

使用 synchronized 修饰的方法,称为同步方法。
对于 synchronized 的修饰的实例方法,同步方法的同步监视器就是 this, 调用该方法的对象,无需显式指定。

    public synchronized  void test(){
        
    }

使用同步方法可以方便的实现线程安全的类,
不可变类是线程安全的, 它的对象状态不可改变。
可变类需要额外的方法保证其线程安全, 不用对所有方法同步,只对竞争资源的方法进行同步。

释放同步监视器的锁定

线程进入同步代码块,同步方法之前,必须先获得对同步监视器的锁定,
下面情况会释放锁:

  • 代码块执行结束, 是否同步监视器
  • 执行中 遇到 break; return;终止了该代码块,该方法的执行
  • 遇到了未处理的 Error, Exception, 导致代码异常结束
  • 执行了同步监视器对象的 wait()方法,则当前线程暂停,释放同步监视器

下面情况不会释放同步监视器:

  • 调用 Thread.sleep(), Thread.yield() 方法来暂停当前线程
  • 调用 suspend()方法挂起。

同步锁Lock

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()容易导致死锁, 不推荐使用,
一旦进入死锁, 线程进入阻塞状态,无法继续。

线程通信

wait(), notify()

使用 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();
        }
    }
}

使用Condition控制线程

当使用Lock对象时, 不能使用 wait(), notify(), notifyAll()。
使用 Condition类保持协调。
Condition类提供下面方法:

  • await(): 类似 wait() . 当前线程等待
  • signal():唤醒此Lock对象的单个线程
  • signalAll(): 唤醒等待的所有线程
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

阻塞队列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");  //线程阻塞
        

你可能感兴趣的:(java)