Java锁机制了解一下
前言
回顾前面:
只有光头才能变强!
本文章主要讲的是Java多线程加锁机制,有两种:
- Synchronized
- 显式Lock
不得不唠叨几句:
- 在《Java核心技术卷 一》是先讲比较难的显式Lock,而再讲的是比较简单的Synchronized
- 而《Java并发编程实战》在前4章零散地讲解了Synchronized,将显式Lock放到了13章
其实都比较坑,如果能先系统讲了Synchronized锁机制,接着讲显式Lock锁机制,那就很容易理解了。也不需要跨那么多章节。
那么接下来我们就开始吧~
一、synchronized锁
1.1synchronized锁是什么?
synchronized是Java的一个关键字,它能够将代码块(方法)锁起来
- 它使用起来是非常简单的,只要在代码块(方法)添加关键字synchronized,即可以实现同步的功能~
public synchronized void test() {
// 关注公众号Java3y
// doSomething
}
synchronized是一种互斥锁
- 一次只能允许一个线程进入被锁住的代码块
synchronized是一种内置锁/监视器锁
- Java中每个对象都有一个内置锁(监视器,也可以理解成锁标记),而synchronized就是使用对象的内置锁(监视器)来将代码块(方法)锁定的!
1.2synchronized用处是什么?
- synchronized保证了线程的原子性。(被保护的代码块是一次被执行的,没有任何线程会同时访问)
- synchronized还保证了可见性。(当执行完synchronized之后,修改后的变量对其他的线程是可见的)
Java中的synchronized,通过使用内置锁,来实现对变量的同步操作,进而实现了对变量操作的原子性和其他线程对变量的可见性,从而确保了并发情况下的线程安全。
1.3synchronized的原理
我们首先来看一段synchronized修饰方法和代码块的代码:
public class Main {
//修饰方法
public synchronized void test1(){
}
public void test2(){
// 修饰代码块
synchronized (this){
}
}
}
来反编译看一下:
同步代码块:
- monitorenter和monitorexit指令实现的
同步方法(在这看不出来需要看JVM底层实现)
- 方法修饰符上的ACC_SYNCHRONIZED实现。
synchronized底层是是通过monitor对象,对象有自己的对象头,存储了很多信息,其中一个信息标示是被哪个线程持有。
具体可参考:
- https://blog.csdn.net/chenssy/article/details/54883355
- https://blog.csdn.net/u012465296/article/details/53022317
1.4synchronized如何使用
synchronized一般我们用来修饰三种东西:
- 修饰普通方法
- 修饰代码块
- 修饰静态方法
1.4.1修饰普通方法:
用的锁是Java3y对象(内置锁)
public class Java3y {
// 修饰普通方法,此时用的锁是Java3y对象(内置锁)
public synchronized void test() {
// 关注公众号Java3y
// doSomething
}
}
1.4.2修饰代码块:
用的锁是Java3y对象(内置锁)--->this
public class Java3y {
public void test() {
// 修饰代码块,此时用的锁是Java3y对象(内置锁)--->this
synchronized (this){
// 关注公众号Java3y
// doSomething
}
}
}
当然了,我们使用synchronized修饰代码块时未必使用this,还可以使用其他的对象(随便一个对象都有一个内置锁)
所以,我们可以这样干:
public class Java3y {
// 使用object作为锁(任何对象都有对应的锁标记,object也不例外)
private Object object = new Object();
public void test() {
// 修饰代码块,此时用的锁是自己创建的锁Object
synchronized (object){
// 关注公众号Java3y
// doSomething
}
}
}
上面那种方式(随便使用一个对象作为锁)在书上称之为-->客户端锁,这是不建议使用的。
书上想要实现的功能是:给ArrayList添加一个putIfAbsent(),这需要是线程安全的。
假定直接添加synchronized是不可行的
使用客户端锁,会将当前的实现与原本的list耦合了:
书上给出的办法是使用组合的方式(也就是装饰器模式)
1.4.3修饰静态方法
获取到的是类锁(类的字节码文件对象):Java3y.class
public class Java3y {
// 修饰静态方法代码块,静态方法属于类方法,它属于这个类,获取到的锁是属于类的锁(类的字节码文件对象)-->Java3y.class
public synchronized void test() {
// 关注公众号Java3y
// doSomething
}
}
1.4.4类锁与对象锁
synchronized修饰静态方法获取的是类锁(类的字节码文件对象),synchronized修饰普通方法或代码块获取的是对象锁。
- 它俩是不冲突的,也就是说:获取了类锁的线程和获取了对象锁的线程是不冲突的!
public class SynchoronizedDemo {
//synchronized修饰非静态方法
public synchronized void function() throws InterruptedException {
for (int i = 0; i <3; i++) {
Thread.sleep(1000);
System.out.println("function running...");
}
}
//synchronized修饰静态方法
public static synchronized void staticFunction()
throws InterruptedException {
for (int i = 0; i < 3; i++) {
Thread.sleep(1000);
System.out.println("Static function running...");
}
}
public static void main(String[] args) {
final SynchoronizedDemo demo = new SynchoronizedDemo();
// 创建线程执行静态方法
Thread t1 = new Thread(() -> {
try {
staticFunction();
} catch (InterruptedException e) {
e.printStackTrace();
}
});
// 创建线程执行实例方法
Thread t2 = new Thread(() -> {
try {
demo.function();
} catch (InterruptedException e) {
e.printStackTrace();
}
});
// 启动
t1.start();
t2.start();
}
}
结果证明:类锁和对象锁是不会冲突的!
1.5重入锁
我们来看下面的代码:
public class Widget {
// 锁住了
public synchronized void doSomething() {
...
}
}
public class LoggingWidget extends Widget {
// 锁住了
public synchronized void doSomething() {
System.out.println(toString() + ": calling doSomething");
super.doSomething();
}
}
- 当线程A进入到LoggingWidget的
doSomething()方法时,此时拿到了LoggingWidget实例对象的锁。 - 随后在方法上又调用了父类Widget的
doSomething()方法,它又是被synchronized修饰。 - 那现在我们LoggingWidget实例对象的锁还没有释放,进入父类Widget的
doSomething()方法还需要一把锁吗?
不需要的!
因为锁的持有者是“线程”,而不是“调用”。线程A已经是有了LoggingWidget实例对象的锁了,当再需要的时候可以继续“开锁”进去的!
这就是内置锁的可重入性。
1.6释放锁的时机
- 当方法(代码块)执行完毕后会自动释放锁,不需要做任何的操作。
- 当一个线程执行的代码出现异常时,其所持有的锁会自动释放。
- 不会由于异常导致出现死锁现象~
二、Lock显式锁
2.1Lock显式锁简单介绍
Lock显式锁是JDK1.5之后才有的,之前我们都是使用Synchronized锁来使线程安全的~
Lock显式锁是一个接口,我们来看看:

