前言

闲暇时刻,谈一下曾经在多线程教程中接触的同步锁synchronized,相当于复习一遍吧。

主要介绍

synchronized:依赖JVM

Lock:依赖特殊的CPU指令,代码实现,ReetrantLock

主体内容

一、那么我们主要先讲解一下关于同步锁synchronized的作用范围。

1.修饰代码块:作用范围-大括号括起来的代码,作用于调用这个代码块的对象,如果不同对象调用该代码块就不会同步。

2.修饰方法:作用范围-整个方法,作用于调用这个方法的对象

3.修饰静态方法:作用范围-整个静态方法,作用于这个类的所有对象

4.修饰类:作用范围-synchronized后面括号括起来的部分,作用于这个类的所有对象(ps:两个线程调用同一个类的不同对象上的这种同步语句,也会进行同步

二、接下来,我们分别针对synchronized修饰的这四种情况写四个例子,顺便对以上的4句话作一个深入理解。

1.synchronized修饰代码块

(1)同一对象调用test

1.首先,写一个方法,让synchronized修饰代码块

@Slf4j
public class SynchronizedExample1 { public void test(String j) {
//代码块
synchronized(this) {
for(int i=;i<;i++) {
log.info("test-{}-{}",i,j);
}
}
}
public static void main(String[] args) {
//同一对象
SynchronizedExample1 se1 = new SynchronizedExample1();
ExecutorService executorService = Executors.newCachedThreadPool();
executorService.execute(()->{
se1.test("线程1");
});
executorService.execute(()->{
se1.test("线程2");
});
}
}

解释:这里我们用线程池创建了两个线程分别访问test1方法中的同步代码块,第二个线程其实不等第一个线程执行完毕,就开始去访问test1方法,但test1方法中的代码块由于第一个线程的访问上了锁,所以第二个线程不得不等待第一个线程执行完这个方法。因此执行结果为如下:

 INFO [pool--thread-] - test--线程1
INFO [pool--thread-] - test--线程1
INFO [pool--thread-] - test--线程1
INFO [pool--thread-] - test--线程1
INFO [pool--thread-] - test--线程1
INFO [pool--thread-] - test--线程1
INFO [pool--thread-] - test--线程1
INFO [pool--thread-] - test--线程1
INFO [pool--thread-] - test--线程1
INFO [pool--thread-] - test--线程1
INFO [pool--thread-] - test--线程2
INFO [pool--thread-] - test--线程2
INFO [pool--thread-] - test--线程2
INFO [pool--thread-] - test--线程2
INFO [pool--thread-] - test--线程2
INFO [pool--thread-] - test--线程2
INFO [pool--thread-] - test--线程2
INFO [pool--thread-] - test--线程2
INFO [pool--thread-] - test--线程2
INFO [pool--thread-] - test--线程2

(2)不同对象调用test

@Slf4j
public class SynchronizedExample2 { public void test(String j) {
//代码块
synchronized(this) {
for(int i=;i<;i++) {
log.info("test-{}-{}",i,j);
}
}
}
public static void main(String[] args) {
//不同对象
SynchronizedExample2 se1 = new SynchronizedExample2();
SynchronizedExample2 se2 = new SynchronizedExample2();
ExecutorService executorService = Executors.newCachedThreadPool();
executorService.execute(()->{
se1.test("线程1");
});
executorService.execute(()->{
se2.test("线程2");
});
}
}

结果我们发现,线程一和线程二都是各自随着for循环升序,互相交叉但却没有影响。这种现象就证明了同步代码块对于当前对象,不同的调用之间是互相不影响的:

 INFO [pool--thread-] - test--线程2
INFO [pool--thread-] - test--线程1
INFO [pool--thread-] - test--线程1
INFO [pool--thread-] - test--线程2
INFO [pool--thread-] - test--线程1
INFO [pool--thread-] - test--线程2
INFO [pool--thread-] - test--线程1
INFO [pool--thread-] - test--线程2
INFO [pool--thread-] - test--线程1
INFO [pool--thread-] - test--线程2
INFO [pool--thread-] - test--线程1
INFO [pool--thread-] - test--线程2
INFO [pool--thread-] - test--线程1
INFO [pool--thread-] - test--线程2
INFO [pool--thread-] - test--线程1
INFO [pool--thread-] - test--线程2
INFO [pool--thread-] - test--线程1
INFO [pool--thread-] - test--线程2
INFO [pool--thread-] - test--线程1
INFO [pool--thread-] - test--线程2

2.接下来,我写一段让synchronized修饰方法的代码。

(1)同一对象调用test

@Slf4j
public class SynchronizedExample3 {
//synchronized修饰方法
public synchronized void test(String j) {
for(int i=;i<;i++) {
log.info("test-{}-{}",i,j);
}
}
public static void main(String[] args) {
//同一对象
SynchronizedExample3 se1 = new SynchronizedExample3();
ExecutorService executorService = Executors.newCachedThreadPool();
executorService.execute(()->{
se1.test("线程1");
});
executorService.execute(()->{
se1.test("线程2");
});
}
}

结果为:

 INFO [pool--thread-] - test--线程1
INFO [pool--thread-] - test--线程1
INFO [pool--thread-] - test--线程1
INFO [pool--thread-] - test--线程1
INFO [pool--thread-] - test--线程1
INFO [pool--thread-] - test--线程1
INFO [pool--thread-] - test--线程1
INFO [pool--thread-] - test--线程1
INFO [pool--thread-] - test--线程1
INFO [pool--thread-] - test--线程1
INFO [pool--thread-] - test--线程2
INFO [pool--thread-] - test--线程2
INFO [pool--thread-] - test--线程2
INFO [pool--thread-] - test--线程2
INFO [pool--thread-] - test--线程2
INFO [pool--thread-] - test--线程2
INFO [pool--thread-] - test--线程2
INFO [pool--thread-] - test--线程2
INFO [pool--thread-] - test--线程2
INFO [pool--thread-] - test--线程2

(2)不同对象调用test

@Slf4j
public class SynchronizedExample4 {
//synchronized修饰方法
public synchronized void test(String j) {
for(int i=;i<;i++) {
log.info("test-{}-{}",i,j);
}
}
public static void main(String[] args) {
//不同对象
SynchronizedExample4 se1 = new SynchronizedExample4();
SynchronizedExample4 se2 = new SynchronizedExample4();
ExecutorService executorService = Executors.newCachedThreadPool();
executorService.execute(()->{
se1.test("线程1");
});
executorService.execute(()->{
se2.test("线程2");
});
}
}

结果:

 INFO [pool--thread-] - test--线程1
INFO [pool--thread-] - test--线程1
INFO [pool--thread-] - test--线程1
INFO [pool--thread-] - test--线程1
INFO [pool--thread-] - test--线程2
INFO [pool--thread-] - test--线程2
INFO [pool--thread-] - test--线程2
INFO [pool--thread-] - test--线程1
INFO [pool--thread-] - test--线程2
INFO [pool--thread-] - test--线程1
INFO [pool--thread-] - test--线程2
INFO [pool--thread-] - test--线程1
INFO [pool--thread-] - test--线程1
INFO [pool--thread-] - test--线程2
INFO [pool--thread-] - test--线程1
INFO [pool--thread-] - test--线程2
INFO [pool--thread-] - test--线程1
INFO [pool--thread-] - test--线程2
INFO [pool--thread-] - test--线程2
INFO [pool--thread-] - test--线程2

由此可见,修饰代码块和修饰方法的两类结果相似。

这里额外补充一点,如果父类中的方法被synchronized修饰,那么子类继承父类的时候是继承不走synchronized的,也就是说同步锁会失效,原因就是synchronized不属于方法声明的一部分。如果子类也想用synchronized,必须显式地在方法上声明synchronized才行。

3.接下来,我们用上面同样的方法测试一下被synchronized修饰的静态方法在两个线程通过两个对象的调用下的结果。

(1)同一对象调用test

@Slf4j
public class SynchronizedExample5 {
//synchronized修饰静态方法
public static synchronized void test(String j) {
for(int i=;i<;i++) {
log.info("test-{}-{}",i,j);
}
}
public static void main(String[] args) {
//同一对象
SynchronizedExample5 se1 = new SynchronizedExample5();
ExecutorService executorService = Executors.newCachedThreadPool();
executorService.execute(()->{
se1.test("线程1");
});
executorService.execute(()->{
se1.test("线程2");
});
}
}

结果:

 INFO [pool--thread-] - test--线程1
INFO [pool--thread-] - test--线程1
INFO [pool--thread-] - test--线程1
INFO [pool--thread-] - test--线程1
INFO [pool--thread-] - test--线程1
INFO [pool--thread-] - test--线程1
INFO [pool--thread-] - test--线程1
INFO [pool--thread-] - test--线程1
INFO [pool--thread-] - test--线程1
INFO [pool--thread-] - test--线程1
INFO [pool--thread-] - test--线程2
INFO [pool--thread-] - test--线程2
INFO [pool--thread-] - test--线程2
INFO [pool--thread-] - test--线程2
INFO [pool--thread-] - test--线程2
INFO [pool--thread-] - test--线程2
INFO [pool--thread-] - test--线程2
INFO [pool--thread-] - test--线程2
INFO [pool--thread-] - test--线程2
INFO [pool--thread-] - test--线程2

(2)不同对象调用test

@Slf4j
public class SynchronizedExample6 {
//synchronized修饰静态方法
public static synchronized void test(String j) {
for(int i=;i<;i++) {
log.info("test-{}-{}",i,j);
}
}
public static void main(String[] args) {
//不同对象
SynchronizedExample6 se1 = new SynchronizedExample6();
SynchronizedExample6 se2 = new SynchronizedExample6();
ExecutorService executorService = Executors.newCachedThreadPool();
executorService.execute(()->{
se1.test("线程1");
});
executorService.execute(()->{
se2.test("线程2");
});
}
}

结果:

 INFO [pool--thread-] - test--线程1
INFO [pool--thread-] - test--线程1
INFO [pool--thread-] - test--线程1
INFO [pool--thread-] - test--线程1
INFO [pool--thread-] - test--线程1
INFO [pool--thread-] - test--线程1
INFO [pool--thread-] - test--线程1
INFO [pool--thread-] - test--线程1
INFO [pool--thread-] - test--线程1
INFO [pool--thread-] - test--线程1
INFO [pool--thread-] - test--线程2
INFO [pool--thread-] - test--线程2
INFO [pool--thread-] - test--线程2
INFO [pool--thread-] - test--线程2
INFO [pool--thread-] - test--线程2
INFO [pool--thread-] - test--线程2
INFO [pool--thread-] - test--线程2
INFO [pool--thread-] - test--线程2
INFO [pool--thread-] - test--线程2
INFO [pool--thread-] - test--线程2

发现当多个线程通过多个对象调用的时候,第二个线程是等待第一个线程执行完毕才执行。这说明什么?说明当synchronized修饰静态方法的时候作用范围于这个类的所有对象,这就是它与众不同的地方。

6.不由分说,我立马执行一下synchronized修饰类的代码,看看结果又如何?

    /**
* 修饰一个类
*/
public static void test1(int j){
synchronized (SyncDecorate2.class) {
for(int i=;i<;i++){
log.info("test1-{}-{}",j,i);
}
}
} public static void main(String[] args){
//声明两个类对象,让两个线程通过两个对象分别调用各自的test1方法
SyncDecorate2 sd1 = new SyncDecorate2();
SyncDecorate2 sd2 = new SyncDecorate2();
ExecutorService executorService = Executors.newCachedThreadPool();
executorService.execute(()->{
sd1.test1();
});
executorService.execute(()->{
sd2.test1();
});
}

结果:

::40.244 [pool--thread-] INFO com.controller.synchronize.SyncDecorate2 - test1--
::40.247 [pool--thread-] INFO com.controller.synchronize.SyncDecorate2 - test1--
::40.247 [pool--thread-] INFO com.controller.synchronize.SyncDecorate2 - test1--
::40.247 [pool--thread-] INFO com.controller.synchronize.SyncDecorate2 - test1--
::40.248 [pool--thread-] INFO com.controller.synchronize.SyncDecorate2 - test1--
::40.248 [pool--thread-] INFO com.controller.synchronize.SyncDecorate2 - test1--
::40.248 [pool--thread-] INFO com.controller.synchronize.SyncDecorate2 - test1--
::40.248 [pool--thread-] INFO com.controller.synchronize.SyncDecorate2 - test1--
::40.248 [pool--thread-] INFO com.controller.synchronize.SyncDecorate2 - test1--
::40.248 [pool--thread-] INFO com.controller.synchronize.SyncDecorate2 - test1--
::40.248 [pool--thread-] INFO com.controller.synchronize.SyncDecorate2 - test1--
::40.248 [pool--thread-] INFO com.controller.synchronize.SyncDecorate2 - test1--
::40.248 [pool--thread-] INFO com.controller.synchronize.SyncDecorate2 - test1--
::40.248 [pool--thread-] INFO com.controller.synchronize.SyncDecorate2 - test1--
::40.248 [pool--thread-] INFO com.controller.synchronize.SyncDecorate2 - test1--
::40.248 [pool--thread-] INFO com.controller.synchronize.SyncDecorate2 - test1--
::40.248 [pool--thread-] INFO com.controller.synchronize.SyncDecorate2 - test1--
::40.248 [pool--thread-] INFO com.controller.synchronize.SyncDecorate2 - test1--
::40.248 [pool--thread-] INFO com.controller.synchronize.SyncDecorate2 - test1--
::40.248 [pool--thread-] INFO com.controller.synchronize.SyncDecorate2 - test1--

可见,修饰类的时候和修饰静态方法得到的结果是同一个道理,并没有交叉执行,而是第二个线程等待第一个执行完毕才执行。

总结

1.当synchronized修饰代码块和方法的时候,通过一个对象调用发现是线程二等待线程一执行完,锁就起了作用。但是一旦两个线程通过不同对象分别调用修饰代码块的方法和修饰方法时,出现了交叉执行的现象,代码块或方法并没有同步。这就证明了synchronized修饰代码块,修饰方法的时候作用于调用这个方法的对象。

2.当synchronized修饰静态方法和修饰类的时候,多个线程通过多个对象调用其静态方法或修饰类的时候,线程二会等待线程一执行完才执行,锁也起了作用。这就证明synchronized修饰静态方法和修饰类的时候,修饰作用于这个类的所有对象。

再回头看看那四句话,

修饰代码块:作用范围-大括号括起来的代码,作用于调用这个代码块的对象,如果不同对象调用该代码块就不会同步。

修饰方法:作用范围-整个方法,作用于调用这个方法的对象

修饰静态方法:作用范围-整个静态方法,作用于这个类的所有对象

修饰类:作用范围-synchronized后面括号括起来的部分,作用于这个类的所有对象(ps:两个线程调用同一个类的不同对象上的这种同步语句,也会进行同步

是不是有眉目了呢?

应用

以下是一个线程不安全的模拟代码。

@Slf4j
@NotThreadSafe
public class ConcurrencyTest {
//请求数
public static int clientTotal=;
//并发数
public static int threadTotal=;
//计数值
public static int count=; public static void main(String[] args) throws InterruptedException{
//创建线程池
ExecutorService executorService = Executors.newCachedThreadPool();
//定义信号量(允许并发数)
final Semaphore semaphore = new Semaphore(threadTotal);
//定义计数器
final CountDownLatch countDownLatch = new CountDownLatch(clientTotal);
for(int i =;i<clientTotal;i++){
executorService.execute(()->{
try {
//.acquire方法用于判断是否内部程序达到允许的并发量,未达到才能继续执行
semaphore.acquire();
add();
//.release相当于关闭信号量
semaphore.release();
} catch (Exception e) {
log.error("exception",e);
}
countDownLatch.countDown();
});
}
//等待计数值为0,也就是所有的过程执行完,才会继续向下执行
countDownLatch.await();
//关闭线程池
executorService.shutdown();
log.info("count:{}",count);
} private static void add(){
count++;
}
}

结果应该为5000才会没有问题,但数次执行,有几次达不到5000的标准。

::50.321 [main] INFO com.controller.ConcurrencyTest - count:

当我们在调用的静态方法前面加上synchronized,那么就变为线程安全的了。

private synchronized static void add(){
count++;
}

结果:

::37.778 [main] INFO com.controller.ConcurrencyTest - count:

以上就是synchronized的修饰作用讲解,如有错误,请指出。

对比

synchronized:不可中断锁,适合竞争不激烈,可读性好

lock:可中断锁,多样化同步,竞争激烈时能维持常态

Atomic:竞争激烈时能维持常态,比lock性能好;只能同步一个值

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

  1. 4-3 线程安全性-原子性-synchronized

    原子性它提供了互斥访问,同一时刻只能有一个线程来对它进行操作.能保证同一时刻只有一个线程来对其进行操作的,除了Atomic包之外,还有锁.JDK提供锁主要分两种,synchronized是一个Java ...

  2. Java并发编程入门与高并发面试(三):线程安全性-原子性-CAS(CAS的ABA问题)

    摘要:本文介绍线程的安全性,原子性,java.lang.Number包下的类与CAS操作,synchronized锁,和原子性操作各方法间的对比. 线程安全性 线程安全? 线程安全性? 原子性 Ato ...

  3. [ 高并发]Java高并发编程系列第二篇--线程同步

    高并发,听起来高大上的一个词汇,在身处于互联网潮的社会大趋势下,高并发赋予了更多的传奇色彩.首先,我们可以看到很多招聘中,会提到有高并发项目者优先.高并发,意味着,你的前雇主,有很大的业务层面的需求, ...

  4. java高并发系列 - 第10天:线程安全和synchronized关键字

    这是并发系列第10篇文章. 什么是线程安全? 当多个线程去访问同一个类(对象或方法)的时候,该类都能表现出正常的行为(与自己预想的结果一致),那我们就可以所这个类是线程安全的. 看一段代码: pack ...

  5. Java并发编程(一):并发与高并发等基础概念

    并发概念 同时拥有两个或者多个线程,如果程序在单核处理器上运行,多个线程将交替地换入或者换出内存,这些线程是同时存在的,每个线程都处于执行过程中的某个状态.如果运行在多核处理器上,程序中的每个线程都将 ...

  6. 并发与高并发(七)-线程安全性-原子性-atomic

    一.线程安全性定义 定义:当多个线程访问某个类时,不管运行时环境采用何种调度方式或者这些线程将如何交替执行,并且在主调代码中不需要任何额外的同步或协同,这个类都能表现出正确的行为,那么就称这个类是线程 ...

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

    1.什么是线程安全性                                                                                      当多个线 ...

  8. 线程安全性-原子性之Atomic包

    先了解什么是线程安全性:当多个线程访问某个类时,不管运行时环境采用何种调度方式或者这些进程将如何交替执行,并且在主调代码中不需要任何额外的同步或协同,这个类都能表现出正确的行为,那么就称为这个类是线程 ...

  9. 4-1 线程安全性-原子性-atomic-1

    我们发现在不做任何同步的情况下,我们计算的累加结果是错误的. com.mmall.concurrency.example.count.CountExample2 C:\Users\ZHONGZHENH ...

随机推荐

  1. Golang的运算符-赋值运算符

    Golang的运算符-赋值运算符 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 一.赋值运算符概述 常见的赋值运算符: =: 表示赋值运算符,如"a = 100" ...

  2. 在 CentOS 中部署 KMS 服务器(vlmcsd)

    准备 vlmcsd 下载 vlmcsd 本文使用的 vlmcsd 版本为 svn1111,支持的产品: Windows Vista – 10Windows Server 2008 - 2016Offi ...

  3. 指令——ps -ef

    一个完整的指令的标准格式: Linux通用的格式——#指令主体(空格) [选项](空格) [操作对象] 一个指令可以包含多个选项,操作对象也可以是多个. 指令:ps [process  show] 作 ...

  4. 【LeetCode】重新安排行程

    [问题]给定一个机票的字符串二维数组 [from, to],子数组中的两个成员分别表示飞机出发和降落的机场地点,对该行程进行重新规划排序.所有这些机票都属于一个从JFK(肯尼迪国际机场)出发的先生,所 ...

  5. TRUNC()函数——oracle

    使用trunc()函数获取不同的日期: select trunc(sysdate) from dual; --今天的日期 select trunc(sysdate,'dd') from dual; - ...

  6. hdu 1160 上升序列 dp

    FatMouse's Speed Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 65536/32768 K (Java/Others) ...

  7. (二)requests模块

    一 requests模块 概念: python中原生的基于网络请求的模块,模拟浏览器进行请求发送,获取页面数据 安装: pip install requests 二 requests使用的步骤 1 指 ...

  8. 使用kali中的Metasploit通过windows7的永恒之蓝漏洞攻击并控制win7系统(9.27 第十三天)

    1.开启postgresql数据库 2.msfconsole 进入MSF中 3.search 17-010 搜索cve17-010相关的exp auxiliary/scanner/smb/smb_ms ...

  9. CGridCtrl 添加button (CGridCellButton类)

    #ifndef __GRID_CELL_BUTTON__ #define __GRID_CELL_BUTTON__ #include "../GridCtrl_src/GridCell.h& ...

  10. cron 表达式0 0/10 * * * 与 0 */10 * * *的区别

    0 0/10 * * * 与 0 */10 * * * 的差别在于什么地方.在说这两者的差别之前,先说下各个字符代表的含义.0代表从0分开始,*代表任意字符,/代表递增. 0 0/10 * * *代表 ...