synchronized使用的正确姿势

Posted 劲火星空

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了synchronized使用的正确姿势相关的知识,希望对你有一定的参考价值。

synchronized在java中的作用是线程同步,其目的是保障同步区代码的正确执行,同一时间仅有一个线程进入同步区,那他的使用方式你了解的是否全面,他的底层原理你是否清楚呢?下面就从使用方式、实例、单例和原理四个方面对synchronized进行分析:

  • 三种使用方式
  • 实例讲解
  • 单例中的使用
  • 原理浅析

请您站稳扶好,开车了…

三种使用方式

分别是修饰实例方法,修饰静态方法,修饰代码块,代码块的修饰又分为三种方式

分类被锁的对象示例
实例方法类的实例对象public synchronized void method()
静态方法类对象public static synchronized void method()
实例对象类的实例对象synchronized(this)
任意Object对象实例对象ObjectString lock = new String(); synchronized(lock)
class对象类对象synchronized(DemoClass.class)

实例讲解

下面分别通过几个实例来加深这几种使用方式的理解:
(1)实例方法
(2)静态方法
(3)this锁代码块
(4)任意锁代码块
(5)类锁代码块
(6)this代码块锁和方法
(7)任意代码块锁和方法

所有实例都是通过Runnable来建立两个线程,通过延时来查看多个线程是否同步执行

1. 实例方法

直接在方法前面加synchronized即可,其中normalMethod中进行两个线程的创建和运行

private void normalMethod() 
    final ObjectMethod service = new ObjectMethod();
    Thread a = new Thread(new Runnable() 
        @Override
        public void run() 
            service.setUserNamePassWord();
        
    );
    a.setName("A");
    a.start();

    Thread b = new Thread(new Runnable() 
        @Override
        public void run() 
            service.setUserNamePassWord();
        
    );
    b.setName("B");
    b.start();


public class ObjectMethod 
    public synchronized void setUserNamePassWord()
        try 
            Log.i("phototest", "thread name="+Thread.currentThread().getName()
                    +" 进入普通方法:"+System.currentTimeMillis());
            Thread.sleep(3000);
            Log.i("phototest", "thread name="+Thread.currentThread().getName()
                    +" 进入普通方法:"+System.currentTimeMillis());
         catch (InterruptedException e) 
            e.printStackTrace();
        
    


执行结果如下:

phototest: thread name=A 进入普通方法:1564112646128
phototest: thread name=A 进入普通方法:1564112649129
phototest: thread name=B 进入普通方法:1564112649131
phototest: thread name=B 进入普通方法:1564112652132

可以看到线程 A 和 B 依次进入和退出 
结论:同一个实例情况下,修饰实例方法可以实现线程同步,因为这里我们只创建了service一个实例用两个线程来调用,如果每个线程分别传入不同的实例的话,就不能保证线程同步了

2. 静态方法

直接在静态方法前面加synchronized即可

private void staticMethod() 
    final ObjectStatic serviceA = new ObjectStatic();
    final ObjectStatic serviceB = new ObjectStatic();

    Thread a = new Thread(new Runnable() 
        @Override
        public void run() 
            serviceA.setUserNamePassWord();
        
    );
    a.setName("A");
    a.start();

    Thread b = new Thread(new Runnable() 
        @Override
        public void run() 
            serviceB.setUserNamePassWord();
        
    );
    b.setName("B");
    b.start();


public static class ObjectStatic 
    public synchronized static void setUserNamePassWord()
        try 
            Log.i("phototest", "thread name="+Thread.currentThread().getName()
                    +" 进入静态方法:"+System.currentTimeMillis());
            Thread.sleep(3000);
            Log.i("phototest", "thread name="+Thread.currentThread().getName()
                    +" 进入静态方法:"+System.currentTimeMillis());
         catch (InterruptedException e) 
            e.printStackTrace();
        
    

执行结果如下:

phototest: thread name=B 进入静态方法:1564113275247
phototest: thread name=B 进入静态方法:1564113278249
phototest: thread name=A 进入静态方法:1564113278250
phototest: thread name=A 进入静态方法:1564113281252

