锁的种类:当我们刚接触到线程的时候,“锁”是一个不可避免的话题,按照锁的名称来说,可以有这些分类方式:
对象锁/类锁,公平锁/非公平锁,自旋锁,可重入锁,偏向锁/轻量级锁/重量级锁,悲观锁/乐观锁,共享锁/排它锁,读写锁,互斥锁,显式锁/隐式锁,内置锁等等。
对象锁:当synchronized修饰的是非静态方法或代码块时,代表锁住的是当前对象,使用格式有以下几种:
synchronized 方法名;
synchronized(this);
synchronized(任一对象);
前两者锁住当前对象,后者锁住任意一个对象
类锁:当synchronized修饰的是静态方法时或代码块,由于一个类中的静态方法和静态变量在内存中只有一份,所以,当将一个静态方法或代码块被关键字synchronized修饰时,此类的所有对象都将会共用同一把锁,将其称为类锁,使用格式有以下两种:
static synchronized 方法名;直接在静态方法中加入synchronized关键字进行修饰
synchronized(类的class对象);
公平锁:公平锁是严格按照线程启动的顺序进行执行,在线程请求时,如果资源正在被占用,线程将会放置到一个先进先出的队列中进行排队等候,然后按照先进先出的策略进行加锁
优点:所有线程都会得到资源
缺点:除了第一个线程,其他线程都会堵塞,CPU唤醒开销大
非公平锁:非公平锁允许插队,执行顺序抢占决定,在线程请求时,当新线程发现没有线程正在使用资源时,将会试图抢占资源,如果资源被占用,则同样加入队列
优点:CPU无需唤醒线程,减少开销
缺点:有些线程永远不能得到线程,导致饿死
实现:公平锁和非公平锁都可以使用ReentrantLock实现
默认非公平锁:ReentrantLock lock = new ReentrantLock();
构造器传入false仍是非公平锁:ReentrantLock lock = new ReentrantLock(false);
实现公平锁:传入true:ReentrantLock lock = new ReentrantLock(true);
/**
* Creates an instance of {@code ReentrantLock}.
* This is equivalent to using {@code ReentrantLock(false)}.
*/
public ReentrantLock() {
sync = new NonfairSync();
}
/**
* Creates an instance of {@code ReentrantLock} with the
* given fairness policy.
*
* @param fair {@code true} if this lock should use a fair ordering policy
*/
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
FairSync()和NonFairSync()是ReentrantLock类的两个静态内部类,顾名思义,分别代表了公平锁和非公平锁
内部实现的主要区别在于:
非公平锁当线程进入时,直接判断锁状态值是否为0,为0代表锁没被占用,则可以直接使用,否则进入队列等待
公平锁当线程进入时,除了判断锁状态值,还需要判断有无正在等待线程,有则加入队列,否则获取锁
自旋锁:自旋锁的含义为:当一个线程尝试获取到锁未成功时,不会进入阻塞状态,而是不断地循环,试图获取锁,直至线程成功获取到锁
优点:不会进行线程间的切换,减少了不必要的操作,使程序执行速度加快
缺点:若当前线程执行时间过长,会导致其他线程一直自旋,极度消耗CPU
实现:利用CAS算法能够实现简单的自旋锁
互斥锁:当一个线程尝试获取锁失败时,进入阻塞状态
可重入锁:可重入锁可以理解为当同一线程获取了锁,并且进入了方法A中,该线程在A没有释放锁的时候,可以进入另一个方法B,例如在外层函数获得锁之后,并没有退出函数的情况下,内层函数仍然可以获取到该锁
常用的synchronized和ReentrantLock都是可重入锁
优点:可以在一定程度上避免死锁
偏向锁:在程序应用过程中往往会发现这样一件事:锁总是由同一个线程占用,而很少有竞争的发生,这种情况则可以使用偏向锁,,减少性能的消耗
轻量级锁:当偏向锁中出现线程竞争的情况时,获得偏向锁的线程会被挂起,偏向锁将会升级,成为轻量级锁,轻量级锁和偏向锁一样,同样是使用CAS来判断是否有线程在进行长期竞争,如果有竞争,将会膨胀为重量级锁
重量级锁:指的是当一个线程获取锁时,其他线程将会直接挂起,进入阻塞状态,与常用synchronized加锁类似
悲观锁:每次读取数据时都认为其他线程会对其进行更改,适用于多写的场景
乐观锁:每次读取数据时都认为其他线程不会对其进行更改,适用于多读的场景
共享锁:称为读锁,若线程对数据加上读锁,则所有线程都可以进行数据的读取,但无法进行数据的写入
排它锁:又称为写锁,若线程对数据加上写锁,则只有该线程能够对其读写操作,其他线程都无法
对数据做任何操作
显式锁:主要指lock
隐式锁:主要指synchronized
二者区别:
区别 | lock | synchronized |
---|---|---|
层面 | 接口 | 关键字 |
分类 | 显式锁 | 隐式锁 |
释放 | 需要手动释放锁 | 不需要手动释放锁 |
使用 | 指定起始结束位置 | 加在方法或代码块上 |
大量线程时性能 | 高 | 低 |
异常 | 没释放可能产生死锁 | 自动释放,故不会产生死锁 |