一、概念
synchronized 是 Java 中的关键字,是利用锁的机制来实现同步的。
锁机制有如下两种特性:
即在同一时间只允许一个线程持有某个对象锁,通过这种特性来实现多线程中的协调机制,这样在同一时间只有一个线程对需同步的代码块(复合操作)进行访问。互斥性我们也往往称为操作的原子性。
必须确保在锁被释放之前,对共享变量所做的修改,对于随后获得该锁的另一个线程是可见的(即在获得锁时应获得最新共享变量的值),否则另一个线程可能是在本地缓存的某个副本上继续操作从而引起不一致。
二、对象锁和类锁
- 对象锁
在 Java 中,每个对象都会有一个 monitor 对象,这个对象其实就是 Java 对象的锁,通常会被称为“内置锁”或“对象锁”。类的对象可以有多个,所以每个对象有其独立的对象锁,互不干扰。
- 类锁
在 Java 中,针对每个类也有一个锁,可以称为“类锁”,类锁实际上是通过对象锁实现的,即类的 Class 对象锁。每个类只有一个 Class 对象,所以每个类只有一个类锁。
synchronized关键字特点具体表现如下:
- synchronized的指令严格遵守Java happens-before原则,一个monitor exit指令之前必须要有一个monitor enter指令。
一个机制、一个原则、两个指令,112。
二、 this锁和class锁分析
同步方法定义
public synchronized void sync(){ }
public synchronized static void staticSync(){ }
同步代码块定义
private final Object MUTEX = new Object();
public void sync(){
synchronized(MUTEX){
}
}
2.1 获取对象锁(this锁)
This锁实例分析(同一个对象)
class ThisLock {
public synchronized void m1() {
try {
System.out.println(Thread.currentThread().getName());
TimeUnit.MILLISECONDS.sleep(10_000L);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public void m2() { // 此方法我们不加锁
try {
System.out.println(Thread.currentThread().getName());
TimeUnit.MILLISECONDS.sleep(10_000L);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public class TestThread {
public static void main(String[] args) {
ThisLock thisLock = new ThisLock();
new Thread("T1"){
@Override
public void run() {
thisLock.m1();
}
}.start();
new Thread("T2"){
@Override
public void run() {
thisLock.m2();
}
}.start();
}
}
T1线程和T2线程不存在This锁的争抢,所以我们可以看到运行结果是几乎T1,T2同时输出,但是当我们在m1()方法和m2()方法同时加锁后,我们再观察运行结果
public synchronized void m2() { // 两个方法同时加锁
try {
System.out.println(Thread.currentThread().getName());
TimeUnit.MILLISECONDS.sleep(10_000L);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
运行结果:
示例二
class ThisLock {
private final Object LOCK = new Object(); // 定义LOCK锁
public synchronized void m1() { // 此时这个地方是 this锁
try {
System.out.println(Thread.currentThread().getName());
TimeUnit.MILLISECONDS.sleep(10_000L);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public void m2() { // 这个地方使用同步代码块 ,使用 LOCK 锁
synchronized (LOCK) {
try {
System.out.println(Thread.currentThread().getName());
TimeUnit.MILLISECONDS.sleep(10_000L);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public class TestThread {
public static void main(String[] args) {
ThisLock thisLock = new ThisLock();
new Thread("T1"){
@Override
public void run() {
thisLock.m1();
}
}.start();
new Thread("T2"){
@Override
public void run() {
thisLock.m2();
}
}.start();
}
}
分析:此时我们可以看到,T1和T2同时输出,因为synchronized锁的是两个不同的monitor,当然也就会同时输出。
不同对象获取this锁
import java.util.concurrent.TimeUnit;
/********************************
* @Author: kangna
* @Date: 2019/8/21 19:45
* @Version:
* @Desc: TODO
********************************/
class ThisLock {
public synchronized void m1() { //
try {
System.out.println("m1 " + Thread.currentThread().getName());
TimeUnit.MILLISECONDS.sleep(10_000L);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public synchronized void m2() { //
try {
System.out.println("m2 " + Thread.currentThread().getName());
TimeUnit.MILLISECONDS.sleep(10_000L);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public class TestThread {
public static void main(String[] args) {
ThisLock thisLock1 = new ThisLock();
ThisLock thisLock2 = new ThisLock();
new Thread("T1") {
@Override
public void run() {
thisLock1.m1();
}
}.start();
new Thread("T1") {
@Override
public void run() {
thisLock1.m2();
/*
由于 T1 执行 m1 和 m2 方法要使用同一个monitor所以T1线程
的 m1 抢到对象锁优先执行 ,而后 m2 执行,所以对于同一个对象锁的方法 顺序执行
同一个类的不同对象的对象锁互不干扰。
*/
}
}.start();
// new Thread("T2") {
// @Override
// public void run() {
// thisLock2.m2();
// }
// }.start();
new Thread("T2") {
@Override
public void run() {
thisLock2.m1();
}
}.start();
}
}
同一个类的不同对象的对象锁互不干扰(monitor不一样),同一对象的同步方法顺序执行。
2.2 类锁(class锁)
class SynchronizedStatic {
public synchronized static void m1() { //
try {
System.out.println(Thread.currentThread().getName());
TimeUnit.MILLISECONDS.sleep(10_000L);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public synchronized static void m2() { //
try {
System.out.println(Thread.currentThread().getName());
TimeUnit.MILLISECONDS.sleep(10_000L);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public class TestThread {
public static void main(String[] args) {
SynchronizedStatic synStatic = new SynchronizedStatic();
new Thread("T1") {
@Override
public void run() {
synStatic.m1();
}
}.start();
new Thread("T2") {
@Override
public void run() {
synStatic.m2();
}
}.start();
}
}
分析:观察运行情况,T1和T2并没有同时输出,说明两个方法用的是同一个锁。到底是个什么锁?
示例代码(添加static代码块)
class SynchronizedStatic {
// 此处定义一个静态代码块
static {
synchronized (SynchronizedStatic.class) { // 初始化静态代码块的时候要加一次锁,加的是class锁
try {
System.out.println("static " + Thread.currentThread().getName());
TimeUnit.MILLISECONDS.sleep(10_000L);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public synchronized static void m1() { //
try {
System.out.println("m1 " + Thread.currentThread().getName());
TimeUnit.MILLISECONDS.sleep(10_000L);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public synchronized static void m2() { //
try {
System.out.println("m2 " + Thread.currentThread().getName());
TimeUnit.MILLISECONDS.sleep(10_000L);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public static void m3() { // 普通的静态方法
try {
System.out.println("m3 " + Thread.currentThread().getName());
TimeUnit.MILLISECONDS.sleep(10_000L);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public class TestThread {
public static void main(String[] args) {
SynchronizedStatic synStatic = new SynchronizedStatic();
new Thread("T1") {
@Override
public void run() {
synStatic.m1();
}
}.start();
new Thread("T2") {
@Override
public void run() {
synStatic.m2();
}
}.start();
new Thread("T3") {
@Override
public void run() {
synStatic.m3();
}
}.start();
}
}
T1在这个静态代码块里去了,T3在执行的时候发现,这个代码块被锁住了,被T1占了。T1结束之后,T3马上结束,T3作为一个线程执行普通的静态方法也被挡在T1之后执行。
总结
- 对于静态方法,由于此时对象还未生成,所以只能采用类锁;
- 只要采用类锁,就会拦截所有线程,只能让一个线程访问
- 对于对象锁(this),如果是同一个实例,就会按顺序访问,但是如果是不同实例,就可以同时访问
- 如果对象锁跟访问的对象没有关系,那么就会都同时访问