随便翻译一下他的顶部注释,看看是干嘛用的:
可以简单概括一下:
- Lock方式来获取锁支持中断、超时不获取、是非阻塞的
- 提高了语义化,哪里加锁,哪里解锁都得写出来
- Lock显式锁可以给我们带来很好的灵活性,但同时我们必须手动释放锁
- 支持Condition条件对象
- 允许多个读线程同时访问共享资源
2.2synchronized锁和Lock锁使用哪个
前面说了,Lock显式锁给我们的程序带来了很多的灵活性,很多特性都是Synchronized锁没有的。那Synchronized锁有没有存在的必要??
必须是有的!!Lock锁在刚出来的时候很多性能方面都比Synchronized锁要好,但是从JDK1.6开始Synchronized锁就做了各种的优化(毕竟亲儿子,牛逼)
- 优化操作:适应自旋锁,锁消除,锁粗化,轻量级锁,偏向锁。
- 详情可参考:https://blog.csdn.net/chenssy/article/details/54883355
所以,到现在Lock锁和Synchronized锁的性能其实差别不是很大!而Synchronized锁用起来又特别简单。Lock锁还得顾忌到它的特性,要手动释放锁才行(如果忘了释放,这就是一个隐患)
所以说,我们绝大部分时候还是会使用Synchronized锁,用到了Lock锁提及的特性,带来的灵活性才会考虑使用Lock显式锁~
2.3公平锁
公平锁理解起来非常简单:
- 线程将按照它们发出请求的顺序来获取锁
非公平锁就是:
- 线程发出请求的时可以“插队”获取锁
Lock和synchronize都是默认使用非公平锁的。如果不是必要的情况下,不要使用公平锁
- 公平锁会来带一些性能的消耗的
四、最后
本文讲了synchronized内置锁和简单描述了一下Lock显式锁,总得来说:
- synchronized好用,简单,性能不差
- 没有使用到Lock显式锁的特性就不要使用Lock锁了。
Lock锁这里只是介绍了一些些,明天会详解它的相关子类和需要注意的地方,敬请期待~
之前在学习操作系统的时候根据《计算机操作系统-汤小丹》这本书也做了一点点笔记,都是比较浅显的知识点。或许对大家有帮助
参考资料:
- 《Java核心技术卷一》
- 《Java并发编程实战》
- 《计算机操作系统-汤小丹》
- https://blog.csdn.net/panweiwei1994/article/details/78483167
- http://www.cnblogs.com/dolphin0520/category/602384.html
- https://blog.csdn.net/chenssy/article/category/3145247
- https://blog.csdn.net/u012465296/article/details/53022317
- https://www.cnblogs.com/wxd0108/p/5479442.html
如果文章有错的地方欢迎指正,大家互相交流。习惯在微信看技术文章,想要获取更多的Java资源的同学,可以关注微信公众号:Java3y。为了大家方便,刚新建了一下qq群:742919422,大家也可以去交流交流。谢谢支持了!希望能多介绍给其他有需要的朋友
文章的目录导航:
Java锁机制了解一下的更多相关文章
- 转 : 深入解析Java锁机制
深入解析Java锁机制 https://mp.weixin.qq.com/s?__biz=MzU0OTE4MzYzMw%3D%3D&mid=2247485524&idx=1&s ...
- Java 锁机制总结
锁的种类 独享锁 VS 共享锁 独享锁:锁只能被一个线程持有(synchronized) 共享锁:锁可以被多个程序所持有(读写锁) 乐观锁 VS 悲观锁 乐观锁:每次去拿数据的时候都乐观地认为别人不会 ...
- java锁机制的面试题
java锁机制的面试题 1.ABA问题 2.CAS乐观锁 3.synchronize实现原理 4.synchronize与lock的区别 5.volatile实现原理 6.乐观锁的业务场景及实现方式 ...
- Java锁机制深入理解
Java锁机制 背景知识 指令流水线 CPU的基本工作是执行存储的指令序列,即程序.程序的执行过程实际上是不断地取出指令.分析指令.执行指令的过程. 几乎所有的冯•诺伊曼型计算机的CPU,其工 ...
- java锁机制
2.4 锁机制 临界区是指,使用同一个锁控制的同一段代码区或多段代码区之间,在同一时间内最多只能有一个线程在执行操作.这个概念与传统的临界区有略微的差别,这里不想强调这些概念上的差别,临 ...
- JAVA锁机制-可重入锁,可中断锁,公平锁,读写锁,自旋锁,
如果需要查看具体的synchronized和lock的实现原理,请参考:解决多线程安全问题-无非两个方法synchronized和lock 具体原理(百度) 在并发编程中,经常遇到多个线程访问同一个 ...
- JAVA锁机制(上)
在实际开发中经常会用到多线程协作来处理问题,锁是处理线程安全不可缺少的机制.在JAVA中可以通过至少三种方式来实现线程锁. 1. synchronized修饰符,这种锁机制是虚拟机实现的一种锁. 2 ...
- java 锁机制(synchronized 与 Lock)
在java中,解决同步问题,很多时候都会使用到synchronized和Lock,这两者都是在多线程并发时候常使用的锁机制. synchronized是java中的一个关键字,也就是说是java内置的 ...
- 【面试专栏】JAVA锁机制
1. 悲观锁 / 乐观锁 在Java和数据库中都存在悲观锁和乐观锁的应用.Mysql锁机制中的悲观锁和乐观锁请查看: Mysql锁机制--悲观锁和乐观锁 悲观锁:在获得数据时先加锁,只到数 ...
随机推荐
- 使用IDEA快速插入数据库数据的方法
如上图所示:数据库创建表主键使用了自增列自增因此忽略,只有后两列非主键得数据,在数据较多得时候使用IDEA快捷键Ctrl+R键,快速查找替换.
- kubernetes入门(03)kubernetes的基本概念
一.Pod 在Kubernetes集群中,Pod是创建.部署和调度的基本单位.一个Pod代表着集群中运行的一个进程,它内部封装了一个或多个应用的容器.在同一个Pod内部,多个容器共享存储.网络IP,以 ...
- Spring Security 入门(3-11)Spring Security 的使用-自定义登录验证和回调地址
配置文件 security-ns.xml <?xml version="1.0" encoding="UTF-8"?> <beans xmln ...
- EasyUI DataGrid - 嵌套的DataGrid
实现效果: 一.在页面头部引用视图脚本JS文件 <script src="@Url.Content("~/Resources/EasyUI/plugins/datagrid- ...
- 官网jquery压缩版引用地址:
3.1.1版本 <!DOCTYPE html> <html lang="en"> <head> <meta charset="U ...
- Maven的作用是什么
现在我们开发的项目基本上都是maven项目,maven项目也是一个项目,类似于javaProject,javaWebProject,就是多了些功能. 那就说说究竟多了什么功能呢. 1 . 帮你下载ja ...
- 小工具:批量导入导出主机上的docker镜像
工作需要,将主机上的部分镜像导出为tar文件,放便在其他主机上使用 用python实现了一个批量打包脚本: import re import os import subprocess if __nam ...
- 使用Docker安装Jenkins
Jenkins Jenkins是一个开源软件项目,是基于Java开发的一种持续集成工具,用于监控持续重复的工作,旨在提供一个开放易用的软件平台,使软件的持续集成变成可能. 环境准备 腾讯云 硬件配置: ...
- 用AJAX实现上传图片或者文件的方法
大家好,我是小C,最近在项目中用到ajax上传图片文件,本篇我们就说说ajax上传文件. 我们平时用到的AJAX,大部分都是传几个参数就可以了.简单说就是传几个字符串. $.ajax({ url: u ...
- shell编程-项目部署(二)
上节我们讲了项目部署的准备工作,现在具体讲下代码部署 首先梳理下思路,大致是这样: 获取代码 打包代码 传输代码 关闭应用 解压文件 放置文件(备份老文件,放置新的文件) 开启应用 最后检查下 OK, ...