可以看到线程 B 和 A 依次进入和退出
结论:这里两个线程分别持有是两个不同的实例,但仍然能够保证线程同步,因为对静态方法加锁实际上是对类对象加锁而不是对实例对象加锁

3. this锁代码块

在方法里面加上synchronized (this) 来修饰整个代码块

private void thisStock() 
    final ThisStockObject service = new ThisStockObject();
    Thread a = new Thread(new Runnable() 
        @Override
        public void run() 
            service.setUserNamePassWord();
        
    );
    a.setName("A");
    a.start();

    Thread b = new Thread(new Runnable() 
        @Override
        public void run() 
            service.setUserNamePassWord();
        
    );
    b.setName("B");
    b.start();


public class ThisStockObject 
    public void setUserNamePassWord()
        try 
            synchronized (this) 
             Log.i("phototest", "thread name="+Thread.currentThread().getName()
                     +" 进入this代码块:"+System.currentTimeMillis());
             Thread.sleep(3000);
             Log.i("phototest", "thread name="+Thread.currentThread().getName()
                     +" 进入this代码块:"+System.currentTimeMillis());
            
         catch (InterruptedException e) 
            e.printStackTrace();
        
    

执行结果如下:

phototest: thread name=B 进入this代码块:1564116947677
phototest: thread name=B 进入this代码块:1564116950679
phototest: thread name=A 进入this代码块:1564116950683
phototest: thread name=A 进入this代码块:1564116953684

可以看到线程 B 和 A 依次进入和退出
结论:两个线程使用同一个实例的情况下使用this代码块可保证线程同步,如果是两个实例的话就不能保证了

4. 任意锁代码块

在方法里面加上synchronized (lock) 来修饰整个代码块,其中lock是任意Object对象

private void lockStock() 
    final LockStockObject service = new LockStockObject();
    Thread a = new Thread(new Runnable() 
        @Override
        public void run() 
            service.setUserNamePassWord();
        
    );
    a.setName("A");
    a.start();

    Thread b = new Thread(new Runnable() 
        @Override
        public void run() 
            service.setUserNamePassWord();
        
    );
    b.setName("B");
    b.start();


public class LockStockObject 
    private String lock=new String();
    public void setUserNamePassWord()
        try 
            synchronized (lock) 
                Log.i("phototest", "thread name="+Thread.currentThread().getName()
                        +" 进入lock代码块:"+System.currentTimeMillis());
                Thread.sleep(3000);
                Log.i("phototest", "thread name="+Thread.currentThread().getName()
                        +" 进入lock代码块:"+System.currentTimeMillis());
            
         catch (InterruptedException e) 
            e.printStackTrace();
        
    

执行结果如下:

phototest: thread name=B 进入lock代码块:1564116947677
phototest: thread name=B 进入lock代码块:1564116950679
phototest: thread name=A 进入lock代码块:1564116950683
phototest: thread name=A 进入lock代码块:1564116953684

可以看到线程 B 和 A 依次进入和退出
结论:两个线程使用一个实例的情况下使用任意类型代码块锁效果和this是相同的,也是可以保证线程同步,如果是两个实例的话就不能保证了

5. 类锁代码块

在方法里面加上synchronized (DemoClass.class) 来修饰整个代码块

private void classStock() 
    final ClassStockTest serviceA = new ClassStockTest();
    final ClassStockTest serviceB = new ClassStockTest();
    Thread a = new Thread(new Runnable() 
        @Override
        public void run() 
            serviceA.methodA();
        
    );
    a.start();

    Thread b = new Thread(new Runnable() 
        @Override
        public void run() 
            serviceB.methodB();
        
    );
    b.start();


