并发与高并发-线程安全性-原子性-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的主要内容,如果未能解决你的问题,请参考以下文章

Java并发编程与高并发解决方案 视频教程

Java并发- synchronized与CAS

java核心-多线程- 并发原子类

Java 并发编程线程操作原子性问题 ( 问题业务场景分析 | 使用 synchronized 解决线程原子性问题 )

java 程序中怎么保证多线程的运行安全?

并发编程CAS与synchronized