java锁
java的锁机制并不复杂,谈到锁一般会提到关键字Sychronized和类Lock以及cas策略,提到锁的分类会提到可重入锁、可中断锁、公平锁/非公平锁、乐观锁/悲观锁等等,其实了解了原理之后,一切都会豁然开朗。我们一步一步来。
线程安全
在了解锁机制之前,我们得先清除为什么会有锁,原因是保证线程安全。在编程中,当多个线程都要对某一变量进行操作时,我们必须保证这些线程在自己操作后所得的结果是正确的,这就是线程安全。而在java中,根据线程安全强度由强至弱排序,将操作共享数据氛围以下五类:
不可变:数据不可变,线程安全也就无从谈起
绝对线程安全:几乎无法实现
相对线程安全:我们通常说的线程安全
线程兼容:值对象本身不是线程安全的,但是我们可以通过调用手段达到安全。
线程对立:无论何种情况,无论调用端使用何种方式,都无法使用并发代码,因为java天生支持多线程,所以这个思想无用
所以我们在java中说的并发同步指的就是相对线程安全和线程兼容这两个概念。
java中线程安全的实现方式
互斥同步:就是我们常说的锁机制
非阻塞同步:CAS
无同步方案
Sychronized偏向锁,轻量级锁与重量级锁的区别与膨胀

