可重入锁:

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

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

目的:避免了死锁现象

代码:

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. 补充:垃圾回收机制、线程池和ORM缺点

    补充:垃圾回收机制.线程池和ORM缺点 垃圾回收机制不仅有引用计数,还有标记清除和分代回收 引用计数就是内存地址的门牌号,为0时就会回收掉,但是会出现循环引用问题,这种情况下会导致内存泄漏(即不会被用 ...

  2. PHP csv导出数据

    全部导出和时间导出 html代码,全程并不需要引用什么插件 <include file="public@header"/> <link href="__ ...

  3. Centos7安装MySQL(多图)

    文章目录 一.在线安装1.替换网易yum源2.清理缓存3.下载rpm文件4.安装MySQL数据库二.本地安装1.上传MySQL安装包2.安装依赖的程序包3.卸载mariadb程序包4.安装MySQL程 ...

  4. 模拟赛 T3 DFS序+树状数组+树链的并+点权/边权技巧

    题意:给定一颗树,有 $m$ 次操作. 操作 0 :向集合 $S$ 中加入一条路径 $(p,q)$,权值为 $v$ 操作 1 :给定一个点集 $T$,求 $T$ 的并集与 $S$ 中路径含交集的权和. ...

  5. GoogleHacking语法篇

    常用GoogleHacking语法: 1.intext:(仅针对Google有效) 把网页中的正文内容中的某个字符作为搜索的条件 2.intitle: 把网页标题中的某个字符作为搜索的条件 3.cac ...

  6. 洛谷 p1047 校门外的树 线段树做法

    非常easy, 注意一下它是两端开始,也就是说0的位置也有一棵树就好了 我由于太弱了,一道红题交了4,5遍 由于树的砍了就没了,lazy标记最大就是1; 直接贴代码吧 #include<bits ...

  7. NOIp初赛题目整理

    NOIp初赛题目整理 这个 blog 用来整理扶苏准备第一轮 csp 时所做的与 csp 没 有 关 系 的历年 noip-J/S 初赛题目,记录了一些我从不知道的细碎知识点,还有一些憨憨题目,不定期 ...

  8. 缓存穿透 & 缓存击穿 & 缓存雪崩

    参考文档: 缓存穿透和缓存失效的预防和解决:https://blog.csdn.net/qq_16681169/article/details/75138876 缓存穿透 缓存穿透是指查询一个一定不存 ...

  9. c++ rvo vs std::move

    c++ rvo vs std::move To summarize, RVO is a compiler optimization technique, while std::move is just ...

  10. Leetcode 初刷(1)

    1.给定一个整数数组 nums 和一个目标值 target,请你在该数组中找出和为目标值的那 两个 整数,并返回他们的数组下标. 你可以假设每种输入只会对应一个答案.但是,你不能重复利用这个数组中同样 ...