可重入锁:

简单来说,支持重复加锁,有可重用性

特征:锁可以传递,方法递归传递

目的:避免了死锁现象

代码:

public class Test implements Runnable {

    @Override
public void run() {
method1();
} public synchronized void method1() {
System.out.println("method1");
method2();
} public synchronized void method2() {
System.out.println("method2");
} public static void main(String[] args) {
new Thread(new Test()).start();
} }

打印:

method1
method2

分析:如果锁不能重用,那么这里将会出现死锁问题

使用ReentrantLock锁:

public class TestLock implements Runnable {
//重入锁
private Lock reentrantLock = new ReentrantLock(); @Override
public void run() {
method1();
} public void method1() {
try {
reentrantLock.lock();
System.out.println("method1");
method2();
} catch (Exception e) {
e.printStackTrace();
} finally {
reentrantLock.unlock();
}
} public void method2() {
try {
reentrantLock.lock();
System.out.println("method2");
} catch (Exception e) {
e.printStackTrace();
} finally {
reentrantLock.unlock();
}
} public static void main(String[] args) {
new Thread(new TestLock()).start();
} }

读写锁:

高并发的时候,写操作的同时应当不允许读操作

(多线程中:读-读共存;读-写、写-写都不可以共存)

代码:制造读写操作的线程安全问题

public class TestWriteLock {

    Map<String, String> cache = new HashMap<>();

    //写入元素
public void put(String key, String value) {
try {
System.out.println("开始写入key : " + key + " value : " + value);
Thread.sleep(50);
cache.put(key, value);
System.out.println("完成写入key : " + key + " value : " + value);
} catch (Exception e) {
e.printStackTrace();
}
} //读取元素
public String get(String key) {
System.out.println("开始读取key : " + key);
String value = cache.get(key);
System.out.println("读取成功key : " + key + " value : " + value);
return value;
} public static void main(String[] args) {
TestWriteLock test = new TestWriteLock();
Thread readThread = new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
test.put("i", i + "");
}
}
}); Thread writeThread = new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
test.get("i");
}
}
}); readThread.start();
writeThread.start();
}
}

观察打印:发现不合理

开始写入key : i value : 0
开始读取key : i
读取成功key : i value : null
.................................

分析:在没有写入完成的时候,就开始了读取,得到的结果为空

解决:

1.使用synchronized,虽然可以解决,但是效率低下,写操作同时不能读,产生阻塞

2.使用读写锁

public class TestWriteLock {

    Map<String, String> cache = new HashMap<>();

    ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
ReentrantReadWriteLock.WriteLock writeLock = lock.writeLock();
ReentrantReadWriteLock.ReadLock readLock = lock.readLock(); //写入元素
public void put(String key, String value) {
try {
writeLock.lock();
System.out.println("开始写入key : " + key + " value : " + value);
Thread.sleep(50);
cache.put(key, value);
System.out.println("完成写入key : " + key + " value : " + value);
} catch (Exception e) {
e.printStackTrace();
} finally {
writeLock.unlock();
}
} //读取元素
public String get(String key) {
String value = "";
try {
readLock.lock();
System.out.println("开始读取key : " + key);
value = cache.get(key);
System.out.println("读取成功key : " + key + " value : " + value);
} catch (Exception e) {
e.printStackTrace();
} finally {
readLock.unlock();
}
return value;
} public static void main(String[] args) {
TestWriteLock test = new TestWriteLock();
Thread readThread = new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
test.put("i", i + "");
}
}
}); Thread writeThread = new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
test.get("i");
}
}
});
readThread.start();
writeThread.start();
}
}

观察打印:完美解决

乐观锁:

简单来讲,乐观锁就是没有锁,无阻塞无等待

一条SQL语句做示范:

UPDATE TABLE SET X=X+1,VERSION=VERSION+1 WHERE ID=#{id} AND VERSION=#{version}

在高并发地情况下,假设初始version是1,请求1到来,根据id和version能查到,所以允许更新

请求2同时做操作,但是根据id和version已经查不到了(被请求1修改了),所以不允许更新

悲观锁:

简单来讲,重量级锁, 会阻塞,会进行等待

可以理解为上锁之后只允许一个线程来操作,也就是Java中的synchronized

原子类:

一段模拟线程安全问题的代码:

public class ThreadTest implements Runnable {

