深入理解Java-synchronized关键字

作者: 289346a467da | 来源:发表于2018-03-23 10:11 被阅读159次

    理解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 代码块控制方位较小,它只会控制代码块中的代码,而位于代码块之外的代码是可以被多个线程访问的。

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

    相关文章

      网友评论

      本文标题:深入理解Java-synchronized关键字

      本文链接:https://www.haomeiwen.com/subject/yqjtcftx.html