Java并发同步锁

synchronized

如果某一个资源被多个线程共享,为了避免因为资源抢占导致资源数据错乱,我们需要对线程进行同步,那么synchronized就是实现线程同步的关键字,可以说在并发控制中是必不可少的部分

特性

  1. 原子性:如同事务要么全部执行,要么回滚最初,保证数据的准确性
  2. 可见性:多个线程需要访问一个资源的时候,具有该资源的状态、属性等都具有访问权限
  3. 有序性:在持锁的代码逻辑,具有先后顺序执行
  4. 可重入性:一个线程拥有了锁然而还可以重复申请锁

锁的是什么?

synchronized 可以修复方法、成员函数、静态方法、同时还有代码块,但是归根结底是锁什么?

  1. Class类
  2. new实体对象
private int num = 0;

@Test
public void testMain() throws InterruptedException {

    Thread thread1 = new Thread(new MyRunnable());
    Thread thread2 = new Thread(new MyRunnable());

    thread1.start();
    thread2.start();

    thread1.join();
    thread2.join();

    System.out.println(num);
}


class MyRunnable implements Runnable {
    
    //锁的对象
    private synchronized void add() {
        num++;
    }

    @Override
    public void run() {
        for (int i = 0; i < 10000; i++) {
            add();
        }
    }
}

//  System.out.println(num);  1875

如果想要执行结果是 20000;怎么改? 方法很多抓锁的本质锁谁,优化谁

锁对象
  • 既然锁对象,那么就在多线程Thread执行同一个对象,搞定!
MyRunnable syMyRunnable= new MyRunnable();

Thread thread1 = new Thread(syMyRunnable);
Thread thread2 = new Thread(syMyRunnable);

//  System.out.println(num);  20000
  • 锁对象,也可以在代码块里锁一个具体的对象,
private int num = 0;
private Object object = new Object();

@Test
public void testMain() throws InterruptedException {
    
    Thread thread1 = new Thread(new MyRunnable());
    Thread thread2 = new Thread(new MyRunnable());
    
    //....
}


private void add() {
    //锁代码块中的 唯一对象Object
    synchronized (object) {
        num++;
    }

}

//  System.out.println(num);  20000
锁类

如果想通过锁类,那么就保证Class为static静态即可

private static int num = 0; //静态变量

@Test
public void testMain() throws InterruptedException {

    Thread thread1 = new Thread(new MyRunnable());
    Thread thread2 = new Thread(new MyRunnable());

    thread1.start();
    thread2.start();

    thread1.join();
    thread2.join();

    System.out.println(num);
}

/**
*静态类
*/
static class MyRunnable implements Runnable {
    //静态函数
    private synchronized static void add() {
        num++;
    }

}

//  System.out.println(num);  20000

可以总结:

  • 静态方法锁class类
  • 非静态方法锁对象
  • 当锁类的情况下,会把这个Class类下的所有变量、方法以及引用对象都所锁住,造成资源浪费
  • 一般可锁不被引用的对象Object
  • synchronized是悲观锁
  • 优点:使用简单、不用手动去管理
  • 弊端:太重、锁的属性对象太多、容易造成死锁

CAS

是英文单词Compare And Swap的缩写,翻译过来就是比较并替换

CAS机制当中使用了3个基本操作数:内存地址原值V,旧的预期值A,要修改的新值B

原理操作是:比较 A 与 V 是否相等, 如果比较相等,将 B 写入 V。(交换) 返回操作是否成功。

当多个线程同时对某个资源进行CAS操作,只能有一个线程操作成功,但是并不会阻塞其他线程,其他线程只会收到操作失败的信号。

lock

public interface Lock {

    /**
     *  获取锁  
     */
    void lock();

    /**
     *  如果当前线程未被中断,则获取锁,可以响应中断
     */
    void lockInterruptibly() throws InterruptedException;

    /**
     * 仅在调用时锁为空闲状态才获取该锁,可以响应中断
     */
    boolean tryLock();

    /**
     * 如果锁在给定的等待时间内空闲,并且当前线程未被中断,则获取锁
     */
    boolean tryLock(long time, TimeUnit unit) throws InterruptedException;

    /**
     *  释放锁  
     */
    void unlock();

    /**
     *  返回绑定到此 Lock 实例的新 Condition   
     *  return:Condition接口实例 具有唤醒阻塞的线程
     */
    Condition newCondition();
}

获取锁

  • lock()
  • tryLock()
  • tryLock(long time, TimeUnit unit)
  • lockInterruptibly()
lock()

lock()就是用来获取锁,如果当前锁已被其他线程获取,则进入等待,采用lock()必须主动去释放锁,并且在发生异常时,不会自动释放,因此在使用时会try{},并且将释放操作放到finally

