1、Synchronized
1.1 作用
能够保证在同一时刻最多只有一个线程执行该段代码,以达到保证并发安全的效果。
1.2 地位
Synchronized是Java的关键字,被Java语言原生支持
是最基本的互斥同步手段
是并发编程中的元老级角色,是并发编程的必学内容
1.3 不用并发手段会有什么后果?
代码实战:两个线程同时a++,最后结果会比预计的少
public class ShowUnsafe1 implements Runnable{
static ShowUnsafe1 showUnsafe = new ShowUnsafe1();
static int j;
@Override
public void run () {
for (int i = 0; i < 10000; i++) {
j++;
}
}
public static void main (String[] args) throws InterruptedException {
Thread thread1 = new Thread(showUnsafe);
Thread thread2 = new Thread(showUnsafe);
thread1.start();
thread2.start();
thread1.join();
thread2.join();
System.out.println(j);
}
}
输出
15930 or 13690等等数值在不断变化
原因
i++,虽然是一行代码,但是实际上至少包含了以下这三个动作:
- 读取 i 的值
- 计算 i+1
- 把 i+1 的计算结果写回到内存中,赋给 i
因为上面三个步骤不具备原子性,所以出现线程不安全现象。
1.4 Synchronized的两个用法(本次重点明白每个锁的锁对象是谁)
对象锁
包括方法锁(默认锁对象为this当前实例对象)和同步代码块锁(自己指定锁对象)
类锁
指Synchronized修饰静态的方法或指定锁为Class对象
1.5 对象锁
代码块形式:手动指定锁对象
方法锁形式:Synchronized修饰普通方法,锁对象默认为this
1.5.1 代码展示:对象锁—代码块形式
/**
* 对象锁的形式1:代码块形式
*/
public class SynchronizedObjectCodeBlock2 implements Runnable{
private static SynchronizedObjectCodeBlock2 instant = new SynchronizedObjectCodeBlock2();
@Override
public void run () {
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(instant);
Thread t2 = new Thread(instant);
t1.start();
t2.start();
while( t1.isAlive() || t2.isAlive() ){
}
System.out.println("finished");
}
}
输出
我是对象锁的代码块形式。我叫Thread-0
我是对象锁的代码块形式。我叫Thread-1
Thread-1运行结束
Thread-0运行结束
finished
此时线程1和2几乎是同时参加run方法
这时我们加入Synchronized
/**
* 对象锁的形式1:代码块形式
*/
public class SynchronizedObjectCodeBlock2 implements Runnable{
private static SynchronizedObjectCodeBlock2 instant = new SynchronizedObjectCodeBlock2();
@Override
public void run () {
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(instant);
Thread t2 = new Thread(instant);
t1.start();
t2.start();
while( t1.isAlive() || t2.isAlive() ){
}
System.out.println("finished");
}
}
输出
我是对象锁的代码块形式。我叫Thread-0
Thread-0运行结束
我是对象锁的代码块形式。我叫Thread-1
Thread-1运行结束
finished
看到加入synchronized之后,我们的synchronized代码块之中的代码已经必须要求只有一个线程能够访问
这是我们新建一个锁对象Object lock1 = new Object();
我们将synchronized的锁对象指定为lock1。synchronized(lock1)
此时,我们使用两个synchronized代码块,来看看最后运行结果
/**
* 对象锁的形式1:代码块形式
*/
public class SynchronizedObjectCodeBlock2 implements Runnable{
private static SynchronizedObjectCodeBlock2 instant = new SynchronizedObjectCodeBlock2();
Object lock1 = new Object();
Object lock2 = new Object();
@Override
public void run () {
synchronized(lock1) {
System.out.println("我是对象锁lock1的代码块形式。我叫" + Thread.currentThread().getName());
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "运行结束");
}
synchronized(lock2) {
System.out.println("我是对象锁lock2的代码块形式。我叫" + 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(instant);
Thread t2 = new Thread(instant);
t1.start();
t2.start();
while( t1.isAlive() || t2.isAlive() ){
}
System.out.println("finished");
}
}
输出
我是对象锁lock1的代码块形式。我叫Thread-0
Thread-0运行结束
我是对象锁lock2的代码块形式。我叫Thread-0
我是对象锁lock1的代码块形式。我叫Thread-1
Thread-0运行结束
Thread-1运行结束
我是对象锁lock2的代码块形式。我叫Thread-1
Thread-1运行结束
finished
1.5.2 代码展示:对象锁—方法锁形式
//锁对象为this当前实例对象
public class SynchronizedObjectMethod3 implements Runnable{
private static SynchronizedObjectMethod3 instant = new SynchronizedObjectMethod3();
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() + "运行结束");
}
@Override
public void run () {
method();
}
public static void main (String[] args) {
Thread t1 = new Thread(instant);
Thread t2 = new Thread(instant);
t1.start();
t2.start();
while( t1.isAlive() || t2.isAlive() ){
}
System.out.println("finished");
}
}
输出
我是对象锁的方法锁形式。我叫Thread-0
Thread-0运行结束
我是对象锁的方法锁形式。我叫Thread-1
Thread-1运行结束
finished
1.6 类锁
概念:
- 只有一个Class对象:Java类可能有很多对象,但是只有一个Class对象
- 本质:所以所谓类锁,不过是锁对象是 Class对象
- 用法和效果:同一时刻最多被一个线程拥有
形式1:synchronized加在static方法上
形式2:synchronized(*.class)代码块
1.6.1 代码展示:类锁—静态方法锁形式
现在我们发现一个有趣的现象
public class SynchronizedClassStatic4 implements Runnable{
private static SynchronizedClassStatic4 instant1 = new SynchronizedClassStatic4();
private static SynchronizedClassStatic4 instant2 = new SynchronizedClassStatic4();
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() + "运行结束");
}
@Override
public void run () {
method();
}
public static void main (String[] args) {
Thread t1 = new Thread(instant1);
Thread t2 = new Thread(instant2);
t1.start();
t2.start();
while( t1.isAlive() || t2.isAlive() ){
}
System.out.println("finished");
}
}
输出
我是类锁的静态方法锁形式。我叫Thread-1
我是类锁的静态方法锁形式。我叫Thread-0
Thread-1运行结束
Thread-0运行结束
finished
看到了没,我们的synchronized好像失效了。照成这种现象的原因是两点:
- Synchronized修饰普通方法,锁对象默认为this
- 线程1和2之间的是不同对象
所以,对于线程1,synchronized锁的是instant1对象。对于线程2,synchronized锁的是instant2对象。
他们之间锁都不同,当然不会发生冲突。
这时候,我们在方法上加上static
public class SynchronizedClassStatic4 implements Runnable{
private static SynchronizedClassStatic4 instant1 = new SynchronizedClassStatic4();
private static SynchronizedClassStatic4 instant2 = new SynchronizedClassStatic4();
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() + "运行结束");
}
@Override
public void run () {
method();
}
public static void main (String[] args) {
Thread t1 = new Thread(instant1);
Thread t2 = new Thread(instant2);
t1.start();
t2.start();
while( t1.isAlive() || t2.isAlive() ){
}
System.out.println("finished");
}
}
输出
我是类锁的静态方法锁形式。我叫Thread-0
Thread-0运行结束
我是类锁的静态方法锁形式。我叫Thread-1
Thread-1运行结束
finished
我们只是将方法变成了静态方法public static synchronized void method()
。这个时候不会出现上面代码的问题了。为什么?
我们将方法变成了静态的,此时锁从对象锁变成了类锁。即,锁对象是SynchronizedClassStatic4.class对象。所以,此时变成了线程1和2共同竞争一把锁
1.6.2 代码展示:类锁—代码块锁形式
public class SynchronizedClassClass5 implements Runnable{
private static SynchronizedClassClass5 instant1 = new SynchronizedClassClass5();
private static SynchronizedClassClass5 instant2 = new SynchronizedClassClass5();
public void method(){
synchronized (SynchronizedClassClass5.class) {
System.out.println("我是类锁的静态方法锁形式。我叫" + Thread.currentThread().getName());
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "运行结束");
}
}
@Override
public void run () {
method();
}
public static void main (String[] args) {
Thread t1 = new Thread(instant1);
Thread t2 = new Thread(instant2);
t1.start();
t2.start();
while( t1.isAlive() || t2.isAlive() ){
}
System.out.println("finished");
}
}
输出
我是类锁的静态方法锁形式。我叫Thread-1
Thread-1运行结束
我是类锁的静态方法锁形式。我叫Thread-0
Thread-0运行结束
finished
我们此时,使用synchronized修饰代码块,并且传入锁对象SynchronizedClassClass5.class。此时这个所是类锁
1.7 面试题
多线程访问同步方法的7种情况(面试常考)
1 两个线程同时访问一个对象的同步方法
public class SynchronizedObjectMethod3 implements Runnable{
private static SynchronizedObjectMethod3 instant = new SynchronizedObjectMethod3();
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() + "运行结束");
}
@Override
public void run () {
method();
}
public static void main (String[] args) {
Thread t1 = new Thread(instant);
Thread t2 = new Thread(instant);
t1.start();
t2.start();
while( t1.isAlive() || t2.isAlive() ){
}
System.out.println("finished");
}
}
2 两个线程访问的是两个对象的同步方法
public class SynchronizedClassStatic4 implements Runnable{
private static SynchronizedClassStatic4 instant1 =
new SynchronizedClassStatic4();
private static SynchronizedClassStatic4 instant2 =
new SynchronizedClassStatic4();
public void method(){
synchronized(this){
System.out.println("我是类锁的静态方法锁形式。我叫"
+ Thread.currentThread().getName());
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "运行结束");
}
}
@Override
public void run () {
method();
}
public static void main (String[] args) {
Thread t1 = new Thread(instant1);
Thread t2 = new Thread(instant2);
t1.start();
t2.start();
while( t1.isAlive() || t2.isAlive() ){
}
System.out.println("finished");
}
}
3 两个线程访问的是synchronized的静态方法
public class SynchronizedClassStatic4 implements Runnable{
private static SynchronizedClassStatic4 instant = new SynchronizedClassStatic4();
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() + "运行结束");
}
@Override
public void run () {
method();
}
public static void main (String[] args) {
Thread t1 = new Thread(instant);
Thread t2 = new Thread(instant);
t1.start();
t2.start();
while( t1.isAlive() || t2.isAlive() ){
}
System.out.println("finished");
}
}
4 同时访问同步方法与非同步方法
public class SynchronizedYesAndNo implements Runnable{
private static SynchronizedYesAndNo instant = new SynchronizedYesAndNo();
public synchronized void method1(){
System.out.println("我是加锁的方法。我叫" + Thread.currentThread().getName());
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "运行结束");
}
public void method2(){
System.out.println("我是没加锁的方法。我叫" + Thread.currentThread().getName());
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "运行结束");
}
@Override
public void run () {
if(Thread.currentThread().getName().equals("Thread-0")){
method1();
}else{
method2();
}
}
public static void main (String[] args) {
Thread t1 = new Thread(instant);
Thread t2 = new Thread(instant);
t1.start();
t2.start();
while( t1.isAlive() || t2.isAlive() ){
}
System.out.println("finished");
}
}
输出
我是加锁的方法。我叫Thread-0
我是没加锁的方法。我叫Thread-1
Thread-0运行结束
Thread-1运行结束
finished
5 访问同一个对象的不同的普通同步方法
/**
* 访问同一个类的不同的普通同步方法
*/
public class SynchronizedDifferentMethod7 implements Runnable{
private static SynchronizedDifferentMethod7 instant = new SynchronizedDifferentMethod7();
public synchronized void method1(){
System.out.println("我是加锁的方法1。我叫" + Thread.currentThread().getName());
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "运行结束");
}
public synchronized void method2(){
System.out.println("我是加锁的方法2。我叫" + Thread.currentThread().getName());
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "运行结束");
}
@Override
public void run () {
if(Thread.currentThread().getName().equals("Thread-0")){
method1();
}else{
method2();
}
}
public static void main (String[] args) {
Thread t1 = new Thread(instant);
Thread t2 = new Thread(instant);
t1.start();
t2.start();
while( t1.isAlive() || t2.isAlive() ){
}
System.out.println("finished");
}
}
6 同时访问静态synchronized和非静态的synchronized方法
/**
* 同时访问静态synchronized和非静态的synchronized方法
*/
public class SynchronizedStaticAndNormal8 implements Runnable{
private static SynchronizedDifferentMethod7 instant = new SynchronizedDifferentMethod7();
public synchronized static void method1(){
System.out.println("我是加锁的静态方法1。我叫" + Thread.currentThread().getName());
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "运行结束");
}
public synchronized void method2(){
System.out.println("我是加锁的方法2。我叫" + Thread.currentThread().getName());
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "运行结束");
}
@Override
public void run () {
if(Thread.currentThread().getName().equals("Thread-0")){
method1();
}else{
method2();
}
}
public static void main (String[] args) {
Thread t1 = new Thread(instant);
Thread t2 = new Thread(instant);
t1.start();
t2.start();
while( t1.isAlive() || t2.isAlive() ){
}
System.out.println("finished");
}
}
7 方法抛出异常后,会释放锁
/**
* 方法抛出异常后,会释放锁
*/
public class SynchronizedException9 implements Runnable{
private static SynchronizedException9 instant = new SynchronizedException9();
public synchronized void method1(){
System.out.println("我是加锁的方法1。我叫" + Thread.currentThread().getName());
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
throw new RuntimeException();
}
public synchronized void method2(){
System.out.println("我是加锁的方法2。我叫" + Thread.currentThread().getName());
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "运行结束");
}
@Override
public void run () {
if(Thread.currentThread().getName().equals("Thread-0")){
method1();
}else{
method2();
}
}
public static void main (String[] args) {
Thread t1 = new Thread(instant);
Thread t2 = new Thread(instant);
t1.start();
t2.start();
while( t1.isAlive() || t2.isAlive() ){
}
System.out.println("finished");
}
}
1.8 Synchronized性质
1.8.1 可重入
指的是某个线程试图获得一个已经由他持有的锁,那么它会请求成功。
好处:避免死锁、提升封装性,简化面向对象代码的开发
//可重入现象的代码
public class SynchronizedOtherMethod11 {
public synchronized void method1(){
System.out.println("这是方法1");
method2();
}
public synchronized void method2 () {
System.out.println("这是方法2");
}
public static void main (String[] args) {
SynchronizedOtherMethod11 s = new SynchronizedOtherMethod11();
s.method1();
}
}
//对象s获得的是一把锁:this,我们可以使用这把锁访问方法2(因为方法2的锁和这把锁是同一把)
重入的一种实现方式是,为每个锁关联一个获取计数器和一个所有者线程。当计数器值为0时,这个锁就认为是没有被任何线程持有。当线程请求一个未被持有的锁时,JVM会记下锁的持有者,并且将计数器值变为1。如果同一个线程再次获取到了这个锁,计数器值将加一,而当线程退出同步代码块时,计数器值递减。当计数器为0时,这个锁将被释放。
1.8.2 不可中断
一旦这个锁被别人拿走了,如果我还想获得,我只能选择等待或者阻塞,直到别的线程释放这个锁。如果别人永远不释放锁,我们就要永远等下去
1.8.3 可见性
- 一个线程执行的结果,另外的线程不一定可见
- 线程1操作x=5,之后线程2可能读取x=3
- synchronized可以保证可见性:线程B执行由锁保护的同步代码块时,可以看到线程A之前在同一个同步代码块中的所有操作结果。
在代码中说明
int x = 3;
public void method1(){
x++;
}
public void method2(){
System.out.println(x)
}
此时,不一定满足可见性。所以method2读到的x的值有可能是3
但是我们将两个方法全部加上synchronized关键词
int x = 3;
public synchronized void method1(){
x++;
}
public synchronized void method2(){
System.out.println(x)
}
此时,method2读到的x的值一定是4
1.9 原理
1.9.1 加锁和释放锁的原理
synchronized会为我们自动加锁和释放锁
public class SynchronizedToLock13 {
Lock lock = new ReentrantLock();
//自动为我们加锁解锁
private synchronized void method1 () {
System.out.println("我是synchronized形式");
}
//我们自己来加锁和解锁
private void method2(){
lock.lock();//上锁
try {
System.out.println("我是Lock形式");
}finally {
lock.unlock();//解锁
}
}
public static void main (String[] args) {
SynchronizedToLock13 s = new SynchronizedToLock13();
s.method1()
}
}
method2其实就是和method1等价的
我们来看看synchronized的字节码
/**
* 看字节码
*/
public class Decompilation14 {
private Object object = new Object();
public void insert(Thread thread){
synchronized (object){
}
}
}
老师使用反编译操作,看到了上面代码的字节码文件
Code:
stack = 2,local = 4,args_size = 2
0:aload_0
1:getfield
4:dup
5:astore_2
6:monitorenter//加锁
7:aload_2
8:monitorexit//释放锁
9:goto
10:astore_3
11:aload_2
12:monitorexit//释放锁
13:aload_3
14:athrow
15:return
synchronized的原理说明(💟):当我们使用代码synchronized (object){}
在底层就会执行monitor相关指令
1.9.2 可重入原理:加锁次数计数器
重入的一种实现方式是,为每个锁关联一个获取计数器和一个所有者线程。当计数器值为0时,这个锁就认为是没有被任何线程持有。当线程请求一个未被持有的锁时,JVM会记下锁的持有者,并且将计数器值变为1。如果同一个线程再次获取到了这个锁,计数器值将加一,而当线程退出同步代码块时,计数器值递减。当计数器为0时,这个锁将被释放。
1.10 synchronized的缺陷
- 效率低:锁的释放情况少、试图获得锁时不能设定超时、不能中断一个正在试图获得锁的线程
- 不够灵活(读写锁更灵活):加锁和释放的时机单一,每个锁仅有单一的条件(某个对象),可能是不够的
- 无法知道是否成功获取锁
1.11 面试题(这里以后回来完善一下)
-
使用注意点:锁的范围不宜过大、避免锁的嵌套
-
如何选择Lock和synchronized关键字?
Lock确实比较灵活,我们可以自主选择加锁和释放锁的时机。优先使用synchronized。只有当不能使用synchronized并必须使用Lock的时候才去使用Lock
-
多线程访问同步方法的各种具体情况(前面讲到)
-
synchronized使得同时只有一个线程可以执行,性能较差,有什么可以提升性能?
如果是读多写少的情况下,我们可以使用读写锁
-
我想更灵活的控制锁的获取和释放(现在释放锁的时机都被规定死了),怎么办?
我们可以使用Lock
本篇文章参考:
《Java并发编程实战》
慕课网悟空老师的sychronized课 https://www.imooc.com/learn/1086
谢谢大家看到这里,如有不对的地方请在评论区指正