深入理解Java-synchronized关键字

理解Java中的synchronized关键字

问题思考:我们可以带着问题理解 synchronized

问题1:
有如下一个类A
class A {
    public synchronized void a() {
    }

    public synchronized void b() {
    }
}

然后创建两个对象
A a1 = new A();
A a2 = new A();  
然后在两个线程中并发访问如下代码:
Thread1                       Thread2
a1.a();                       a2.a();
请问二者能否构成线程同步?

问题2:
如果A的定义是下面这种呢? 可以构成线程同步
class A {
    public static synchronized void a() {
    }

    public static synchronized void b() {
    }
}

1.理解synchronized 的含义

synchronized 是Java多线程中的同步锁机制可以对方法、对象或代码块进行加锁,保证在同一时间只有一个线程操作对应的资源,避免多线程同时访问相同的资源发生冲突。简单来说,synchronized 就是用来控制线程同步的,被加锁的代码块,不能被多个线程同时执行。

2.synchronized 主要修饰的对象有以下三种

1. 修饰普通方法。一个对象中的加锁方法只允许一个线程访问。但要注意这种情况下锁的是访问该方法的实例对象,多个线程并发访问同一个对象的该方法可以保证线程同步,若多个线程不同对象访问该方法无法保证线程同步。

举个例子:

public class Syncthread implements Runnable{
    private static int count;

    public Syncthread() {
        count = 0;
    }

    @Override
    public void run() {
        String name = Thread.currentThread().getName();
        test(name);
    }

    public  synchronized void test(String a) {
            for (int i = 0; i < 5; i++) {
                try {
                    System.out.println(Thread.currentThread().getName() + ":" + count++);
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
    }
}

1> 多个线程同一个对象访问test方法

        Syncthread syncthread = new Syncthread();
        Thread thread1 = new Thread(syncthread, "a");
        Thread thread2 = new Thread(syncthread, "b");
        thread1.start();
        thread2.start();

输出结果为:

a:0
a:1
a:2
a:3
a:4
b:5
b:6
b:7
b:8
b:9

从结果可以看出当thread1 执行完后,才会执行thread2 ,synchronized修饰的方法,同一时间只能有一个线程进行访问

2> 另一种情况,多个线程不同对象访问test 方法

        Syncthread syncthread = new Syncthread();
        Syncthread syncthread2 = new Syncthread();
        Thread thread1 = new Thread(syncthread, "a");
        Thread thread2 = new Thread(syncthread2, "b");
        thread1.start();
        thread2.start();

输出结果为:

a:0
b:1
a:2
b:2
b:3
a:3
a:4
b:4
b:5
a:6

从结果看出,test方法同一时间被多个线程同时访问,多个线程不同对象访问被加锁的方法,是无法保证线程同步的。

注意:

1.在定义接口方法时不能使用synchronized关键字。

2.造方法不能使用synchronized关键字,但可以使用synchronized代码块来进行同步。

问题1的答案可以呼之欲出了,是不能构成线程同步的。

2.修饰静态方法。由于静态方法是类方法,所以这种情况加锁的是这个类对象,这样多个线程不同对象访问该静态方法,也是可以保证同步的。

看一下代码,我们将test方法加上static关键字

 public  synchronized static void test(String a) 
Syncthread syncthread = new Syncthread();
Syncthread syncthread2 = new Syncthread();
Thread thread1 = new Thread(syncthread, "a");
Thread thread2 = new Thread(syncthread2, "b");
thread1.start();
thread2.start();

输出结果为:

a:0
a:1
a:2
a:3
a:4
b:5
b:6
b:7
b:8
b:9

从结果看出,test方法同一时间被多个线程同时访问,多个线程不同对象访问被加锁的静态方法,是可以保证线程同步的。

问题2的答案是:可以线程同步。

3.修饰代码块。其中普通代码块 如synchronized(obj) obj可以是类中的属性也可以是当前对象,它的同步效果和修饰普通方法是一样的;synchronized(obj.class)静态代码块它的同步效果和修饰静态方法类似。

用代码解释更容易理解,看一下代码

1>synchronized(obj.class) 修饰代码块,我们将test 方法改为

 public  void test(String a) {
        synchronized (Syncthread.class) {
            for (int i = 0; i < 5; i++) {
                try {
                    System.out.println(Thread.currentThread().getName() + ":" + count++);
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }

在多个线程同一个对象,或者多个线程不同对象的情况下。

看下输出结果:

a:0
a:1
a:2
a:3
a:4
b:5
b:6
b:7
b:8
b:9

果然,两种情况都可以保证线程同步,和修饰静态法的效果是一样的。

2> synchronized(obj) 修饰代码块,将test方法改为

 synchronized (this) {
            for (int i = 0; i < 5; i++) {
                try {
                    System.out.println(Thread.currentThread().getName() + ":" + count++);
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }

在多个线程同一个对象情况下,输出结果为:

a:0
a:1
a:2
a:3
a:4
b:5
b:6
b:7
b:8
b:9

可以看出,在多个线程同一个对象情况下,可以保证线程同步。

在多个线程,不同对象的情况下,输出结果为:

b:0
b:1
a:2
a:3
b:4
b:5
a:6
a:7
b:8

多个线程,不同对象的情况下,不能保证线程同步,和修饰普通方法的效果是一样的。

这里在说一下,synchronized(obj) 修饰代码块

当没有明确的对象作为锁,只是想让一段代码同步时,可以创建一个特殊的对象来充当锁。如:
     private byte[] lock = new byte[0];//特殊的变量
     public void method(){
             synchronized(lock){
                 //同步代码块
              }
     }
   说明:零长度的byte数组对象创建起来比任何对象都要好,只需要3条操作码,而object 则需要7行操作码

总结

synchronized 方法控制方位较大,它会同步对象中所有的synchronized方法的代码。

synchronized 代码块控制方位较小,它只会控制代码块中的代码,而位于代码块之外的代码是可以被多个线程访问的。

实现同步是要很大的系统开销作为代价的,甚至可能造成死锁,所以尽量避免无谓的同步控制。

    原文作者:JakePrim
    原文地址: https://www.jianshu.com/p/4a4070bb07a5
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