原子性它提供了互斥访问,同一时刻只能有一个线程来对它进行操作。能保证同一时刻只有一个线程来对其进行操作的,除了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. How do I create zip file in Servlet for download?

    原文链接:https://kodejava.org/how-do-i-create-zip-file-in-servlet-for-download/ The example below is a s ...

  2. 从无到有开发自己的Wordpress博客主题---创建主题

    上一篇教程,我们已经安装了Wordpress,我们可以成功的登录到Wordpress后台,接下来的任务就是创建我们自己的主题. 要想创建一个Wordpress主题,就必须按照Wordpress的规则, ...

  3. Linux 工具套件 —— binutils、readelf

    readelf:Linux 下专门针对 ELF 文件格式的解析器: 0. binutils GNU Binutils gnu binutils 一套二进制工具的集合,主要包含:ld(gnu linke ...

  4. 深入理解java虚拟机-第二章

    第2章 Java内存区域与内存溢出异常 运行数据区域 1.程序计数器(Program Counter Register) 是一块较小的内存空间,它可以看作是当前线程所执行的字节码的行号指示器. 2.J ...

  5. N位N进制里有多少个N

    32位二进制里有多少个1 https://blog.csdn.net/zhangsj1007/article/details/81411063 有这样一道计算机问题"32位二进制里面有多少个 ...

  6. 剑指offer-第五章优化时间和空间效率(数组中的逆序对的总数)

    题目:在数组中如果两个数字的前面的数比后面的数大,则称为一对逆序对.输入一个数组求出数组中逆序对的总数. 以空间换时间:思路:借助一个辅助数组,将原来的数组复制到该数组中.然后将该数组分成子数组,然后 ...

  7. Android 杂记

    Android Studio 报错:sdk location should not contain whitespace as this can cause problems with the ndk ...

  8. slabtop 监控实时内核片缓存信息

                                        使用 slabtop命令监控实时内核片缓存信息                                 默认情况下,sl ...

  9. shell编程中变量的运算 (shell 06)

    主要包括以下3种 字符串操作数学运算浮点运算 一.字符串操作 字符串的连接 连接字2个字符串不需要任何连接符,挨着写即可 长度获取 expr length "hello" expr ...

  10. 中间件——Oracle Fusion Middleware

    Oracle Fusion Middleware定义: 什么是Oracle Fusion Middleware Oracle Fusion Middleware is a comprehensive ...