并发与高并发(八)-线程安全性-原子性-synchronized
前言
闲暇时刻,谈一下曾经在多线程教程中接触的同步锁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的更多相关文章
- 4-3 线程安全性-原子性-synchronized
原子性它提供了互斥访问,同一时刻只能有一个线程来对它进行操作.能保证同一时刻只有一个线程来对其进行操作的,除了Atomic包之外,还有锁.JDK提供锁主要分两种,synchronized是一个Java ...
- Java并发编程入门与高并发面试(三):线程安全性-原子性-CAS(CAS的ABA问题)
摘要:本文介绍线程的安全性,原子性,java.lang.Number包下的类与CAS操作,synchronized锁,和原子性操作各方法间的对比. 线程安全性 线程安全? 线程安全性? 原子性 Ato ...
- [ 高并发]Java高并发编程系列第二篇--线程同步
高并发,听起来高大上的一个词汇,在身处于互联网潮的社会大趋势下,高并发赋予了更多的传奇色彩.首先,我们可以看到很多招聘中,会提到有高并发项目者优先.高并发,意味着,你的前雇主,有很大的业务层面的需求, ...
- java高并发系列 - 第10天:线程安全和synchronized关键字
这是并发系列第10篇文章. 什么是线程安全? 当多个线程去访问同一个类(对象或方法)的时候,该类都能表现出正常的行为(与自己预想的结果一致),那我们就可以所这个类是线程安全的. 看一段代码: pack ...
- Java并发编程(一):并发与高并发等基础概念
并发概念 同时拥有两个或者多个线程,如果程序在单核处理器上运行,多个线程将交替地换入或者换出内存,这些线程是同时存在的,每个线程都处于执行过程中的某个状态.如果运行在多核处理器上,程序中的每个线程都将 ...
- 并发与高并发(七)-线程安全性-原子性-atomic
一.线程安全性定义 定义:当多个线程访问某个类时,不管运行时环境采用何种调度方式或者这些线程将如何交替执行,并且在主调代码中不需要任何额外的同步或协同,这个类都能表现出正确的行为,那么就称这个类是线程 ...
- Java并发(理论知识)—— 线程安全性
1.什么是线程安全性 当多个线 ...
- 线程安全性-原子性之Atomic包
先了解什么是线程安全性:当多个线程访问某个类时,不管运行时环境采用何种调度方式或者这些进程将如何交替执行,并且在主调代码中不需要任何额外的同步或协同,这个类都能表现出正确的行为,那么就称为这个类是线程 ...
- 4-1 线程安全性-原子性-atomic-1
我们发现在不做任何同步的情况下,我们计算的累加结果是错误的. com.mmall.concurrency.example.count.CountExample2 C:\Users\ZHONGZHENH ...
随机推荐
- POJ 3007:Organize Your Train part II
Organize Your Train part II Time Limit: 1000MS Memory Limit: 65536K Total Submissions: 7561 Acce ...
- 百度杀毒停止下载,个人PC杀毒软件真的走到尽头了吗?
在时代浪潮的席卷下,不适应的服务和产品终将被淘汰.诺基亚如此,杀毒软件亦是如此.就在近日,久久沉寂的杀毒软件市场被投下一颗重磅炸弹--百度杀毒软件官网PC端不再提供下载,但手机端依然提供下载和杀毒服务 ...
- python 输出99乘法表
for i in range(1,10): for j in range(1,i+1): print("%s*%s=%2s"%(i,j,i*j),end=" " ...
- 解决:Server IPC version 9 cannot communicate with client version 4
使用idea的maven项目运行mapreduce程序Server IPC version 9 cannot communicate with client version 4 原因: Java初始化 ...
- (转)Navicat Premium 连接Oracle 数据库(图文教程)
Navicat premium是一款数据库管理工具,是一个可多重连线资料库的管理工具,它可以让你以单一程式同时连线到MySQL.SQLite.Oracle及PostgreSQL 资料库,让管理不同类型 ...
- 《动手学深度学习》系列笔记—— 1.2 Softmax回归与分类模型
目录 softmax的基本概念 交叉熵损失函数 模型训练和预测 获取Fashion-MNIST训练集和读取数据 get dataset softmax从零开始的实现 获取训练集数据和测试集数据 模型参 ...
- 配置mysql时报错
配置mysql时无法启动此程序,因为计算机丢失MSVCR100.dll. 去https://cn.dll-files.com/下载相应的版本 复制MSVCR100.dll 粘贴到下面 32位系统: 复 ...
- Python MySQL 教程
章节 Python MySQL 入门 Python MySQL 创建数据库 Python MySQL 创建表 Python MySQL 插入表 Python MySQL Select Python M ...
- webpack随笔2--编译ES6/ES7
一.Babel 1.安装babel Bable-loader: babeljs.io babel最新版:npm install babel-loader@8.0.0-beta.0 @babel/cor ...
- P1303 A*B Problem(高精度乘法)
P1303 A*B Problem 模拟就好了.\(c_ {i+j} +=a_i \times b_j\).时间复杂度 \(O(n*m)\) (FFT版可以做到 \(O((n+m)\log (n+m) ...