    private static int count = 1;

    @Override
public void run() {
while (true) {
Integer count = getCount();
if (count >= 100) {
break;
}
System.out.println(count);
}
} public synchronized Integer getCount() {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
return count++;
} public static void main(String[] args) {
ThreadTest t = new ThreadTest();
new Thread(t).start();
new Thread(t).start();
} }

观察打印后发现果然出现了线程安全问题

一种修改方式:效率较低

    public synchronized Integer getCount() {

使用原子类:乐观锁,底层没有加锁,使用CAS无锁技术

public class ThreadTest implements Runnable {

    // 线程安全
private AtomicInteger atomicInteger = new AtomicInteger(); @Override
public void run() {
while (true) {
Integer count = getCount();
if (count >= 100) {
break;
}
System.out.println(count);
}
} public Integer getCount() {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
return atomicInteger.incrementAndGet();
} public static void main(String[] args) {
ThreadTest t = new ThreadTest();
new Thread(t).start();
new Thread(t).start();
} }

CAS无锁技术(Compare And Swap):

翻译过来为:比较再交换

本地内存中存放共享内存的副本

比如主内存中有i=0,复制到两个线程的本地内存中

两个线程执行了i++,本地内存都变成i=1,然后刷新入主内存

CAS算法:

它包含三个参数CAS(V,E,N):

V表示要更新的变量(主内存)

E表示预期值(本地内存)

N表示新值(新值)

仅当V值等于E值时(主内存=本地内存),才会将V的值设为N

如果V值和E值不同(主内存!=本地内存),则说明已经有其他线程做了更新,则当前线程什么都不做

最后,CAS返回当前V的真实值。

观察原子类的源码:

    /**
* Atomically increments by one the current value.
*
* @return the updated value
*/
public final int incrementAndGet() {
for (;;) {
//获取当前值
int current = get();
//设置期望值
int next = current + 1;
//调用Native方法compareAndSet,执行CAS操作
if (compareAndSet(current, next))
//成功后才会返回期望值,否则无线循环
return next;
}
}

CAS无锁机制的缺点:

1.死循环

2.ABA问题:如果变量V初次读取的时候是A,并且在准备赋值的时候检查到它仍然是A,那能说明它的值没有被其他线程修改过了吗

(如果在这段期间曾经被改成B,然后又改回A,那CAS操作就会误认为它从来没有被修改过。针对这种情况,java并发包中提供了一个带有标记的原子引用类AtomicStampedReference,它可以通过控制变量值的版本来保证CAS的正确性。)

JVM数据同步:采用分布式锁

自旋锁和互斥锁的区别:

悲观和乐观锁的区别,自旋锁是死循环不会阻塞,互斥锁是同一时间只有一个线程访问数据

Java深入学习(5):锁的更多相关文章

  1. Java多线程学习——synchronized锁机制

    Java在多线程中使用同步锁机制时,一定要注意锁对对象,下面的例子就是没锁对对象(每个线程使用一个被锁住的对象时,得先看该对象的被锁住部分是否有人在使用) 例子:两个人操作同一个银行账户,丈夫在ATM ...

  2. 轻松学习java可重入锁(ReentrantLock)的实现原理

    转载自https://blog.csdn.net/yanyan19880509/article/details/52345422,(做了一些补充) 前言 相信学过java的人都知道 synchroni ...

  3. 轻松学习java可重入锁(ReentrantLock)的实现原理(转 图解)

    前言 相信学过java的人都知道 synchronized 这个关键词,也知道它用于控制多线程对并发资源的安全访问,兴许,你还用过Lock相关的功能,但你可能从来没有想过java中的锁底层的机制是怎么 ...

  4. Java多线程学习(六)Lock锁的使用

    系列文章传送门: Java多线程学习(二)synchronized关键字(1) Java多线程学习(二)synchronized关键字(2) Java多线程学习(三)volatile关键字 Java多 ...

  5. Java基础学习笔记: 多线程,线程池,同步锁(Lock,synchronized )(Thread类,ExecutorService ,Future类)(卖火车票案例)

    多线程介绍 学习多线程之前,我们先要了解几个关于多线程有关的概念.进程:进程指正在运行的程序.确切的来说,当一个程序进入内存运行,即变成一个进程,进程是处于运行过程中的程序,并且具有一定独立功能. 线 ...

  6. Java并发包下锁学习第一篇:介绍及学习安排

    Java并发包下锁学习第一篇:介绍及学习安排 在Java并发编程中,实现锁的方式有两种,分别是:可以使用同步锁(synchronized关键字的锁),还有lock接口下的锁.从今天起,凯哥将带领大家一 ...

  7. Java并发包下锁学习第二篇Java并发基础框架-队列同步器介绍

    Java并发包下锁学习第二篇队列同步器 还记得在第一篇文章中,讲到的locks包下的类结果图吗?如下图: ​ 从图中,我们可以看到AbstractQueuedSynchronizer这个类很重要(在本 ...

  8. java并发学习第五章--线程中的锁

    一.公平锁与非公平锁 线程所谓的公平,就是指的是线程是否按照锁的申请顺序来获取锁,如果是遵守顺序来获取,这就是个公平锁,反之为非公平锁. 非公平锁的优点在于吞吐量大,但是由于其不是遵循申请锁的顺序来获 ...

  9. Java多线程系列--“JUC锁”04之 公平锁(二)

    概要 前面一章,我们学习了“公平锁”获取锁的详细流程:这里,我们再来看看“公平锁”释放锁的过程.内容包括:参考代码释放公平锁(基于JDK1.7.0_40) “公平锁”的获取过程请参考“Java多线程系 ...

  10. Java多线程系列--“JUC锁”09之 CountDownLatch原理和示例

    概要 前面对"独占锁"和"共享锁"有了个大致的了解:本章,我们对CountDownLatch进行学习.和ReadWriteLock.ReadLock一样,Cou ...

随机推荐

  1. 【java】定时任务@Scheduled

    每隔5秒执行一次:"*/5 * * * * ?" 每隔1分钟执行一次:"0 */1 * * * ?" 每天23点执行一次:"0 0 23 * * ?& ...

  2. Q1094

    一,看题 1,字符串确实是我的弱项. 2, 二,看题解 #include<iostream> #include<string> using namespace std; int ...

  3. [RN] React Native 使用 阿里 ant-design

    React Native 使用 阿里 ant-design 实例效果如图: 一.安装 npm install antd-mobile-rn --save npm install babel-plugi ...

  4. C语言实现Socket简单通信

    环境是linux,不过应该没什么影响,因为只用到了socket的基本用法,没有涉及pthread等. 分为服务器端和客户端,服务器端监听端口发来的请求,收到后向客户端发送一个Hello World,客 ...

  5. 回溯法 | 图的m着色问题

    学习链接:算法 图的M着色问题 虽然今早9点才醒来,10点才来教室,但是coding得很高效.吃个早餐,拉个粑粑的时间,就把算法书上的[图的m着色]问题看明白了,大脑里也形成了解决问题的框架. 其实这 ...

  6. PATA1028 List Sorting

    Excel can sort records according to any column. Now you are supposed to imitate this function. Input ...

  7. Biorhythms(信息学奥赛一本通 1639)

    题目描述: 人生来就有三个生理周期,分别为体力.感情和智力周期,它们的周期长度为23天.28天和33天.每一个周期中有一天是高峰.在高峰这天,人会在相应的方面表现出色.例如,智力周期的高峰,人会思维敏 ...

  8. 在Proxmox VE上运行OpenWrt/LEDE虚拟机——导入OW/LEDE固件文件到虚拟机中

    PVE的OW/LEDE虚拟机初始化创建完成后,需要将编译好的固件文件上传到PVE主机上,然后转换为更适合KVM使用的磁盘映像格式并导入到OW/LEDE虚拟机中,这样就可以更好地使用基于KVM的OW/L ...

  9. 【技术博客】 Laravel 5.1单元测试(PHPUnit)入门

    目录 Laravel 5.1单元测试(PHPUnit)入门 简介 安装与配置 1. 安装 2. 配置 编写测试样例 1. 新建测试样例 2. 编写函数的测试 3. 编写Web功能测试 运行测试与查看结 ...

  10. Docker 一步搞定 ZooKeeper 集群的搭建

    Docker 一步搞定 ZooKeeper 集群的搭建 背景 原来学习 ZK 时, 我是在本地搭建的伪集群, 虽然说使用起来没有什么问题, 但是总感觉部署起来有点麻烦. 刚好我发现了 ZK 已经有了 ...