前言

闲暇时刻,谈一下曾经在多线程教程中接触的同步锁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. Gym 101158D(暴力)

    题意:给定两个长度为N的字符串,1<=N<=4000,求满足字符串1中的某个区间所有的字母种类和个数都与字符串2中的某个区间相同最长的区间长度. 分析: 1.预处理每个串字母个数的前缀和. ...

  2. MongoDB 删除,添加副本集,并修改副本集IP等信息

    MongoDB 删除,添加副本集,并修改副本集IP等信息 添加副本,在登录到主节点下输入 rs.add("ip:port"); 删除副本 rs.remove("ip:po ...

  3. CentOs 后台运行jar

    1.启动jar包,后台运行 nohup java -jar xxxx.jar & 2.结束运行 查出正在运行的进程 ps -ef | grep java 杀掉进程 kill pid 上面橙色字 ...

  4. ES6 之 Reflect 的方法总结

    1. 概述 将 Object 对象的一些明显属于语言内部的方法(比如 Object.defineProperty ),放到 Reflect 对象上. 修改某些 Object 方法的返回结果,让其变得更 ...

  5. 吴裕雄 Bootstrap 前端框架开发——Bootstrap 字体图标(Glyphicons):glyphicon glyphicon-share

    <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <meta name ...

  6. wget 403 forbidden

    CMD: wget --user-agent="Mozilla" down_url wget -U Mozilla 下载地址 wget -U NoSuchBrowser/1.0 下 ...

  7. UVA - 1612 Guess (猜名次)(贪心)

    题意:有n(n<=16384)位选手参加编程比赛.比赛有3道题目,每个选手的每道题目都有一个评测之前的预得分(这个分数和选手提交程序的时间相关,提交得越早,预得分越大).接下来是系统测试.如果某 ...

  8. 自学Java第五章——《面向对象的基本特征》

    面向对象的基本特征: 1.封装 2.继承 3.多态 6.1 封装 1.好处: (1)隐藏实现细节,方便使用者使用 (2)安全,可以控制可见范围 2.如何实现封装? 通过权限修饰符 面试题:请按照可见范 ...

  9. 浅谈ASCII 、ISO8859-1、GB2312、GBK、Unicode、UTF-8 的区别。

    浅谈ASCII .ISO8859-1.GB2312.GBK.Unicode.UTF-8 的区别. 首先,先科普一下什么是字符编码.字符是指一种语言中使用的基本实体,比如英文中的26个英文字母,标点符号 ...

  10. 吴裕雄--天生自然Django框架开发笔记:Django 安装

    Window 下安装 Django 如果你还未安装Python环境需要先下载Python安装包. 1.Python 下载地址:https://www.python.org/downloads/ 2.D ...