Sychronized重量级锁
首先是关键字Sychronized,这是java中最早的也是最常用的锁,这个锁的原理是什么呢?
首先,java中对象在内存中存储时分为三块区域,对象头,实例数据,对齐补充,对象头中对象哈希吗,分代年龄,指向锁记录的指针,指向重量级锁的指针等等。我们今天只关注指向重量级锁的指针。该指针指向一个由c++实现的monitor类的对象,每个对象实例都有一个monitor实例,monitor中记录着当前占有该对象的线程,以及等待中的线程,这就是对象的wait方法和notify方法生效的原因。
对下面代码中的SychronizedTest类编译出来的class文件进行反编译,命令javap -verbose,查看有同步代码块的print和print1方法,如下图(注意,只有方法中有使用Sychronized同步的代码块才会有这两个code,直接修饰方法不会有,但是会使用ACC_SYNCHRONIZED标志位,比如代码里的print1方法)
关于这两条指令的作用,我们直接参考JVM规范中描述:
monitorenter:1
2
3
4Each object is associated with a monitor. A monitor is locked if and only if it has an owner. The thread that executes monitorenter attempts to gain ownership of the monitor associated with objectref, as follows:
• If the entry count of the monitor associated with objectref is zero, the thread enters the monitor and sets its entry count to one. The thread is then the owner of the monitor.
• If the thread already owns the monitor associated with objectref, it reenters the monitor, incrementing its entry count.
• If another thread already owns the monitor associated with objectref, the thread blocks until the monitor's entry count is zero, then tries again to gain ownership.
这段话的大概意思为:
每个对象有一个监视器锁(monitor)。当monitor被占用时就会处于锁定状态,线程执行monitorenter指令时尝试获取monitor的所有权,过程如下:
1、如果monitor的进入数为0,则该线程进入monitor,然后将进入数设置为1,该线程即为monitor的所有者。
2.如果线程已经占有该monitor,只是重新进入,则进入monitor的进入数加1.
3.如果其他线程已经占用了monitor,则该线程进入阻塞状态,直到monitor的进入数为0,再重新尝试获取monitor的所有权
monitorexit:1
2The thread that executes monitorexit must be the owner of the monitor associated with the instance referenced by objectref.
The thread decrements the entry count of the monitor associated with objectref. If as a result the value of the entry count is zero, the thread exits the monitor and is no longer its owner. Other threads that are blocking to enter the monitor are allowed to attempt to do so.
这段话的大概意思为:
执行monitorexit的线程必须是objectref所对应的monitor的所有者。
指令执行时,monitor的进入数减1,如果减1后进入数为0,那线程退出monitor,不再是这个monitor的所有者。其他被这个monitor阻塞的线程可以尝试去获取这个 monitor 的所有权。
通过这两段描述,我们应该能很清楚的看出Synchronized的实现原理,Synchronized的语义底层是通过一个monitor的对象来完成,其实wait/notify等方法也依赖于monitor对象,这就是为什么只有在同步的块或者方法中才能调用wait/notify等方法,否则会抛出java.lang.IllegalMonitorStateException的异常的原因。
我们再来看一下同步方法的反编译结果:
从反编译的结果来看,方法的同步并没有通过指令monitorenter和monitorexit来完成(理论上其实也可以通过这两条指令来实现),不过相对于普通方法,其常量池中多了ACC_SYNCHRONIZED标示符。JVM就是根据该标示符来实现方法的同步的:当方法调用时,调用指令将会检查方法的 ACC_SYNCHRONIZED 访问标志是否被设置,如果设置了,执行线程将先获取monitor,获取成功之后才能执行方法体,方法执行完后再释放monitor。在方法执行期间,其他任何线程都无法再获得同一个monitor对象。 其实本质上没有区别,只是方法的同步是一种隐式的方式来实现,无需通过字节码来完成。
Sychronized使用
Sychronized可以对对象加锁,也可以对类加锁,至于怎么生效主要看1.修饰的是什么,这个影响到竞争的是对象锁还是类锁;2.a对象的锁、b对象的锁以及类的锁之间不存在竞争冲突,具体表现在:
对对象加锁,那么只有该对象进入同被Sychronized修饰的方法时才会去竞争锁
对类加锁或者修饰静态方法,那就是对类加锁,这时候,所有该类的对象进入Sychronized修饰的静态方法时都需要先竞争类锁,也就是所谓的类锁对所有对象生效
其实说了这么多,总结起来无非3句话
1.对象锁是由对象维护的,类锁是由类的对象维护的,两者并不冲突。这里的不冲突表现在,当Sychronized修饰的静态方法被锁定时,Sychronized修饰的非静态方法仍可以进入,未被Sychronized修饰的方法也可以进入。
2.只有请求同一个锁的时候才会发生同步问题
3.代码块只会锁定代码块,其余部分可以被其他线程调用
那么Sychronized怎么使用呢?下面是使用Sychronized实现两种加锁方式,并不会发生冲突1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93package com.zgq.java;
import com.zgq.Algorithm;
import com.zgq.Utils;
/**
* Created by zgq on 2019/4/6.
*/
public class SychronizedTest implements Algorithm {
private SychronizedTest test1;
private SychronizedTest test2;
private static int count = 5;
public SychronizedTest(){}
@Override
public void execut() {
test1 = new SychronizedTest();
new Thread(new Test1("thread 1", test1)).start();
new Thread(new Test2("thread 2", test1)).start();
new Thread(new Test3("thread 3", test1)).start();
}
public void print(String name) throws InterruptedException {
synchronized(this) {
Utils.printString(name + " in and sleep");
Thread.sleep(1000);
count--;
Utils.printString(name + " run count = " + count);
}
}
public static synchronized void print1(String name) throws InterruptedException {
Utils.printString(name + " in and sleep");
Thread.sleep(2000);
count--;
Utils.printString(name + " run count = " + count);
}
public void print2(String name) throws InterruptedException {
Utils.printString(name + " in and sleep");
Thread.sleep(3000);
count--;
Utils.printString(name + " run count = " + count);
}
private static class Test1 implements Runnable {
private String name;
private SychronizedTest test;
private Test1(String name, SychronizedTest test) {
this.name = name;
this.test = test;
}
@Override
public void run() {
try {
test.print1(name);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
private static class Test2 implements Runnable {
private String name;
private SychronizedTest test;
private Test2(String name, SychronizedTest test) {
this.name = name;
this.test = test;
}
@Override
public void run() {
try {
test.print(name);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
private static class Test3 implements Runnable {
private String name;
private SychronizedTest test;
private Test3(String name, SychronizedTest test) {
this.name = name;
this.test = test;
}
@Override
public void run() {
try {
test.print2(name);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
上面代码最终输出1
2
3
4
5
6thread 1 in and sleep
thread 2 in and sleep
thread 3 in and sleep
thread 2 run count = 4
thread 1 run count = 3
thread 3 run count = 2
没有同步,可见非同步方法、同步方法、静态同步方法之间因为不会竞争同一锁,所以不会出现同步问题。