public class ClassStockTest 
    public void methodA()
        try 
            synchronized (ClassStockTest.class) 
                Log.i("phototest", "a begin");
                Thread.sleep(3000);
                Log.i("phototest", "a   end");
            
         catch (InterruptedException e) 
            e.printStackTrace();
        
    
    public void methodB() 
        synchronized (ClassStockTest.class) 
            Log.i("phototest", "b begin");
            Log.i("phototest", "b   end");
        
    

执行结果如下:

phototest: a begin
phototest: a   end
phototest: b begin
phototest: b   end

可以看到线程 a 和 b 依次进入和退出
结论:和静态方法加锁效果相同,也是对类对象加的锁,即使使用多个实例的情况也是可保证线程安全的

6. this代码块锁和方法

线程分别调用不同的两个方法

private void thisStockAndMethod() 
    final ThisStockAndMethod service = new ThisStockAndMethod();
    Thread a = new Thread(new Runnable() 
        @Override
        public void run() 
            service.methodA();
        
    );
    a.start();

    Thread b = new Thread(new Runnable() 
        @Override
        public void run() 
            service.methodB();
        
    );
    b.start();


public class ThisStockAndMethod 
    public void methodA()
        try 
            synchronized (this) 
                Log.i("phototest", "a begin");
                Thread.sleep(3000);
                Log.i("phototest", "a   end");
            
         catch (InterruptedException e) 
            e.printStackTrace();
        
    
    public synchronized void methodB() 
        Log.i("phototest", "b begin");
        Log.i("phototest", "b   end");
    

执行结果如下:

phototest: a begin
phototest: a   end
phototest: b begin
phototest: b   end

可以看到线程 a 和 b 依次进入和退出
结论:也是线程同步的,因为this静态代码块和methodB用的是一个锁实例,如果是不同的实例就不同步的,可以看下边lock

7. 任意代码块锁和方法

两个线程分别调用不同的两个方法

private void lockStockAndMethod() 
    final LockStockAndMethod service = new LockStockAndMethod();
    Thread a = new Thread(new Runnable() 
        @Override
        public void run() 
            service.methodA();
        
    );

    Thread b = new Thread(new Runnable() 
        @Override
        public void run() 
            service.methodB();
        
    );

    a.start();
    b.start();


public class LockStockAndMethod 
    private String lock=new String();
    public void methodA()
        try 
            synchronized (lock) 
                Log.i("phototest", "a begin");
                Thread.sleep(3000);
                Log.i("phototest", "a   end");
            
         catch (InterruptedException e) 
            e.printStackTrace();
        
    
    public synchronized void methodB() 
        Log.i("phototest", "b begin");
        Log.i("phototest", "b   end");
    

执行结果如下:

phototest: a begin
phototest: a   end
phototest: b begin
phototest: b   end

可以看到线程 a 和 b 没有依次进入和退出
结论:因为methodA和methodB是使用不同的锁对象,所以不是能线程同步

单例中的使用

这里只对三种线程安全的单例模式进行分析:
首先我们直接在getInstance静态方法上synchronized,如下

class SingletonLazy1 
  private static SingletonLazy1 singletonLazy;

  private SingletonLazy1() 
  

  public static synchronized SingletonLazy1 getInstance() 
    try 
      if (null == singletonLazy) 
        singletonLazy = new SingletonLazy1();
      
     catch (InterruptedException e) 
      e.printStackTrace();
    
    return singletonLazy;
  

这种方式是对类加锁,可以达到线程安全。但是缺点就是效率太低,是同步运行的,下个线程想要取得对象,就必须要等上一个线程释放,才可以继续执行。我们可通过如下代码来测试,通过观察打印的hashCode是否相同来看是否创建的是一个单例对象:

public class SingletonLazyTest 

  以上是关于synchronized使用的正确姿势的主要内容,如果未能解决你的问题,请参考以下文章

synchronized使用的正确姿势

【UTC】CentOS7修改时区的正确姿势

掌握Redis分布式锁的正确姿势

进程同步 (Process Synchronization)

Synchronized锁的是什么?

JUC并发编程 --wait 和 sleep的区别 & 加锁对象的小建议 & wait notify 的正确姿势 & 虚假唤醒