原子性它提供了互斥访问,同一时刻只能有一个线程来对它进行操作。能保证同一时刻只有一个线程来对其进行操作的,除了Atomic包之外,还有锁。JDK提供锁主要分两种,synchronized是一个Java的关键字,主要是依赖JVM去实现锁,因此在这个关键字作用对象的作用范围内,都是同一时刻只能有一个线程可以进行操作的。记住是作用对象的作用范围内。

另外一种锁是JDK提供的代码层面的锁。JDK里面提供了一个叫做Lock的接口类,它主要是依赖特殊的CPU指令,实现类里面比较有代表性的是ReentrantLock。

关于Lock这个接口类的实现类,我们会在后面的课程里面单独重点地来讲,这里我们重点讲解一下synchronized这个关键字的使用。


synchronized是Java中的一个关键字,它是一种同步锁,它修饰的对象主要有四种:

第一种是修饰一个代码块,被修饰的代码称作为同步语句块,它作用的范围是大括号括起来的代码,它作用对象是调用这个代码块的对象。

修饰对象的第二种是修饰一个方法,被修饰的方法称为同步方法,它作用的范围是整个方法,作用的对象也是调用这个方法的对象。

修饰对象的第三种是修饰一个静态的方法,它作用的范围是整个静态方法。这个时候作用的对象是这个类的所有对象。

修饰对象的第四种是修饰一个类。这个时候作用的范围是synchronized后面括号括起来的部分,作用对象也是这个类的所有对象。

如果我们这里不使用线程池的话,这一个类对象两次调用了同一个方法,它们肯定本身就是同步执行的,因此我们是没法验证它们的具体的影响的。而我们加上线程池之后呢,它相当于是分别启动了两个进程去执行,然后它相当于是这个方法执行完了之后,不等这个方法执行完,立马又继续调用了一次这个方法。正好我们才能看到一个对象的两个进程同时来调用这个代码的时候它的执行情况。因此呢这里面我们就是通过了线程池以及两次调用的方式来模拟了同一个调用对象同时来调用这个方法,准确来说是这个同步代码块的执行情况。

这里对于一个修饰方法的时候,它们也是作用于当前对象的。

刚才正向的验证我们验证完了,接下来我们换不同的对象来让它乱序输出。刚才我们讲的修饰一个代码块的时候,它作用的对象是调用的对象,因此如果我们使用两个不同的对象调用同步代码块的时候,它俩互相不影响的。这里面如果我们使用线程池的话,理论上example1的test1方法的执行跟example2的test1方法它们是互相交叉执行的,而不是那种example1的test1方法执行完之后再执行example2的test1。

这个现象它就证明了对于同步代码块,它作用的是当前对象,不同调用对象之间是互相不影响的。


紧接着我们来测试一下修饰一个方法。这次的结果应该跟我们刚才演示的调用修饰一个代码块应该是很相似的,因为线程之间谁先启动谁后启动它俩是根据CPU自己来决定的,不是我们完全能控制的。example1和example2它们是交替进行执行的,这代表对于修饰一个方法也是作用于调用对象的,不同的调用对象之间是互相不影响的。

刚才我们演示了这么多,我们其实还可以额外总结出来,如果一个方法内部是一个完整的同步代码块,那么它和用synchronized的修饰的一个方法它俩是等同的,因为整个实际中需要执行的代码都是被synchronized修饰的,这个我们是可以通过我们的现象来总结出来的。大家就在理解的时候可以按照这个多会去理解,当一个方法里面整个都是一个同步代码块的时候,它跟修饰的一个方法它俩是一样的。同时呢我们额外指出一点是,如果当前这个类是个父类,如果子类继承了这个类之后呢,如果它想调用它test2的时候,它这里面是带不上synchronized的这个关键字的,这个大家一定要清楚。如果这个类是个父类,子类在继承了这个类的时候,如果调用test2,它是不包含synchronized的。原因呢是因为synchronized它不属于方法声明的一部分,这里需要大家注意一下。如果子类也想使用synchronized的话,那么它需要自己显示的在方法上面声明synchronized的才可以。修饰的代码块和修饰一个方法介绍完了,接下来介绍修饰静态方法和修饰一个类。

com.mmall.concurrency.example.sync.SynchronizedExample1

C:\Users\ZHONGZHENHUA\imooc\concurrency\src\main\java\com\mmall\concurrency\example\sync\SynchronizedExample1.java

package com.mmall.concurrency.example.sync;

