前言

闲暇时刻,谈一下曾经在多线程教程中接触的同步锁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. sql server 2012插入排序后的数据到临时表无效

    IF OBJECT_ID('TEMPDB..#list') IS NOT NULLBEGIN DROP TABLE TEMPDB.#list END CREATE TABLE #list(OFC_ID ...

  2. html5游戏的横屏问题

    html5 API有这个参数 Screen Orientation API 可以看w3c定义的规范 The Screen Orientation API <!-- UC强制竖屏 --> & ...

  3. xv6 锁

    在xv6 中锁对象是 spinlock,spinlock中的locked为1的时候表示被占用,为0的时候锁空闲. struct spinlock { uint locked; // Is the lo ...

  4. jQuery原理系列-Dom Ready

    ready事件是jquery的一个很重要的功能,在很久很久以前,我们是使用window.onload监听页面加载成功的,onload事件的好处是你不用考虑浏览器兼容性,也不需要依赖任何框架就可以写,但 ...

  5. C++ do while无限循环~

    #include<iostream> using namespace std; #include<Windows.h> int main() { ; ; system(&quo ...

  6. 超级简单 一分钟实现react-native屏幕适配

    今天因为react-native的style只能给width和height设置数字 没有react上的vw和vh 因为之前经常用vh vw 感觉不适应 找到了一个新的方法 使用Demension模块 ...

  7. 用Git管理项目进行版本控制

    一.安装 1.1windows 要在Windows系统中安装Git,请访问http://msysgit.github.io/,并单击Download.安装. 1.2 在 Linux 系统中安装 Git ...

  8. 三、ReactJS、jsx、 Component 特性

    reactjs特性: 基于组件(Component)化思考 用 JSX 进行声明式(Declarative)UI 设计 使用 Virtual DOM Component PropType 错误校对机制 ...

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

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

  10. 动态添加,删除class样式

    function hasClass(obj, cls) { //class位于单词边界,判断class样式是否已经存在 return obj.className.match(new RegExp('( ...