并发与高并发-线程安全性-原子性-synchronized
Posted jmy520
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了并发与高并发-线程安全性-原子性-synchronized相关的知识,希望对你有一定的参考价值。
前言
闲暇时刻,谈一下曾经在多线程教程中接触的同步锁synchronized,相当于复习一遍吧。
主要介绍
synchronized:依赖JVM
Lock:依赖特殊的CPU指令,代码实现,ReetrantLock
主体内容
一、那么我们主要先讲解一下关于同步锁synchronized的作用范围。
1.修饰代码块:作用范围-大括号括起来的代码,作用于调用这个代码块的对象,如果不同对象调用该代码块就不会同步。
2.修饰方法:作用范围-整个方法,作用于调用这个方法的对象。
3.修饰静态方法:作用范围-整个静态方法,作用于这个类的所有对象。
4.修饰类:作用范围-synchronized后面括号括起来的部分,作用于这个类的所有对象(ps:两个线程调用同一个类的不同对象上的这种同步语句,也会进行同步)。
二、接下来,我们分别针对synchronized修饰的这四种情况写四个例子。
1.首先,写一个方法,让synchronized修饰代码块。
/** * 修饰代码块 */ public void test1(){ synchronized (this) { for(int i=0;i<10;i++){ log.info("test1-{}",i); } } } public static void main(String[] args){ SyncDecorate sd = new SyncDecorate(); ExecutorService executorService = Executors.newCachedThreadPool(); executorService.execute(()->{ sd.test1(); }); executorService.execute(()->{ sd.test1(); }); }
解释:这里我们用线程池创建了两个线程分别访问test1方法中的同步代码块,第二个线程其实不等第一个线程执行完毕,就开始去访问test1方法,但test1方法中的代码块由于第一个线程的访问上了锁,所以第二个线程不得不等待第一个线程执行完这个方法。因此执行结果为如下:
23:42:39.348 [pool-1-thread-1] INFO com.controller.synchronize.SyncDecorate - test1-0 23:42:39.351 [pool-1-thread-1] INFO com.controller.synchronize.SyncDecorate - test1-1 23:42:39.351 [pool-1-thread-1] INFO com.controller.synchronize.SyncDecorate - test1-2 23:42:39.351 [pool-1-thread-1] INFO com.controller.synchronize.SyncDecorate - test1-3 23:42:39.351 [pool-1-thread-1] INFO com.controller.synchronize.SyncDecorate - test1-4 23:42:39.351 [pool-1-thread-1] INFO com.controller.synchronize.SyncDecorate - test1-5 23:42:39.351 [pool-1-thread-1] INFO com.controller.synchronize.SyncDecorate - test1-6 23:42:39.351 [pool-1-thread-1] INFO com.controller.synchronize.SyncDecorate - test1-7 23:42:39.351 [pool-1-thread-1] INFO com.controller.synchronize.SyncDecorate - test1-8 23:42:39.351 [pool-1-thread-1] INFO com.controller.synchronize.SyncDecorate - test1-9 23:42:39.351 [pool-1-thread-2] INFO com.controller.synchronize.SyncDecorate - test1-0 23:42:39.351 [pool-1-thread-2] INFO com.controller.synchronize.SyncDecorate - test1-1 23:42:39.351 [pool-1-thread-2] INFO com.controller.synchronize.SyncDecorate - test1-2 23:42:39.351 [pool-1-thread-2] INFO com.controller.synchronize.SyncDecorate - test1-3 23:42:39.351 [pool-1-thread-2] INFO com.controller.synchronize.SyncDecorate - test1-4 23:42:39.351 [pool-1-thread-2] INFO com.controller.synchronize.SyncDecorate - test1-5 23:42:39.351 [pool-1-thread-2] INFO com.controller.synchronize.SyncDecorate - test1-6 23:42:39.351 [pool-1-thread-2] INFO com.controller.synchronize.SyncDecorate - test1-7 23:42:39.351 [pool-1-thread-2] INFO com.controller.synchronize.SyncDecorate - test1-8 23:42:39.351 [pool-1-thread-2] INFO com.controller.synchronize.SyncDecorate - test1-9
2.接下来,我写一段让synchronized修饰方法的代码。
/** * 修饰方法 */ public synchronized void test2(){ for(int i=0;i<10;i++){ log.info("test2-{}",i); } } public static void main(String[] args){ SyncDecorate sd = new SyncDecorate(); ExecutorService executorService = Executors.newCachedThreadPool(); executorService.execute(()->{ sd.test2(); }); executorService.execute(()->{ sd.test2(); }); }
结果为:
00:42:09.200 [pool-1-thread-1] INFO com.controller.synchronize.SyncDecorate - test2-0 00:42:09.203 [pool-1-thread-1] INFO com.controller.synchronize.SyncDecorate - test2-1 00:42:09.203 [pool-1-thread-1] INFO com.controller.synchronize.SyncDecorate - test2-2 00:42:09.203 [pool-1-thread-1] INFO com.controller.synchronize.SyncDecorate - test2-3 00:42:09.203 [pool-1-thread-1] INFO com.controller.synchronize.SyncDecorate - test2-4 00:42:09.203 [pool-1-thread-1] INFO com.controller.synchronize.SyncDecorate - test2-5 00:42:09.203 [pool-1-thread-1] INFO com.controller.synchronize.SyncDecorate - test2-6 00:42:09.203 [pool-1-thread-1] INFO com.controller.synchronize.SyncDecorate - test2-7 00:42:09.203 [pool-1-thread-1] INFO com.controller.synchronize.SyncDecorate - test2-8 00:42:09.203 [pool-1-thread-1] INFO com.controller.synchronize.SyncDecorate - test2-9 00:42:09.203 [pool-1-thread-2] INFO com.controller.synchronize.SyncDecorate - test2-0 00:42:09.203 [pool-1-thread-2] INFO com.controller.synchronize.SyncDecorate - test2-1 00:42:09.203 [pool-1-thread-2] INFO com.controller.synchronize.SyncDecorate - test2-2 00:42:09.203 [pool-1-thread-2] INFO com.controller.synchronize.SyncDecorate - test2-3 00:42:09.203 [pool-1-thread-2] INFO com.controller.synchronize.SyncDecorate - test2-4 00:42:09.203 [pool-1-thread-2] INFO com.controller.synchronize.SyncDecorate - test2-5 00:42:09.203 [pool-1-thread-2] INFO com.controller.synchronize.SyncDecorate - test2-6 00:42:09.203 [pool-1-thread-2] INFO com.controller.synchronize.SyncDecorate - test2-7 00:42:09.203 [pool-1-thread-2] INFO com.controller.synchronize.SyncDecorate - test2-8 00:42:09.203 [pool-1-thread-2] INFO com.controller.synchronize.SyncDecorate - test2-9
由此可见,修饰代码块和修饰方法的结果是一个道理。
3.上面两个例子都是正向的例子,如果我们现在换不同的对象,通过调用test1,也就是同步代码块的方法,让其乱序输出。结果会是怎样呢?那么我得首先在test1方法中加入一个参数j用以区分两个线程的结果。
/** * 修饰代码块 */ public void test1(int j){ synchronized (this) { for(int i=0;i<10;i++){ log.info("test1-{}-{}",j,i); } } } public static void main(String[] args){ //声明两个类对象,让两个线程通过两个对象分别调用各自的test1方法 SyncDecorate sd1 = new SyncDecorate(); SyncDecorate sd2 = new SyncDecorate(); ExecutorService executorService = Executors.newCachedThreadPool(); executorService.execute(()->{ sd1.test1(1); }); executorService.execute(()->{ sd2.test1(2); }); }
结果为:
00:46:51.803 [pool-1-thread-1] INFO com.controller.synchronize.SyncDecorate - test1-1-0 00:46:51.803 [pool-1-thread-2] INFO com.controller.synchronize.SyncDecorate - test1-2-0 00:46:51.806 [pool-1-thread-1] INFO com.controller.synchronize.SyncDecorate - test1-1-1 00:46:51.806 [pool-1-thread-2] INFO com.controller.synchronize.SyncDecorate - test1-2-1 00:46:51.806 [pool-1-thread-1] INFO com.controller.synchronize.SyncDecorate - test1-1-2 00:46:51.806 [pool-1-thread-2] INFO com.controller.synchronize.SyncDecorate - test1-2-2 00:46:51.807 [pool-1-thread-1] INFO com.controller.synchronize.SyncDecorate - test1-1-3 00:46:51.807 [pool-1-thread-2] INFO com.controller.synchronize.SyncDecorate - test1-2-3 00:46:51.807 [pool-1-thread-1] INFO com.controller.synchronize.SyncDecorate - test1-1-4 00:46:51.807 [pool-1-thread-2] INFO com.controller.synchronize.SyncDecorate - test1-2-4 00:46:51.807 [pool-1-thread-1] INFO com.controller.synchronize.SyncDecorate - test1-1-5 00:46:51.807 [pool-1-thread-2] INFO com.controller.synchronize.SyncDecorate - test1-2-5 00:46:51.807 [pool-1-thread-1] INFO com.controller.synchronize.SyncDecorate - test1-1-6 00:46:51.807 [pool-1-thread-1] INFO com.controller.synchronize.SyncDecorate - test1-1-7 00:46:51.807 [pool-1-thread-2] INFO com.controller.synchronize.SyncDecorate - test1-2-6 00:46:51.807 [pool-1-thread-1] INFO com.controller.synchronize.SyncDecorate - test1-1-8 00:46:51.807 [pool-1-thread-1] INFO com.controller.synchronize.SyncDecorate - test1-1-9 00:46:51.807 [pool-1-thread-2] INFO com.controller.synchronize.SyncDecorate - test1-2-7 00:46:51.807 [pool-1-thread-2] INFO com.controller.synchronize.SyncDecorate - test1-2-8 00:46:51.807 [pool-1-thread-2] INFO com.controller.synchronize.SyncDecorate - test1-2-9
我们发现,线程一和线程二都是各自随着for循环升序,互相交叉但却没有影响。这种现象就证明了同步代码块对于当前对象,不同的调用之间是互相不影响的。
4.再猜想一下,如果我们标号3中的例子中用两个线程通过不同对象调用test2方法,结果会不会也是一样的呢?同样的,给test2方法也加上一个参数j,用以区分。
/** * 修饰方法 */ public synchronized void test2(int j){ for(int i=0;i<10;i++){ log.info("test2-{}-{}",j,i); } } public static void main(String[] args){ //声明两个类对象,让两个线程通过两个对象分别调用各自的test1方法 SyncDecorate sd1 = new SyncDecorate(); SyncDecorate sd2 = new SyncDecorate(); ExecutorService executorService = Executors.newCachedThreadPool(); executorService.execute(()->{ sd1.test2(1); }); executorService.execute(()->{ sd2.test2(2); }); }
最后的结果为:
01:00:07.354 [pool-1-thread-1] INFO com.controller.synchronize.SyncDecorate - test2-1-0 01:00:07.354 [pool-1-thread-2] INFO com.controller.synchronize.SyncDecorate - test2-2-0 01:00:07.358 [pool-1-thread-1] INFO com.controller.synchronize.SyncDecorate - test2-1-1 01:00:07.358 [pool-1-thread-2] INFO com.controller.synchronize.SyncDecorate - test2-2-1 01:00:07.358 [pool-1-thread-1] INFO com.controller.synchronize.SyncDecorate - test2-1-2 01:00:07.358 [pool-1-thread-2] INFO com.controller.synchronize.SyncDecorate - test2-2-2 01:00:07.358 [pool-1-thread-1] INFO com.controller.synchronize.SyncDecorate - test2-1-3 01:00:07.358 [pool-1-thread-2] INFO com.controller.synchronize.SyncDecorate - test2-2-3 01:00:07.358 [pool-1-thread-1] INFO com.controller.synchronize.SyncDecorate - test2-1-4 01:00:07.358 [pool-1-thread-2] INFO com.controller.synchronize.SyncDecorate - test2-2-4 01:00:07.358 [pool-1-thread-1] INFO com.controller.synchronize.SyncDecorate - test2-1-5 01:00:07.358 [pool-1-thread-2] INFO com.controller.synchronize.SyncDecorate - test2-2-5 01:00:07.358 [pool-1-thread-1] INFO com.controller.synchronize.SyncDecorate - test2-1-6 01:00:07.358 [pool-1-thread-2] INFO com.controller.synchronize.SyncDecorate - test2-2-6 01:00:07.358 [pool-1-thread-1] INFO com.controller.synchronize.SyncDecorate - test2-1-7 01:00:07.358 [pool-1-thread-2] INFO com.controller.synchronize.SyncDecorate - test2-2-7 01:00:07.358 [pool-1-thread-1] INFO com.controller.synchronize.SyncDecorate - test2-1-8 01:00:07.358 [pool-1-thread-2] INFO com.controller.synchronize.SyncDecorate - test2-2-8 01:00:07.358 [pool-1-thread-1] INFO com.controller.synchronize.SyncDecorate - test2-1-9 01:00:07.358 [pool-1-thread-2] INFO com.controller.synchronize.SyncDecorate - test2-2-9
由此可见,无论是同步代码块还是同步方法,在面对多个线程通过不同对象调用同一个方法的时候,结果都是一个道理。
这里额外补充一点,如果父类中的方法被synchronized修饰,那么子类继承父类的时候是继承不走synchronized的,也就是说同步锁会失效,原因就是synchronized不属于方法声明的一部分。如果子类也想用synchronized,必须显式地在方法上声明synchronized才行。
以上是关于并发与高并发-线程安全性-原子性-synchronized的主要内容,如果未能解决你的问题,请参考以下文章