您的位置:首页 > 编程语言 > Java开发

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

2017-02-06 00:34 357 查看
前面我们分析了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在同一时刻只能被一个线程执行,因为他们持有的是同一把锁。

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


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方法(厕所门的锁控制权在你手上你才能决定是否把厕所让给别人用)。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: