从此不怕Synchronized锁_java

  • Synchronized的使用

Synchronized是通过监视器保证线程同步从而保证线程安全。但是Synchronized锁可以锁对象和锁类,并会产生不同的效果,通过下面的案例彻底理解Synchronized锁的使用方式。

即:

对于普通的同步方法,锁是当前实例对象

对于静态同步方法,锁是该类

对于同步方法块,锁是Synchronized括号里面配置的对象。

下面通过代码具体分析几种情况。要想了解并发情况,首先我们必须知道,类信息、实例对象分别存放在什么位置。类的信息,包括静态变量都是存放在方法区中;而实例对象,包括类的成员变量,是存放在堆中。

1. 成员变量+普通同步方法+锁

public class SynDemo implements Runnable {

    private int sum = 0;

    @Override
    public void run() {
        add();
    }

    public synchronized void add() {
        for (int i = 0; i < 1000; i++) {
            sum++;
        }
    }

    public static void main(String[] args) throws InterruptedException {
        SynDemo sync01 = new SynDemo();
        Thread thread1 = new Thread(sync01);
        Thread thread2 = new Thread(sync01);
        thread1.start();
        thread2.start();
        thread1.join(); //等待线程执行完
        thread2.join(); //等待线程执行完
        System.out.println(sync01.sum);
        System.out.println(new SynDemo().sum);
    }
}

result:
  2000
        0

 

public class SynDemo implements Runnable {

    private int sum = 0;

    @Override
    public void run() {
        add();
    }

    public synchronized void add() {
        for (int i = 0; i < 1000; i++) {
            sum++;
        }
    }

    public static void main(String[] args) throws InterruptedException {
        SynDemo sync01 = new SynDemo();
        SynDemo sync02 = new SynDemo();
        Thread thread1 = new Thread(sync01);
        Thread thread2 = new Thread(sync02);
        thread1.start();
        thread2.start();
        thread1.join(); //等待线程执行完
        thread2.join(); //等待线程执行完
        System.out.println(sync01.sum);
        System.out.println(sync02.sum);
    }
}

result:
  1000
        1000
        0

 

分析:

理解了这两个demo再去理解同步代码块下的多线程安全问题,将会达到事半功倍的效果。上面两个demo主要是想表达,成员变量和类的实例化对象一样,都是在堆中创建,每次new对象,都会在堆中产生一个新的对象。所以第一个demo中,当在线程同步的情况下,两个线程去操作同一个对象,最后的结果是2000;而第二个demo中,两个线程去操作两个实例化对象,所以每个对象的成员变量sum为1000。因此我们也可以发现,其实在第一个demo中才会有线程安全问题,在第二个demo中是不存在线程安全问题的,有疑问可以去掉锁验证一下。通过这个例子也可以去理解为什么sping中多例是线程安全的。

2. 成员变量+同步代码块+对象锁

public class SynDemo implements Runnable {

    private int sum = 0;

    @Override
    public void run() {
        add();
    }

    private void add() {
        synchronized (this) {
            for (int i = 0; i < 1000; i++) {
                sum++;
            }
        }
    }

    public static void main(String[] args) throws InterruptedException {
        SynDemo sync01 = new SynDemo();
        Thread thread1 = new Thread(sync01);
        Thread thread2 = new Thread(sync01);
        thread1.start();
        thread2.start();
        thread1.join(); //等待线程执行完
        thread2.join(); //等待线程执行完
        System.out.println(sync01.sum);
        System.out.println(new SynDemo().sum);
    }
}

result:
  2000
        0

 

public class SynDemo implements Runnable {

    private int sum = 0;

    @Override
    public void run() {
        add();
    }

    private void add() {
        synchronized (this) {
            for (int i = 0; i < 1000; i++) {
                sum++;
            }
        }
    }

    public static void main(String[] args) throws InterruptedException {
        SynDemo sync01 = new SynDemo();
        SynDemo sync02 = new SynDemo();
        Thread thread1 = new Thread(sync01);
        Thread thread2 = new Thread(sync02);
        thread1.start();
        thread2.start();
        thread1.join(); //等待线程执行完
        thread2.join(); //等待线程执行完
        System.out.println(sync01.sum);
        System.out.println(sync02.sum);
        System.out.println(new SynDemo().sum);
    }
}

result:
  1000
  1000
  0

 

分析:

同案例1一样,Demo1为两个线程执行一个实例化对象,但是加了Synchronized对象锁,因此实现了同步,保证线程安全。Demo2为两个线程执行两个实例化对象,各自利用各自的成员变量sum,因此不会产生并发安全问题。

3. 成员变量+同步代码块+类锁

public class SynDemo implements Runnable {

    private int sum = 0;

    @Override
    public void run() {
        add();
    }

    private void add() {
        synchronized (SynDemo.class) {
            for (int i = 0; i < 1000; i++) {
                sum++;
            }
        }
    }

    public static void main(String[] args) throws InterruptedException {
        SynDemo sync01 = new SynDemo();
        Thread thread1 = new Thread(sync01);
        Thread thread2 = new Thread(sync01);
        thread1.start();
        thread2.start();
        thread1.join(); //等待线程执行完
        thread2.join(); //等待线程执行完
        System.out.println(sync01.sum);
        System.out.println(new SynDemo().sum);
    }
}

result:
  2000
  0

 

public class SynDemo implements Runnable {

    private int sum = 0;

    @Override
    public void run() {

        add();
    }

    private void add() {
        synchronized (SynDemo.class) {
            for (int i = 0; i < 1000; i++) {
                sum++;
            }
        }
    }

    public static void main(String[] args) throws InterruptedException {
        SynDemo sync01 = new SynDemo();
        SynDemo sync02 = new SynDemo();
        Thread thread1 = new Thread(sync01);
        Thread thread2 = new Thread(sync02);
        thread1.start();
        thread2.start();
        thread1.join(); //等待线程执行完
        thread2.join(); //等待线程执行完
        System.out.println(sync01.sum);
        System.out.println(sync02.sum);
        System.out.println(new SynDemo().sum);
    }
}

result:
  1000
  1000
  0

 

分析:

Demo1为两个线程执行一个实例化对象,会产生并发安全问题,但是加了同步类锁(可以理解为锁的级别比对象锁更高),当然也可以实现并发同步,保证线程安全。而Demo2同样实例化两个对象,各自操作各自的成员变量sum,也不会产生线程安全问题。

4. 静态变量+普通方法+锁

public class SynDemo implements Runnable {

    private static int sum = 0;

    @Override
    public void run() {
        add();
    }

    private synchronized void add() {
            for (int i = 0; i < 1000; i++) {
                sum++;
            }
    }

    public static void main(String[] args) throws InterruptedException {
        SynDemo sync01 = new SynDemo();
        Thread thread1 = new Thread(sync01);
        Thread thread2 = new Thread(sync01);
        thread1.start();
        thread2.start();
        thread1.join(); //等待线程执行完
        thread2.join(); //等待线程执行完
        System.out.println(sum);
    }
}

result:
  2000

 

public class SynDemo implements Runnable {

    private static int sum = 0;

    @Override
    public void run() {
        add();
    }

    private synchronized void add() {
            for (int i = 0; i < 1000; i++) {
                sum++;
            }
    }

    public static void main(String[] args) throws InterruptedException {
        SynDemo sync01 = new SynDemo();
        SynDemo sync02 = new SynDemo();
        Thread thread1 = new Thread(sync01);
        Thread thread2 = new Thread(sync02);
        thread1.start();
        thread2.start();
        thread1.join(); //等待线程执行完
        thread2.join(); //等待线程执行完
        System.out.println(sum);
    }
}

输出结果不确定(存在线程安全问题)

 

分析:

从案例4我们要注意,由成员变量换成静态变量,而上面已经讲过,静态变量存放在方法区中,所有实例化对象共享一份。再看Demo1,两个线程执行同一个实例化对象,然后添加的是对象锁,因此该对象锁能锁住该实例化对象,实现同步,保证线程安全。

Demo2是两个线程执行两个实例化对象,添加的是对象锁,相当于各自的对象锁锁住各自的对象,而静态变量是类变量,存放在方法区中而不是堆中,此情况对象锁并不能保证线程同步,因此会产生线程安全问题。

5. 静态变量+静态方法+锁

public class SynDemo implements Runnable {

    private static int sum = 0;

    @Override
    public void run() {
        add();
    }

    static synchronized void add() {
        for (int i = 0; i < 1000; i++) {
            sum++;
        }
    }

    public static void main(String[] args) throws InterruptedException {
        SynDemo sync01 = new SynDemo();
        Thread thread1 = new Thread(sync01);
        Thread thread2 = new Thread(sync01);
        thread1.start();
        thread2.start();
        thread1.join(); //等待线程执行完
        thread2.join(); //等待线程执行完
        System.out.println(sum);
    }
}

result:
  2000

 

public class SynDemo implements Runnable {

    private static int sum = 0;

    @Override
    public void run() {
        add();
    }

    static synchronized void add() {
        for (int i = 0; i < 1000; i++) {
            sum++;
        }
    }

    public static void main(String[] args) throws InterruptedException {
        SynDemo sync01 = new SynDemo();
        SynDemo sync02 = new SynDemo();
        Thread thread1 = new Thread(sync01);
        Thread thread2 = new Thread(sync02);
        thread1.start();
        thread2.start();
        thread1.join(); //等待线程执行完
        thread2.join(); //等待线程执行完
        System.out.println(sum);
    }
}

result:
  2000

 

分析:

该案例相比案例4,锁由对象锁换成类锁,对于Demo1,两个线程操作一个对象,毫无疑问会使其同步。而Demo2,两个线程执行两个实例化对象,由于使用的是类锁,也会使线程同步,保证线程安全。

6. 静态变量+同步代码块+对象锁

public class SynDemo implements Runnable {

    private static int sum = 0;

    @Override
    public void run() {
        add();
    }

    private void add() {
        synchronized (this) {
            for (int i = 0; i < 1000; i++) {
                sum++;
            }
        }
    }

    public static void main(String[] args) throws InterruptedException {
        SynDemo sync01 = new SynDemo();
        Thread thread1 = new Thread(sync01);
        Thread thread2 = new Thread(sync01);
        thread1.start();
        thread2.start();
        thread1.join(); //等待线程执行完
        thread2.join(); //等待线程执行完
        System.out.println(sum);
    }
}

result:
  2000

 

public class SynDemo implements Runnable {

    private static int sum = 0;

    @Override
    public void run() {
        add();
    }

    private void add() {
        synchronized (this) {
            for (int i = 0; i < 1000; i++) {
                sum++;
            }
        }
    }

    public static void main(String[] args) throws InterruptedException {
        SynDemo sync01 = new SynDemo();
        SynDemo sync02 = new SynDemo();
        Thread thread1 = new Thread(sync01);
        Thread thread2 = new Thread(sync02);
        thread1.start();
        thread2.start();
        thread1.join(); //等待线程执行完
        thread2.join(); //等待线程执行完
        System.out.println(sum);
    }
}

输出结果不确定(存在线程安全问题)

 

分析:该案例和案例4一样,添加对象锁,只能保证同一对象的并发同步,不能保证不同对象同步。

7. 静态变量+同步代码块+类锁

public class SynDemo implements Runnable {

    private static int sum = 0;

    @Override
    public void run() {
        add();
    }

    private void add() {
        synchronized (SynDemo.class) {
            for (int i = 0; i < 1000; i++) {
                sum++;
            }
        }
    }

    public static void main(String[] args) throws InterruptedException {
        SynDemo sync01 = new SynDemo();
        Thread thread1 = new Thread(sync01);
        Thread thread2 = new Thread(sync01);
        thread1.start();
        thread2.start();
        thread1.join(); //等待线程执行完
        thread2.join(); //等待线程执行完
        System.out.println(sum);
    }
}

result:
  2000

 

public class SynDemo implements Runnable {

    private static int sum = 0;

    @Override
    public void run() {
        add();
    }

    private void add() {
        synchronized (SynDemo.class) {
            for (int i = 0; i < 1000; i++) {
                sum++;
            }
        }
    }

    public static void main(String[] args) throws InterruptedException {
        SynDemo sync01 = new SynDemo();
        SynDemo sync02 = new SynDemo();
        Thread thread1 = new Thread(sync01);
        Thread thread2 = new Thread(sync02);
        thread1.start();
        thread2.start();
        thread1.join(); //等待线程执行完
        thread2.join(); //等待线程执行完
        System.out.println(sum);
    }
}

result:
  2000

 

分析:

该案例同案例5一样,添加类锁,无论是多个线程操作一个实例化对象还是多个实例化对象,都能保证线程安全。

总结:

对象锁只能保证各自实例化对象并发的线程安全问题。类锁可以保证多个实例化多谢的安全问题。