synchronized使用的正确姿势
Posted 劲火星空
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了synchronized使用的正确姿势相关的知识,希望对你有一定的参考价值。
synchronized在java中的作用是线程同步,其目的是保障同步区代码的正确执行,同一时间仅有一个线程进入同步区,那他的使用方式你了解的是否全面,他的底层原理你是否清楚呢?下面就从使用方式、实例、单例和原理四个方面对synchronized进行分析:
- 三种使用方式
- 实例讲解
- 单例中的使用
- 原理浅析
请您站稳扶好,开车了…
三种使用方式
分别是修饰实例方法,修饰静态方法,修饰代码块,代码块的修饰又分为三种方式
分类 | 被锁的对象 | 示例 |
---|---|---|
实例方法 | 类的实例对象 | public synchronized void method() |
静态方法 | 类对象 | public static synchronized void method() |
实例对象 | 类的实例对象 | synchronized(this) |
任意Object对象 | 实例对象Object | String 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使用的正确姿势的主要内容,如果未能解决你的问题,请参考以下文章
进程同步 (Process Synchronization)
JUC并发编程 --wait 和 sleep的区别 & 加锁对象的小建议 & wait notify 的正确姿势 & 虚假唤醒