import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors; @Slf4j
public class SynchronizedExample1 { // 修饰一个代码块
//public void test1() {
public void test1(int j) {
synchronized (this){
for (int i = 0; i < 10; i++) {
//log.info("test1 - {}", i);
log.info("test1 {} - {}", j, i);
}
}
} // 修饰一个方法
//public synchronized void test2() {
public synchronized void test2(int j) {
for (int i = 0; i < 10; i++) {
//log.info("test2 - {}", i);
log.info("test2 {} - {}", j, i);
}
}
public static void main(String args[]) {
SynchronizedExample1 example1 = new SynchronizedExample1();//声明这个类的实例
SynchronizedExample1 example2 = new SynchronizedExample1();//
ExecutorService executorService = Executors.newCachedThreadPool();//声明一个线程池
//开启一个进程去执行这个方法
executorService.execute(()->{
//example1.test1();
//example1.test2();
//example1.test1(1);
example1.test2(1);
});
executorService.execute(()->{
//example1.test1();
//example1.test2();
//example2.test1(2);
example2.test2(2);
});
}
}

修饰一个静态方法它的作用范围是synchronized后面括起来的部分,其实就是当前这一部分。

        for (int i = 0; i < 10; i++) {
//log.info("test2 - {}", i);
log.info("test2 {} - {}", j, i);
}

然后作用的对象是这个类的所有对象,因此我们使用不同的类,它们在调用被synchronized修饰的静态方法时,同一个时间只有一个线程可以执行,因此对于当前的keys,它的运行结果我们预期是test2,然后是1,后面0-9,接下来是test2,2,0-9。

这是修饰一个静态方法的验证,接下来我们来看修饰一个类。

一个方法里面,如果它所有需要执行代码部分,都是被synchronized修饰的一个类来包围的时候,那么它和synchronized的修饰的一个静态方法它的表现是一致的。

com.mmall.concurrency.example.sync.SynchronizedExample2

C:\Users\ZHONGZHENHUA\imooc\concurrency\src\main\java\com\mmall\concurrency\example\sync\SynchronizedExample2.java

package com.mmall.concurrency.example.sync;

import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors; @Slf4j
public class SynchronizedExample2 { // 修饰一个类
//public void test1() {
//public void test1(int j) {
public static void test1(int j) {//这里面呢为了方便测试,我这里边也把它变成一个静态方法
//synchronized (this){
synchronized (SynchronizedExample2.class){
for (int i = 0; i < 10; i++) {
//log.info("test1 - {}", i);
log.info("test1 {} - {}", j, i);
}
}
} // 修饰一个静态方法
//public synchronized void test2() {
//public synchronized void test2(int j) {
public static synchronized void test2(int j) {
for (int i = 0; i < 10; i++) {
//log.info("test2 - {}", i);
log.info("test2 {} - {}", j, i);
}
}
public static void main(String args[]) {
SynchronizedExample2 example1 = new SynchronizedExample2();//声明这个类的实例
SynchronizedExample2 example2 = new SynchronizedExample2();//
ExecutorService executorService = Executors.newCachedThreadPool();//声明一个线程池
//开启一个进程去执行这个方法
executorService.execute(()->{
//example1.test1();
//example1.test2();
example1.test1(1);
//example1.test2(1);
});
executorService.execute(()->{
//example1.test1();
//example1.test2();
example2.test1(2);
//example2.test2(2);
});
}
}

关于synchronized的四种修饰,我们就演示完了。使用synchronized该如何保证计数是线程安全的。

我们如果是让synchronized修饰一个静态方法,那么所有类之间都是原子性操作,同一个时间只有一个线程可以执行。

因此CountExample3是一个线程安全的类。

com.mmall.concurrency.example.count.CountExample3

C:\Users\ZHONGZHENHUA\imooc\concurrency\src\main\java\com\mmall\concurrency\example\count\CountExample3.java

package com.mmall.concurrency.example.count;

import com.mmall.concurrency.annoations.ThreadSafe;
import lombok.extern.slf4j.Slf4j; import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Semaphore; @Slf4j
@ThreadSafe
public class CountExample3 { // 请求总数
public static int clientTotal = 5000;//1000个请求 // 同时并发执行的线程数
public static int threadTotal = 200;//允许并发的线程数是50 public static int count = 0; public static void main(String[] args) throws Exception {
ExecutorService executorService = Executors.newCachedThreadPool();
final Semaphore semaphore = new Semaphore(threadTotal);
final CountDownLatch countDownLatch = new CountDownLatch(clientTotal);
for (int i = 0; i < clientTotal; i++) {
executorService.execute(()-> {
try {
semaphore.acquire();
add();
semaphore.release();
} catch (Exception e) {
log.error("exception",e);
}
countDownLatch.countDown();
});
}
countDownLatch.await();
executorService.shutdown();
log.info("count:{}",count); } //private static void add() {
private synchronized static void add() {
count++;
}
}

我们可以看到之前计数错误的类里面,我们把我们核心计算的方法使用synchronized修饰之后,这个类就变成线程安全的了。synchronized在这方面它使用起来还是比较简单的。

