首页 > 学院 > 开发设计 > 正文

Java并发编程系列(二)----synchronized锁详解

2019-11-14 08:45:53
字体:
来源:转载
供稿:网友

前面我们分析了volatile关键字,我们知道volatile关键字是无法保证线程安全的。那么java的线程安全主要由内置的synchronized关键字和Locks包下的类来保证,Locks包下的类留到下一节再讲。

关于synchronized,有两种同步方式,一种是同步方法,另外一种是同步代码块,关于什么是同步代码块,什么是同步方法就不细讲了,这里主要讲讲Java的内置锁。看一段代码

package com.rancho945.concurrent;public class SynchronizedDemo { PRivate Object lockObject = new Object(); //A:同步方法 public synchronized void method1(){ } //B:同步静态方法 public static synchronized void method2(){ } public void method3(){ //C:同步代码块 synchronized (lockObject) { } } public void method4(){ //D:同步代码块 synchronized (this) { } } //E:没有同步 public void method5() { }}

很多小伙伴们容易把锁的各种表现形式搞蒙,其实只要记住两点即可:

内置锁有两种:一种是类锁,另一种是对象锁;类锁只有一个,不同的对象有不同的对象锁。不同的锁之间不互斥,线程可以并发执行没有互斥条件的代码(废话)。

看上面的代码,A、B、C、三处的锁是不同的,A和D是同一把锁。A和D处的是SynchronizedDemo对象锁,B是Synchronized类锁,C是lockObject对象锁,E没有加锁。

那么也就意味着:多线程可以同时执行ABCE处的代码,因为他们没有互斥条件。而A和D在同一时刻只能被一个线程执行,因为他们持有的是同一把锁。

我们把上面的代码加一点点料。

package com.rancho945.concurrent;public class SynchronizeDemo { private Object lockObject = new Object(); //临界资源(共享变量) private static int count = 0; //A:同步方法 public synchronized void method1(){ count++; } //B:同步静态方法 public static synchronized void method2(){ count++; } public void method3(){ //C:同步代码块 synchronized (lockObject) { count++; } } public void method4(){ //D:同步代码块 synchronized (this) { count++; } } //E:没有同步 public void method5() { count++; }}

有可能被多个线程访问到的资源,我们称之为临界资源或共享变量。这里的count变量就是属于共享变量。那么在多线程的环境下,对count的操作是不安全的,比如某个线程执行method1的时候,另外的线程执行了method2或者3或者5。要想对count变量进行线程安全的操作,那么所有操作count变量的都需要同一把锁。 再看一个换汤不换药的:

package com.rancho945.concurrent;public class SynchronizeDemo { private Object lockObject = new Object(); private SynchronizeDemo lockDemo = new SynchronizeDemo(); //临界资源(共享资源) private static int count = 0; //A:同步方法 public synchronized void method1(){ count++; } //B:同步静态方法 public static synchronized void method2(){ count++; } public void method3(){ //C:同步代码块 synchronized (lockObject) { count++; } } public void method4(){ //D:同步代码块 synchronized (this) { count++; } } //E:没有同步 public void method5() { count++; } public void method6() { //F lockDemo.method1(); } public void method7() { //G lockDemo.method4(); } public void method8() { //H synchronized (lockDemo) { } } public void method9() { //I lockDemo.method3(); }}

这里的FGH都是同一把锁,他们之间是互斥的,因为使用的都是lockDemo对象的锁。而I与FGH不互斥,因为用的是lockDemo中的lockObject对象的锁

在发生异常的时候,JVM会自动释放锁,因此不会因为异常而发生死锁

那么我们开始思考,为什么锁的设计会是放在对象上而不是放在线程上呢?

答案从我们的生活中找,比如说,你(线程)上厕所(对象),是自己每次都带一把锁还是厕所门上装一把锁?这道理同样适合用于Java锁的设计,同时也更好地解释了:

当执行Thread.sleep()的时候为什么不会释放锁(相当你在厕所睡着了,厕所还是锁着的); wait方法是在Object上而不是Thread上(锁在厕所门上而不是在你手上); 必须获得对象锁才能调用wait和notify、notifyAll方法(厕所门的锁控制权在你手上你才能决定是否把厕所让给别人用)。
发表评论 共有条评论
用户名: 密码:
验证码: 匿名发表