JAVA 各种锁机制
可重入锁
可重锁是指同一个线程,外层函数获取锁后,内层函数可以自动获取到锁。
java中synchronized和ReentrantLock都是可重入锁。
对于synchronized,其实现机制有jvm实现。
对于ReentrantLock,其继承自父类AQS,其父类AQS中维护了一个同步状态status来计数重入次数,status初始值为0。
当线程尝试获取锁时,可重入锁先尝试获取并更新status值,如果status == 0表示没有其他线程在执行同步代码,则把status置为1,当前线程开始执行。如果status != 0,则判断当前线程是否是获取到这个锁的线程,如果是的话执行status+1,且当前线程可以再次获取锁。而非可重入锁是直接去获取并尝试更新当前status的值,如果status != 0的话会导致其获取锁失败,当前线程阻塞。
释放锁时,可重入锁同样先获取当前status的值,在当前线程是持有锁的线程的前提下。如果status-1 == 0,则表示当前线程所有重复获取锁的操作都已经执行完毕,然后该线程才会真正释放锁。而非可重入锁则是在确定当前线程是持有锁的线程之后,直接将status置为0,将锁释放。
测试代码
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;
/**
* 可重入锁
* 1 synchronized
* 2 ReentrantLock
*/
public class Main {
private ReentrantLock lock = new ReentrantLock();
private synchronized void get() {
System.out.println(Thread.currentThread().getName() + " invoke get() method");
set();
}
private synchronized void set() {
System.out.println(Thread.currentThread().getName() + " invoke set() method");
}
private void _get() {
lock.lock();
try {
System.out.println(Thread.currentThread().getName() + " invoke _get() method");
_set();
} catch (Exception ignored) {
} finally {
lock.unlock();
}
}
private void _set(){
lock.lock();
try {
System.out.println(Thread.currentThread().getName() + " invoke _set() method");
} catch (Exception ignored) {
} finally {
lock.unlock();
}
}
public static void main(String[] args) throws InterruptedException {
Main main = new Main();
new Thread(main::get, "t1").start();
new Thread(main::get, "t2").start();
TimeUnit.SECONDS.sleep(1);
System.out.println();
System.out.println();
System.out.println();
new Thread(main::_get,"t3").start();
new Thread(main::_get,"t4").start();
}
}
输出结果
t1 invoke get() method
t1 invoke set() method
t2 invoke get() method
t2 invoke set() method
t3 invoke _get() method
t3 invoke _set() method
t4 invoke _get() method
t4 invoke _set() method
自旋锁
自旋锁(spinlock):当一个线程在获取锁的时候,如果锁已经被其它线程获取,那么该线程将循环等待,然后不断的判断锁是否能够被成功获取,直到获取到锁才会退出循环。
优点:
自旋锁不会使线程状态发生切换,一直处于用户态,即线程一直都是active的;不会使线程进入阻塞状态,减少了不必要的上下文切换,执行速度快
非自旋锁在获取不到锁的时候会进入阻塞状态,从而进入内核态,当获取到锁的时候需要从内核态恢复,需要线程上下文切换。 (线程被阻塞后便进入内核(Linux)调度状态,这个会导致系统在用户态与内核态之间来回切换,严重影响锁的性能)
缺点
若锁被其他线程长时间占用,由于一直死循环,会带来许多性能上的开销。
java实现自旋锁
package demo;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
public class SpinLock {
AtomicReference<Thread> reference = new AtomicReference<>();
public void lock() {
Thread thread = Thread.currentThread();
while (!reference.compareAndSet(null, thread)) {
}
}
public void unlock() {
Thread thread = Thread.currentThread();
reference.compareAndSet(thread, null);
}
public static void main(String[] args) throws InterruptedException {
SpinLock spinLock = new SpinLock();
new Thread(() -> {
spinLock.lock();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
spinLock.unlock();
}, "t2").start();
TimeUnit.SECONDS.sleep(1);
new Thread(() -> {
spinLock.lock();
spinLock.unlock();
}, "t2").start();
}
}
实现可重入自选锁
思路为维持一个状态变量,记录当前lock的次数。
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
public class SpinLock {
AtomicReference<Thread> reference = new AtomicReference<>();
private AtomicInteger num = new AtomicInteger(0);
public void lock() {
Thread thread = Thread.currentThread();
if (reference.get() == thread) {
num.incrementAndGet();
return;//当前锁已经获取了,直接return
}
while (!reference.compareAndSet(null, thread)) {
}
}
public void unlock() {
Thread thread = Thread.currentThread();
if (reference.get() != thread) return;
if (num.get() > 0) {//多次lock
num.decrementAndGet();
} else {
reference.compareAndSet(thread, null);
}
}
public static void main(String[] args) throws InterruptedException {
SpinLock spinLock = new SpinLock();
new Thread(() -> {
spinLock.lock();
System.out.printf("%s\tlock%n", Thread.currentThread().getName());
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
spinLock.unlock();
System.out.printf("%s\tunlock%n", Thread.currentThread().getName());
}, "t1").start();
TimeUnit.SECONDS.sleep(1);
new Thread(() -> {
spinLock.lock();
System.out.printf("%s\tlock%n", Thread.currentThread().getName());
spinLock.unlock();
System.out.printf("%s\tunlock%n", Thread.currentThread().getName());
}, "t2").start();
}
}
## 读写锁
在java中,ReentrantLock和synchronized都是独占锁,也就是当一个线程获取到锁后,其他线程会阻塞。
使用ReentrantLock和synchronized可以保证线程获取和写入资源的安全,但是在读多写少的场景下,如果每次对读操作都加上独占锁,那么会降低读的性能。
在此基础上,有了读写锁的应用场景,一般的缓存操作(读缓存和写缓存),更适合用读写锁。
### 测试代码
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.stream.IntStream;
class Cache {
private volatile Map<Integer, Object> map = new HashMap<>();//对于线程共同操作的资源,需要加上volatile
private ReentrantReadWriteLock lock = new ReentrantReadWriteLock();//读写锁
public void put(Integer num, Object o) {
lock.writeLock().lock();
try {
System.out.println(Thread.currentThread().getName() + "\t正在写入," + num + ":" + o);
map.put(num, o);
System.out.println(Thread.currentThread().getName() + "\t写入完成");
} finally {
lock.writeLock().unlock();
}
}
public void get(Integer num) {
lock.readLock().lock();
try {
System.out.println(Thread.currentThread().getName() + "\t正在读取," + num);
Object o = map.get(num);
System.out.println(Thread.currentThread().getName() + "\t读取完成," + num + ":" + o);
} finally {
lock.readLock().unlock();
}
}
}
public class Main {
public static void main(String[] args) {
Cache cache = new Cache();
IntStream.range(0, 10).forEach(i -> {
new Thread(() -> cache.put(i, i), String.valueOf(i)).start();
});
IntStream.range(0, 10).forEach(i -> {
new Thread(() -> cache.get(i), String.valueOf(i)).start();
});
}
}
输出
0 正在写入,0:0
0 写入完成
5 正在写入,5:5
5 写入完成
1 正在写入,1:1
1 写入完成
7 正在写入,7:7
7 写入完成
2 正在写入,2:2
2 写入完成
3 正在写入,3:3
3 写入完成
4 正在写入,4:4
4 写入完成
6 正在写入,6:6
6 写入完成
8 正在写入,8:8
8 写入完成
9 正在写入,9:9
9 写入完成
0 正在读取,0
0 读取完成,0:0
1 正在读取,1
2 正在读取,2
2 读取完成,2:2
1 读取完成,1:1
4 正在读取,4
4 读取完成,4:4
3 正在读取,3
6 正在读取,6
7 正在读取,7
7 读取完成,7:7
5 正在读取,5
8 正在读取,8
6 读取完成,6:6
3 读取完成,3:3
8 读取完成,8:8
9 正在读取,9
5 读取完成,5:5
9 读取完成,9:9
由结果可见,在线程进行写操作时,其操作是没有被阻塞的。
在线程进行读操作时,可以由多个线程来同时读取。
ReentrantReadWriteLock的特性
①具有与ReentrantLock类似的公平锁和非公平锁的实现:默认的支持非公平锁,对于二者而言,非公平锁的吞吐量由于公平锁;
②支持重入:读线程获取读锁之后能够再次获取读锁,写线程获取写锁之后能再次获取写锁,也可以获取读锁。
③锁能降级:遵循获取写锁、获取读锁在释放写锁的顺序,即写锁能够降级为读锁
https://www.cnblogs.com/fsmly/p/10721433.html
JAVA 各种锁机制的更多相关文章
- Java的锁机制--synchronsized关键字
引言 高并发环境下,多线程可能需要同时访问一个资源,并交替执行非原子性的操作,很容易出现最终结果与期望值相违背的情况,或者直接引发程序错误. 举个简单示例,存在一个初始静态变量count=0,两个线程 ...
- java的锁机制
一段synchronized的代码被一个线程执行之前,他要先拿到执行这段代码的权限,在Java里边就是拿到某个同步对象的锁(一个对象只有一把锁): 如果这个时候同步对象的锁被其他线程拿走了,他(这个线 ...
- Java常用锁机制简介
在开发Java多线程应用程序中,各个线程之间由于要共享资源,必须用到锁机制.Java提供了多种多线程锁机制的实现方式,常见的有synchronized.ReentrantLock.Semaphore. ...
- lesson3:java的锁机制原理和分析
jdk1.5之前,我们对代码加锁(实际是对象加锁),都是采用Synchronized关键字来处理,jdk1.5及以后的版本中,并发编程大师Doug Lea在concurrrent包中提供了Lock机制 ...
- 深入浅出 Java Concurrency 锁机制 : AQS
转载:http://www.blogjava.net/xylz/archive/2010/07/06/325390.html 在理解J.U.C原理以及锁机制之前,我们来介绍J.U.C框架最核心也是最复 ...
- [java多线程] - 锁机制&同步代码块&信号量
在美眉图片下载demo中,我们可以看到多个线程在公用一些变量,这个时候难免会发生冲突.冲突并不可怕,可怕的是当多线程的情况下,你没法控制冲突.按照我的理解在java中实现同步的方式分为三种,分别是:同 ...
- java的锁机制——synchronized
一段synchronized的代码被一个线程执行之前,他要先拿到执行这段代码的权限,在java里边就是拿到某个同步对象的锁(一个对象只有一把锁): 如果这个时候同步对象的锁被其他线程拿走了,他(这个线 ...
- [置顶] 深入探析Java线程锁机制
今天在iteye上提了一个关于++操作和线程安全的问题,一位朋友的回答一言点醒梦中人,至此我对Java线程锁有了更加深刻的认识.在这里也做个总结供大家参考. 先看几段代码吧! 代码一: public ...
- Java 线程锁机制 -Synchronized Lock 互斥锁 读写锁
(1)synchronized 是互斥锁: (2)ReentrantLock 顾名思义 :可重入锁 (3)ReadWriteLock :读写锁 读写锁特点: a)多个读者可以同时进行读b)写者必须互斥 ...
随机推荐
- JQuery插件,轻量级表单模型验证(续 二)
好不容易,有心思,那就把没做完的JQuery轻量级表单验证做完吧 之前做到了空参数验证的,现在增加带参数的验证. 附上html <form id="ValidataForm" ...
- excel-格式处理
问题[1]:将excl中数据导出txt,并且每列之间距离一个空格 在C1(任意空列) 输入=A1&" "&B1" "中间是一个半角英文空格下拉 ...
- Python 告诉你疫情扩散有多可怕
今年(2020年)是注定要铭记史册的一年,从年初开始新冠疫情,席卷了全球,中国人民众志成城,为战胜疫情做出了巨大牺牲.最近北京疫情形式又变得严峻,面对疫情我们不能掉以轻心.今天我们模拟一下病毒的扩散过 ...
- GitLab 系列文章
GitLab 系列文章 记录 GitLab 的相关文章 列表 Docker 搭建 GitLab GitLab CI/CD 配置 GitLab 配置模板 访问 GitLab 数据库 GitLab 转让所 ...
- Springboot开启事务的支持
主要分为两步 步骤一.在main方法加上@EnableTransactionManagement注解: @SpringBootApplication @EnableTransactionManagem ...
- Spring IOC 启动过程
1. 引言 本篇博文主要介绍 IOC 容器的启动过程,启动过程分为两个步骤,第一个阶段是容器的启动阶段,第二个阶段是 Bean 实例化阶段,这两个阶段各自需要执行的步骤如下图,接下来会一一介绍. 需要 ...
- idea如何打war包(不使用maven)
用多了maven的小伙伴,应该快忘了怎么不用maven打war包了吧,我也快忘了,所以趁我还记得,赶紧记录下来,多年后,当我回忆起往事........ 而且网上的教程也太坑了吧,牛头不搭马嘴,这害死多 ...
- java 模拟斗地主发牌洗牌
一 模拟斗地主洗牌发牌 1.案例需求 按照斗地主的规则,完成洗牌发牌的动作. 具体规则: 1. 组装54张扑克牌 2. 将54张牌顺序打乱 3. 三个玩家参与游戏,三人交替摸牌,每人17张牌,最后三张 ...
- JVM垃圾回收(GC)
JVM垃圾回收(GC) 1. 判断对象是否可以被回收 引用计数法:每个对象有一个引用计数属性,新增一个引用时计数加1,引用释放时计数减1,计数为0时可以回收.此方法简单,但无法解决对象相互循环引用的问 ...
- Homekit_DoHome_智能通断器
本款通断器适用于IOS和android系统用户,苹果用户可以非常方便的使用siri进行有效控制,android用户需要下载Dohome App进行操作,同时支持市面上主流的智能音箱进行控制. 对于an ...