线程安全性的原子性。synchronized它是不可中断的锁,一当代码执行到synchronized作用范围之内的时候,是必须等待代码执行完的,而Lock它是可中断的锁,只要调用了unLock就可以了。synchronzied它更适合竞争不激烈的时候使用,可读性较好,Lock它在竞争激烈的时候,依然能保持常态。synchronized在竞争激烈的时候它的性能会下降的特别的快,而Atomic包它在竞争激烈时也能维持常态,性能比Lock还要好一些,但是它也有缺点,它每次只能同步一个值。

synchronized和Atomic在实际开发中我们会经常使用。

4-3 线程安全性-原子性-synchronized的更多相关文章

  1. 并发与高并发(八)-线程安全性-原子性-synchronized

    前言 闲暇时刻,谈一下曾经在多线程教程中接触的同步锁synchronized,相当于复习一遍吧. 主要介绍 synchronized:依赖JVM Lock:依赖特殊的CPU指令,代码实现,Reetra ...

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

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

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

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

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

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

  5. 线程安全性-原子性之synchronized锁

    原子性提供了互斥访问:同一时刻只能有一个线程进行操作: 除了Atomic包类之外,还有锁可以实现此功能: synchronized:  java关键字,依赖于jvm实现锁功能,被此关键字所修饰的,都是 ...

  6. 4-2 线程安全性-原子性-atomic-2

    AtomicReference和AtomicLong.AtomicInteger很像,方法也基本上是一样的,然后我们通过引用Integer来做一个简单的例子. com.mmall.concurrenc ...

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

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

  8. Java线程安全性-原子性工具对比

    synchronized 不可中断锁,适合竞争不激烈的场景,可读性好,竞争激烈时性能下降很快 Lock 可中断锁,多样化同步,竞争激烈时能维持常态 Atomic 竞争激烈时能维持常态,比Lock性能还 ...

  9. Java并发编程 (四) 线程安全性

    个人博客网:https://wushaopei.github.io/    (你想要这里多有) 一.线程安全性-原子性-atomic-1 1.线程安全性 定义: 当某个线程访问某个类时,不管运行时环境 ...

随机推荐

  1. Arcgis for javascript不同的状态下自定义鼠标样式

    俗话说:爱美之心,人皆有之.是的,没错,即使我只是一个做地图的,我也希望自己的地图看起来好看一点.在本文,给大家讲讲在Arcgis for javascript下如何自定义鼠标样式. 首先,说几个状态 ...

  2. HAWQ取代传统数仓实践(十一)——维度表技术之维度合并

    有一种合并维度的情况,就是本来属性相同的维度,因为某种原因被设计成重复的维度属性.例如,在销售订单示例中,随着数据仓库中维度的增加,我们会发现有些通用的数据存在于多个维度中.客户维度的客户地址相关信息 ...

  3. Leetcode 1006. Clumsy Factorial

    class Solution(object): def clumsy(self, N): """ :type N: int :rtype: int "" ...

  4. transition 总结

    详情:http://www.css88.com/book/css/properties/transition/transition-property.htm left不能进行transition

  5. LG2120 [ZJOI2007]仓库建设

    题意 L公司有N个工厂,由高到底分布在一座山上. 工厂1在山顶,工厂N在山脚. 由于这座山处于高原内陆地区(干燥少雨),L公司一般把产品直接堆放在露天,以节省费用. 突然有一天,L公司的总裁L先生接到 ...

  6. Object.prototype.hasOwnProperty()

    hasOwnProperty() 方法会返回一个布尔值,指示对象是否具有指定的属性作为自身(不继承)属性. 语法 obj.hasOwnProperty(prop) 参数 prop 要检测的属性  字符 ...

  7. linux 使用asciinema 进行命令行屏幕录制共享

    1. 安装 yum install asciinema 2. 使用 录制 asciinema rec filename(可选,方便进行后期的回放play) 同时生成一个url 地址方便传递 https ...

  8. 从如何优化SQL入手,提高数据仓库的ETL效率

    1        引言数据仓库建设中的ETL(Extract, Transform, Load)是数据抽取.转换和装载到模型的过程,整个过程基本是通过控制用SQL语句编写的存储过程和函数的方式来实现对 ...

  9. 5 数组 Swift/Object-C ——《Swift3.0从入门到出家》

    Swift中数组是一种数据结构,用来存放多个形同类型的数据结构,数据在数组内的存放是有序的,存进来的数据个读出来的顺序相同 Object-C 中数组能够存放任意类型的数据类型为[AnyObject] ...

  10. 一个detect问题引发的一系列思考

    在用BoneCP的时候,发现一个JVM日志中报了一个异常,大意是“探测(detect)到有数据库链接没有关闭”(不得不说JVM的强大),但是我用的是连接池里面的链接啊,怎么会需要关闭呢? 有问题首先找 ...