try{
    //处理任务
}catch(Exception ex){

}finally{
    //释放锁
    lock.unlock();   
}
tryLock() && tryLock(long time, TimeUnit unit)
  • tryLock() 方法具有返回值,用来尝试获取锁,如果获取成功返回True,如果获取失败说明此时锁已经被其他线程所占用,则返回false,也就是说这个方法无论如何都会立即返回,而并不是在那里自旋。
  • tryLock(long time, TimeUnit unit) 也是具有返回值,区别是拿不到锁会等待一定的时间,在时间内还拿不到锁,就返回false,同时响应中断,如果一开始拿到锁或者是在等待时间范围类拿到锁就直接返回True
if(lock.tryLock()) {
     try{
         //处理任务
     }catch(Exception ex){

     }finally{
         //释放锁
         lock.unlock();  
     } 
}else {
    //如果不能获取锁,则直接做其他事情
}
lockInterruptibly()

该方法比较特殊,就是当lockInterruptibly()去获取锁的时候,如果线程正在等待取锁,则这个线程能够响应中断,立即中断线程的等待状态,例如:当俩个线程同时通过lock.lockinterruptibly想获取某个锁时,此时Thread-A获取到了锁,而Thread-B只能等待,那么对线程B调用Thread-B.Lockinterruptibly就可以使得Thread-B中断等待过程。

public void method() throws InterruptedException {
    lock.lockInterruptibly();
    try {  
     //.....
    }
    finally {
        lock.unlock();
    }  
}

释放锁

  • unlock()

lock.unlock(); 合适的try{}地方进行释放

绑定用于占用

  • newCondition()
// 只有一个锁
Lock lock = new ReentrantLock(); 

//condition 进行通信管理状态
Condition condition = lock.newCondition();

Condition

对于synchronized锁,线程之间的通信方式是waitnotify。对于Lock接口线程间就是通过Condition方式通信的。它比wait/notify更强大的是,它支持等待过程中不响应中断、多个等待队列和在指定时间苏醒。

//reentrantlock对应一个AQS阻塞队列 ,其内部有个 abstract static class Sync extends AbstractQueuedSynchronizer {}
Lock lock = new ReentrantLock();  

Condition condition = lock.newCondition();

public void conditionWait() throws InterruptedException {
    lock.lock();
    try {
        // 释放cpu的执行资格和执行权,同时释放锁
        condition.await();
    } finally {
        //抛异常时进行释放锁
        lock.unlock();
    }
}

public void conditionSignal() throws InterruptedException {
    lock.lock();
    try {
        //唤醒
        condition.signal();
    } finally {
        //抛异常时进行释放锁
        lock.unlock();
    }
}

Atomic

JDK为了防止一些基本数据以及数组对象,分别在java.util.concurrent包专门处理线程并发安全的工具类,并且包含多个原子操作,

  • 标量类:AtomicBoolean,AtomicInteger,AtomicLong,AtomicReference
  • 数组类:AtomicIntegerArray,AtomicLongArray,AtomicReferenceArray
  • 更新器类:AtomicLongFieldUpdater,AtomicIntegerFieldUpdater,AtomicReferenceFieldUpdater
  • 复合变量类:AtomicMarkableReference,AtomicStampedReference

其内部实现不是简单使用synchronized,而是更加高效的 CAS + volatie,避免大开销,提升执行效率,其中CAS比较通过直接内存指针操作获取unsafe.getAndAddInt(**this**, valueOffset, 1), Unsafe.class是大量native 操作本地方法的类,应用层一般都是通过反射机制操作,而JDK是可以直接使用,其操作不当容易操作各种问题,官方不建议普通开发者进行使用。

volatile

Java语言提供了一种消弱的同步机制,即volatile变量,用来确保将变量的更新操作通知到其他线程,当把变量声明为volatile类型后,编译器与运行的时候都会注意到这个变量是共享的,因此不会将变量上的操作与其他内存操作一起排序。

  • 在访问volatile变量时不会执行加锁操作,因此也就不会使执行线程阻塞,因此volatile变量是一种比synchronized关键字更为轻量级的同步机制

当一个变量定义了volatile之后具有俩中特性:

  1. 此变量在多线程中具有可见性(顶部的特性中有说明)
  2. 禁止指向重新序优化,也就不会和其他内存操作一起排序。俗称内存屏障

常见作用多线程中:“一写多读”,只有唯一的线程(Thread-0)对事件的增删改,其他线程只读取用

总结

  1. 乐观锁:CAS,基准为有对象属性可能会被锁定
  2. 悲观锁:synchronized,基准为当前代码一定都会被锁定
  3. 无锁:轻量级无锁资源,单线程独享
  4. 偏向锁:总是由同一个线程多次获得,例如main-Thread,那么此时的锁为偏向锁
  5. 轻量级锁:由偏向锁升级而来,当有第二个线程也申请该锁的资源,那么一前一后交替执行同步代码
  6. 重量级锁:当同一个时间,多个线程同时竞争资源锁,此时锁就会升级重量级锁,导致开销大执行较重

你可能感兴趣的:(Java并发同步锁)