Synchronized 详解
为了方便记忆,将锁做如下的分类
一、对象锁
包括方法锁(默认锁对象为this,当前实例对象)和同步代码块锁(自己指定锁对象)
1.代码块形式:手动指定锁定对象,也可是是this,也可以是自定义的锁
public class SynchronizedObjectLock implements Runnable {
static SynchronizedObjectLock instence = new SynchronizedObjectLock(); @Override
public void run() {
// 同步代码块形式——锁为this,两个线程使用的锁是一样的,线程1必须要等到线程0释放了该锁后,才能执行
synchronized (this) {
System.out.println("我是线程" + Thread.currentThread().getName());
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "结束");
}
} public static void main(String[] args) {
Thread t1 = new Thread(instence);
Thread t2 = new Thread(instence);
t1.start();
t2.start();
}
} 输出结果:
我是线程Thread-0
Thread-0结束
我是线程Thread-1
Thread-1结束
public class SynchronizedObjectLock implements Runnable {
static SynchronizedObjectLock instence = new SynchronizedObjectLock();
// 创建2把锁
Object block1 = new Object();
Object block2 = new Object(); @Override
public void run() {
// 这个代码块使用的是第一把锁,当他释放后,后面的代码块由于使用的是第二把锁,因此可以马上执行
synchronized (block1) {
System.out.println("blocl1锁,我是线程" + Thread.currentThread().getName());
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("blocl1锁,"+Thread.currentThread().getName() + "结束");
} synchronized (block2) {
System.out.println("blocl2锁,我是线程" + Thread.currentThread().getName());
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("blocl2锁,"+Thread.currentThread().getName() + "结束");
}
} public static void main(String[] args) {
Thread t1 = new Thread(instence);
Thread t2 = new Thread(instence);
t1.start();
t2.start();
} 输出结果:
blocl1锁,我是线程Thread-0
blocl1锁,Thread-0结束
blocl2锁,我是线程Thread-0 // 可以看到当第一个线程在执行完第一段同步代码块之后,第二个同步代码块可以马上得到执行,因为他们使用的锁不是同一把
blocl1锁,我是线程Thread-1
blocl2锁,Thread-0结束
blocl1锁,Thread-1结束
blocl2锁,我是线程Thread-1
blocl2锁,Thread-1结束
2.方法锁形式:synchronized修饰普通方法,锁对象默认为this
public class SynchronizedObjectLock implements Runnable {
static SynchronizedObjectLock instence = new SynchronizedObjectLock(); @Override
public void run() {
method();
} public synchronized void method() {
System.out.println("我是线程" + Thread.currentThread().getName());
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "结束");
} public static void main(String[] args) {
Thread t1 = new Thread(instence);
Thread t2 = new Thread(instence);
t1.start();
t2.start();
}
} 输出结果:
我是线程Thread-0
Thread-0结束
我是线程Thread-1
Thread-1结束
二、类锁
指synchronize修饰静态的方法或指定锁对象为Class对象
1.synchronize修饰静态方法
public class SynchronizedObjectLock implements Runnable {
static SynchronizedObjectLock instence1 = new SynchronizedObjectLock();
static SynchronizedObjectLock instence2 = new SynchronizedObjectLock(); @Override
public void run() {
method();
} // synchronized用在普通方法上,默认的锁就是this,当前实例
public synchronized void method() {
System.out.println("我是线程" + Thread.currentThread().getName());
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "结束");
} public static void main(String[] args) {
// t1和t2对应的this是两个不同的实例,所以代码不会串行
Thread t1 = new Thread(instence1);
Thread t2 = new Thread(instence2);
t1.start();
t2.start();
}
} 输出结果:
我是线程Thread-0
我是线程Thread-1
Thread-1结束
Thread-0结束
public class SynchronizedObjectLock implements Runnable {
static SynchronizedObjectLock instence1 = new SynchronizedObjectLock();
static SynchronizedObjectLock instence2 = new SynchronizedObjectLock(); @Override
public void run() {
method();
} // synchronized用在静态方法上,默认的锁就是当前所在的Class类,所以无论是哪个线程访问它,需要的锁都只有一把
public static synchronized void method() {
System.out.println("我是线程" + Thread.currentThread().getName());
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "结束");
} public static void main(String[] args) {
Thread t1 = new Thread(instence1);
Thread t2 = new Thread(instence2);
t1.start();
t2.start();
}
} 输出结果:
我是线程Thread-0
Thread-0结束
我是线程Thread-1
Thread-1结束
2.synchronized指定锁对象为Class对象
public class SynchronizedObjectLock implements Runnable {
static SynchronizedObjectLock instence1 = new SynchronizedObjectLock();
static SynchronizedObjectLock instence2 = new SynchronizedObjectLock(); @Override
public void run() {
// 所有线程需要的锁都是同一把
synchronized(SynchronizedObjectLock.class){
System.out.println("我是线程" + Thread.currentThread().getName());
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "结束");
}
} public static void main(String[] args) {
Thread t1 = new Thread(instence1);
Thread t2 = new Thread(instence2);
t1.start();
t2.start();
}
} 输出结果:
我是线程Thread-0
Thread-0结束
我是线程Thread-1
Thread-1结束
三、思考
1.两个线程同时访问1个对象的同步方法
2.两个线程同时访问2个对象的同步方法
3.两个线程访问的是synchronized静态方法
4.两个线程同时访问同步(被synchronized修饰)和非同步(未被snychronized修饰)方法
5.两个线程同时访问1个对象的不同的普通同步方法
6.两个线程同时访问一个静态的synchronized方法和非静态的synchronized方法
7.方法抛出异常后,会释放锁吗?
核心思想:
1.一把锁只能同时被一个线程获取,没有难道锁的线程只能等待(对应上面的1,5)
2.每个实例都对应有自己的一把锁(this),不同实例之间互不影响;例外:锁对象是*.class以及synchronized修饰的是static方法的时候,所有对象公用同一把锁(对应上面的2,3,4,6)
3.synchronized修饰的方法,无论方法正常执行完毕还是抛出异常,都会释放锁(对应上面的7)
四、synchronized的性质
1.可重入性
概念:指同一个线程外层函数获取到锁之后,内层函数可以直接使用该锁
好处:避免死锁,提升封装性(如果不可重入,假设method1拿到锁之后,在method1中又调用了method2,如果method2没办法使用method1拿到的锁,那method2将一直等待,但是method1由于未执行完毕,又无法释放锁,就导致了死锁,可重入正好避免这这种情况)
粒度:线程而非调用(用3中情况来说明与pthread的区别)
1)情况1:证明同一个方法是可重入的(递归)
public class SynchronizedDemo2 {
int a = 0; public static void main(String[] args) {
new SynchronizedDemo2().method1();
} public synchronized void method1() {
System.out.println("a=" + a);
if (a == 0) {
a++;
method1();
}
}
} 输出结果:
a=0
a=1
2)情况2:证明可重入不要求是同一个方法
public class SynchronizedDemo2 {
public static void main(String[] args) {
new SynchronizedDemo2().method1();
} public synchronized void method1() {
System.out.println("method1");
method2();
} public synchronized void method2() {
System.out.println("method2");
}
} 输出结果:
method1
method2
3)情况3:证明可重入不要求是同一个类中
public class SynchronizedDemo2 {
public synchronized void method1() {
System.out.println("父类method1");
}
} class SubClass extends SynchronizedDemo2 {
public synchronized void method1() {
System.out.println("子类method1");
super.method1();
} public static void main(String[] args) {
new SubClass().method1();
}
} 输出结果:
子类method1
父类method1
2.不可中断性
概念:如果这个锁被B线程获取,如果A线程想要获取这把锁,只能选择等待或者阻塞,直到B线程释放这把锁,如果B线程一直不释放这把锁,那么A线程将一直等待。
相比之下,未来的Lock类,可以拥有中断的能力(如果一个线程等待锁的时间太长了,有权利中断当前已经获取锁的线程的执行,也可以退出等待)
五、深入原理
1.加锁和释放锁的原理:现象、时机(内置锁this)、深入JVM看字节码(反编译看monitor指令)
Lock lock = new ReentrantLock(); public synchronized void method1() {
System.out.println("synchronized method1");
} public void method2() {
lock.lock();
try {
System.out.println("lock method2");
} finally {
lock.unlock();
}
}
method1与method2等价,synchronized相当于先获取锁,执行结束/抛出异常后,释放锁。
深入JVM看字节码,创建如下的代码:
public class SynchronizedDemo2 {
Object object = new Object(); public void method1() {
synchronized (object) { }
}
}
使用javac命令进行编译生成.class文件 >javac SynchronizedDemo2.java 使用javap命令反编译查看.class文件的信息 >javap -verbose SynchronizedDemo2.class
得到如下的信息:
关注红色方框里的monitorenter和monitorexit即可。
Monitorenter和Monitorexit指令,会让对象在执行,使其锁计数器加1或者减1。每一个对象在同一时间只与一个monitor(锁)相关联,而一个monitor在同一时间只能被一个线程获得,一个对象在尝试获得与这个对象相关联的Monitor锁的所有权的时候,monitorenter指令会发生如下3中情况之一:
1)monitor计数器为0,意味着目前还没有被获得,那这个线程就会立刻获得然后把锁计数器+1,一旦+1,别的线程再想获取,就需要等待
2)如果这个monitor已经拿到了这个锁的所有权,又重入了这把锁,那锁计数器就会累加,变成2,并且随着重入的次数,会一直累加
3)这把锁已经被别的线程获取了,等待锁释放
monitorexit指令:释放对于monitor的所有权,释放过程很简单,就是讲monitor的计数器减1,如果减完以后,计数器不是0,则代表刚才是重入进来的,当前线程还继续持有这把锁的所有权,如果计数器变成0,则代表当前线程不再拥有该monitor的所有权,即释放锁。
2.可重入原理:加锁次数计数器
jvm会负责跟踪对象被加锁的次数
线程第一次获得所,计数器+1,当锁重入的时候,计数器会递增
当任务离开的时候(一个同步代码块的代码执行结束),计数器会减1,当减为0的时候,锁被完全释放。
3.保证可见性的原理:内存模型
访问链接 https://www.cnblogs.com/xyabk/p/10894384.html
六、synchronized的缺陷
效率低:锁的释放情况少,只有代码执行完毕或者异常结束才会释放锁;试图获取锁的时候不能设定超时,不能中断一个正在使用锁的线程,相对而言,Lock可以中断和设置超时
不够灵活:加锁和释放的时机单一,每个锁仅有一个单一的条件(某个对象),相对而言,读写锁更加灵活
无法知道是否成功获得锁,相对而言,Lock可以拿到状态,如果成功获取锁,....,如果获取失败,.....
七、Lock对synchronized的弥补
Lock类这里不做过多解释,主要看上面红色方框里面的4个方法
lock():加锁
unlock():解锁
tryLock():尝试获取锁,返回一个boolean值
tryLock(long,TimeUtil):尝试获取锁,可以设置超时
八、注意
1.锁对象不能为空,因为锁的信息都保存在对象头里
2.作用域不宜过大,影响程序执行的速度,控制范围过大,编写代码也容易出错
3.避免死锁
4.在能选择的情况下,既不要用Lock也不要用synchronized关键字,用java.util.concurrent包中的各种各样的类,如果不用该包下的类,在满足业务的情况下,可以使用synchronized关键,因为代码量少,避免出错
九、思考
1.多个线程等待同一个snchronized锁的时候,JVM如何选择下一个获取锁的线程?
2.Synchronized使得同时只有一个线程可以执行,性能比较差,有什么提升的方法?
3.我想更加灵活地控制锁的释放和获取(现在释放锁和获取锁的时机都被规定死了),怎么办?
4.什么是锁的升级和降级?什么事JVM里的偏斜锁、轻量级锁、重量级锁?
Synchronized 详解的更多相关文章
- 黑马------synchronized详解
黑马程序员:Java培训.Android培训.iOS培训..Net培训 JAVA线程-synchronized详解 一.synchronized概述 1.线程间实现互斥,必须使用同一个监视器(一个对象 ...
- Java synchronized 详解
Java synchronized 详解 Java语言的关键字,当它用来修饰一个方法或者一个代码块的时候,能够保证在同一时刻最多只有一个线程执行该段代码. 1.当两个并发线程访问同一个对象object ...
- 剑指Offer——线程同步volatile与synchronized详解
(转)Java面试--线程同步volatile与synchronized详解 0. 前言 面试时很可能遇到这样一个问题:使用volatile修饰int型变量i,多个线程同时进行i++操作,这样可以实现 ...
- synchronized详解
关于synchronized,本文从使用方法,底层原理和锁的升级优化这几个方面来介绍. 1.synchronized的使用: synchronized可以保证在同一时刻,只有一个线程可以操作共享变量, ...
- JAVA 中 synchronized 详解
看到一篇关于JAVA中synchronized的用法的详解,觉得不错遂转载之..... 原文地址: http://www.cnblogs.com/GnagWang/archive/2011/02/27 ...
- java并发编程(七)synchronized详解
Java语言的关键字,当它用来修饰一个方法或者一个代码块的时候,能够保证在同一时刻最多只有一个线程执行该段代码. 一.当两个并发线程访问同一个对象object中的这个synchronized( ...
- java synchronized详解
Java语言的关键字,当它用来修饰一个方法或者一个代码块的时候,能够保证在同一时刻最多只有一个线程执行该段代码. 一.当两个并发线程访问同一个对象object中的这个synchronized(this ...
- Java中synchronized详解
synchronized 原则: 尽量避免无谓的同步控制,同步需要系统开销,可能造成死锁 尽量减少锁的粒度 同步方法 public synchronized void printVal(int v) ...
- [zt]java synchronized详解
作者:GangWang 出处:http://www.cnblogs.com/GnagWang/ 记下来,很重要. Java语言的关键字,当它用来修饰一个方法或者一个代码块的时候,能够保证在同一时刻最多 ...
- JAVA多线程synchronized详解
Java语言的关键字,当它用来修饰一个方法或者一个代码块的时候,能够保证在同一时刻最多只有一个线程执行该段代码. 当两个并发线程访问同一个对象object中的这个synchronized(this)同 ...
随机推荐
- 20191126PHP连接数据(1)
引进数据 mysql> create database stu1 character set utf8; mysql> use stu1 mysql> set names utf8; ...
- JVM Heap Memory和Native Memory
JVM管理的内存可以总体划分为两部分:Heap Memory和Native Memory.前者我们比较熟悉,是供Java应用程序使用的:后者也称为C-Heap,是供JVM自身进程使用的.Heap Me ...
- Linux性能优化从入门到实战:17 网络篇:网络基础
网络模型 为了解决网络互联中异构设备的兼容性问题,并解耦复杂的网络包处理流程,国际标准化组织制定了开放式系统互联通信参考模型(Open System Interconnection Reference ...
- Linux性能优化从入门到实战:11 内存篇:内存泄漏的发现与定位
用户空间内存包括多个不同的内存段,比如只读段.数据段.堆.栈以及文件映射段等.但会发生内存泄漏的内存段,只有堆和文件映射段中的共享内存. 内存泄漏的危害非常大,这些忘记释放的内存,不仅应用程序 ...
- Linux架构之NFS共享存储1
第35章 NFS共享存储 35.1 NFS基本概述 NFS是Network File System的缩写及网络文件系统.NFS主要功能是通过局域网络让不同的主机系统之间可以共享文件或目录. 常见的文件 ...
- thinkphp数据库连接
https://www.kancloud.cn/manual/thinkphp5/118059 一.配置文件定义 常用的配置方式是在应用目录或者模块目录下面的database.php中添加下面的配置参 ...
- 阿里云吴天议:云原生SDWAN 应用 构建智能化云原生SDWAN生态
2019年11月16日 SDWAN 大会在北京正式召开.阿里云网络资深产品专家吴天议先生继阿里云网络研究员祝顺民先生发表了对云原生SDWAN的进化与展望之后(原文请见https://bit.ly/2K ...
- 测量TSQL语句的性能
你怎样确定你的应用程序执行的怎么样?你可以使用什么工具?每一个开发人员需要确定每一条应用程序TSQL语句是最优的.通过调整每一个查询你可以确定你的应用程序运行得尽可能的高效.你使用开发环境时更容易对应 ...
- gawk进阶
一.使用变量 gawk支持两种不同类型的变量: 内建变量 自定义变量 1.1 内建变量 ①字段和记录分隔符变量 FIELDWIDTHS:有空格分割的一列数字,定义了每个数据字段确切宽度 FS:输入字段 ...
- PHP Timer 页面运行时间监测类
转至:http://blog.csdn.net/fdipzone/article/details/20160567 php Timer 页面运行时间监测类,可按不同key监测不同的运行时间 Timer ...