并发与高并发(八)-线程安全性-原子性-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才行。

原文地址:https://www.cnblogs.com/jmy520/p/12041592.html

时间: 2024-10-09 06:07:57

并发与高并发(八)-线程安全性-原子性-synchronized的相关文章

Java并发(理论知识)—— 线程安全性

1.什么是线程安全性 当多个线程访问某个类时,不管运行时环境采用何种调度方式或者这些进程将如何交替执行,并且在主调代码中不需要任何额外的同步或协同,这个类都能表现出正确的行为,那么就称这个类是线程安全的.      在线程安全类中封装了必要的同步机制,因此客户端无需进一步采取同步错失. 2.原子性 要编写线程安全的代码,其核心在于要对状态访问操作进行管理,特别是对共享的和可变的状态的访问.当多个线程访问某个状态变量,并且其中有一个线程执行写入操作时,必须采用同步机制来协调这些线程对变量的访问.无

【高并发】高并发环境下诡异的加锁问题(你加的锁未必安全)

声明 特此声明:文中有关支付宝账户的说明,只是用来举例,实际支付宝账户要比文中描述的复杂的多.也与文中描述的完全不同. 前言 很多网友留言说:在编写多线程并发程序时,我明明对共享资源加锁了啊?为什么还是出问题呢?问题到底出在哪里呢?其实,我想说的是:你的加锁姿势正确吗?你真的会使用锁吗?错误的加锁方式不但不能解决并发问题,而且还会带来各种诡异的Bug问题,有时难以复现! 在上一篇<[高并发]如何使用互斥锁解决多线程的原子性问题?这次终于明白了!>一文中,我们知道在并发编程中,不能使用多把锁保护

并发、高并发、集群的含义?

并发:多个用户同时向服务器发出请求,服务器会开启多个线程,每个线程服务一个用户 高并发:当用户量达到成千上万,会导致服务器资源不再充足,最终使服务器内存溢出 集群:可以解决高并发问题  集群将一个应用拷贝多份,搭在多台服务器(机器),多台服务器构成一个集群 原文地址:https://www.cnblogs.com/codeXi/p/11429871.html

并发与高并发-线程安全性-可见性

前言 乍看可见性,不明白它的意思.联想到线程,意思就是一个线程对主内存的修改及时的被另一个线程观察到,即为可见性. 那么既然有可见性,会不会存在不可见性呢? 答案是肯定的,导致线程不可见的原因是什么呢? 有三个原因: (1)线程交叉执行. (2)重排序结合线程交叉执行. (3)共享变量更新后的值没有在工作内存与主存间及时更新. 主体内容 一.这里的可见性涉及到synchronized,顺便了解一些一下JMM对synchronized的两条规定: 1.线程解锁前,必须把共享变量的最新值刷新到主内存

[高并发]Java高并发编程系列开山篇--线程实现

ava是最早开始有并发的语言之一,再过去传统多任务的模式下,人们发现很难解决一些更为复杂的问题,这个时候我们就有了并发. 引用 多线程比多任务更加有挑战.多线程是在同一个程序内部并行执行,因此会对相同的内存空间进行并发读写操作.这可能是在单线程程序中从来不会遇到的问题.其中的一些错误也未必会在单CPU机器上出现,因为两个线程从来不会得到真正的并行执行.然而,更现代的计算机伴随着多核CPU的出现,也就意味着不同的线程能被不同的CPU核得到真正意义的并行执行. 那么,要开始Java并发之路,就要开始

Java并发学习之十八——线程同步工具之CyclicBarrier

本文是学习网络上的文章时的总结,感谢大家无私的分享. CyclicBarrier 类有一个整数初始值,此值表示将在同一点同步的线程数量.当其中一个线程到达确定点,它会调用await() 方法来等待其他线程.当线程调用这个方法,CyclicBarrier阻塞线程进入休眠直到其他线程到达.当最后一个线程调用CyclicBarrier 类的await() 方法,它唤醒所有等待的线程并继续执行它们的任务. 注意比较CountDownLatch和CyclicBarrier: 1.CountDownLatc

Java并发和高并发学习总结(四)- J.U.C之工具类

1.总览 CountDownLatch允许一个或多个线程等待某些操作完成 Semaphore Java版本的信号量实现 CyclicBarrier 一种辅助性的同步结构,允许多个线程等待到达某个屏障 Exchanger 在线程间交换数据的一种手段 2.CountDownLatch 当一个或多个线程需要等待其他线程完成操作时,就可以使用CountDownLatch了,当然,最简单的你也可以使用join方法 2.1.join public class JoinTest { public static

并发与高并发-并发模拟代码

一.CountDownLatch 1.背景: (1)countDownLatch是在java1.5被引入,跟它一起被引入的工具类还有CyclicBarrier.Semaphore.concurrentHashMap和BlockingQueue. (2)存在于java.util.cucurrent包下. 2.概念 (1)countDownLatch这个类使一个线程等待其他线程各自执行完毕后再执行. (2)是通过一个计数器来实现的,计数器的初始值是线程的数量.每当一个线程执行完毕后,计数器的值就-1

并发与高并发(十三)J.U.C之AQS

前言 什么是AQS,是AbstractQueuedSynchronizer类的简称.J.U.C大大提高了并发的性能,而AQS又是J.U.S的核心. 主体概要 J.U.C之AQS介绍 J.U.C之AQS-CountDownLatch J.U.C之AQS-Semaphore J.U.C之AQS-CyclicBarrier J.U.C之AQS-ReentrantLock与锁 主体内容 总结 原文地址:https://www.cnblogs.com/jmy520/p/12337891.html