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

前面我们分析了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() {

    }
}

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

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

看上面的代码,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锁的设计,同时也更好地解释了:

  1. 当执行Thread.sleep()的时候为什么不会释放锁(相当你在厕所睡着了,厕所还是锁着的);
  2. wait方法是在Object上而不是Thread上(锁在厕所门上而不是在你手上);
  3. 必须获得对象锁才能调用wait和notify、notifyAll方法(厕所门的锁控制权在你手上你才能决定是否把厕所让给别人用)。

你可能感兴趣的:(Java多线程,java,并发,线程安全,编程)