并发编程基础概念-锁

Posted by Pismery Liu on Friday, November 30, 2018

TOC

偏向锁、轻量级锁

引入偏向锁和轻量级锁目的都是为了当没有多线程竞争或者竞争不激烈的情况下,减少传统的重量级锁使用操作系统的互斥量(即Monitor)导致的性能消耗。轻量级锁通过CAS操作避免使用互斥量。而偏向锁则在无线程竞争的情况下整个同步都取消了。

Java6后默认启用偏向锁,若程序确认存在大量锁对象,并且都是高度并发情况,可以禁用偏向锁来提升性能

## 关闭偏向锁
-XX:-UseBiasedLocking   (注意是-号)

## 开启偏向锁
-XX:+UseBiasedLocking -XX:BiasedLockingStartupDelay=0  (注意+号,BiasedLockingStartupDelay因为默认是系统启动后延迟一段时间再开启偏向锁,所以配置成立即启用)

自旋锁

自旋锁是采用让当前线程不停地的在循环体内执行实现的,当循环的条件被其他线程改变时 才能进入临界区。

与互斥锁相似,基本作用是用于线程(进程)之间的同步。与普通锁不同的是,一个线程A在获得普通锁后,如果再有线程B试图获取锁,那么这个线程B将会挂起(阻塞);试想下,如果两个线程资源竞争不是特别激烈,而处理器阻塞一个线程引起的线程上下文的切换的代价高于等待资源的代价的时候(锁的已保持者保持锁时间比较短),那么线程B可以不放弃CPU时间片,而是在“原地”忙等,直到锁的持有者释放了该锁,这就是自旋锁的原理,可见自旋锁是一种非阻塞锁。

公平锁、非公平锁

ReentrantLock可实现公平锁和非公平锁方式。

公平锁:进入先检测等待队列是否有等待线程,若没有或者是第一个则获取锁,否则在队列中排队FIFO

非公平锁:进入直接获取锁,获取失败了才加入等待队列排队。

可重入锁

可重入锁指的是可重复可递归调用的锁,在外层使用锁之后,在内层仍然可以使用,并且不发生死锁(前提得是同一个对象或者class),这样的锁就叫做可重入锁。ReentrantLock和synchronized都是可重入锁

对象锁 vs 类锁

对象锁

介绍

在java中,对非静态方法和非静态代码块加synchronized表明对于每个对象实例最多只有一个线程能够访问此方法或代码块。用于保证实例对象级别的线程安全。

示例代码

public class Demo {
    public synchronized void method() {
        ...
    }
}


public class Demo {
    public void method() {
        synchronized(this) {
            ...
        }
    }
}


public class Demo {
    private final Object lock = new Object();
    
    public void method() {
        synchronized(lock) {
            ...
        }
    }
}

类锁

介绍

类锁避免了所有线程通过不同的实例进入代码块。即在某一时刻,系统有多个对象实例,只有一个线程通过其中一个实例访问代码块,其他实例和线程被阻塞。类锁用于保证静态属性的线程安全,因为静态属性是所有实例共享的。

示例代码

public class Demo {
    public synchronized static void method() {
        ...
    }
}


public class Demo {
    private final static Object lock = new Object();
    
    public void method() {
        synchornized(lock) {
            ...
        }
    }
}

关键点

  1. synchronized关键字能够用于静态或非静态的方法和代码块,不能用于成员变量和构造函数。
  2. 任何一个线程进入同步方法都需要获取锁,并在执行完同步方法或出现Error或者Exception后释放锁。
  3. synchronized是可重入的,即当一个线程已经获取锁,可以访问另一个需要相同锁的同步方法,而不需要重新获取锁。
  4. 当“synchronized (lock)”中lock是null时,会抛出NPE
  5. synchronized很消耗性能,因此当必须要使用同步时,尽可能的缩小同步的范围。
  6. 有可能静态同步方法和非静态同步方法会同时并发的跑,因为他们锁的对象不同。
  7. 不要同步一个非final的变量,最好用String变量,因为本身就是final不可变的?。
  8. 当一个线程进入了同步方法,其他线程是能够访问非同步的方法。

使用同步非final示例

public class Demo {
    private Object lock = new Object();
    
    public void setObejct(Obejct lock) {
        this.lock = lock;
    }
    
    public void method() {
        synchornized(lock) {
            ....
        }
    }
    
    
    public static void main(String[] args) {
        thread1:run the method and get the lock;
        thread2:setObejct(new Object);
        thread2:run the method;(Although threa1 no release the lock, thread 2 will also get the new lock)
    }
}


参考链接