原子性它提供了互斥访问,同一时刻只能有一个线程来对它进行操作。能保证同一时刻只有一个线程来对其进行操作的,除了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. 微信小程序转支付宝小程序

    使用方法: npm install wx-alipay -g wxToalipay --src={{小程序源码目录}} --dest={{支付宝小程序目录,可缺省}} 点击回车后就可将微信小程序转换为 ...

  2. LINUX 命令—netstat [简单实用]

    1.--当我们在检查程序是否启动或者网络状况的时候 会查看本机活跃的端口,就需要这个命令: |--"netstat – Print network connections, routing ...

  3. MFC中控制Tips的显示 - lingyun1120

    from:  http://www.cnblogs.com/lingyun1120/archive/2011/11/09/2243279.html MFC中为ToolTips生成了一个类CToolTi ...

  4. bzoj 3052 糖果公园

    Written with StackEdit. Description \(Candyland\) 有一座糖果公园,公园里不仅有美丽的风景.好玩的游乐项目,还有许多免费糖果的发放点,这引来了许多贪吃的 ...

  5. request.getDispatcher().forward(request,response)和response.sendRedirect()的区别

    在进行web开发时,跳转是最常见的,今天在这里来学习下2种跳转: 第一种是request.getDispatcher().forward(request,response): 1.属于转发,也是服务器 ...

  6. database - 数据库设计/使用容易忽略的细节

    一.设计 1,数据类型尽量使用数字型,数字型的比较比字符型的快很多 2,数据类型尽量小,预测可以满足未来需求的前提 3,尽量建表时字段不允许为null,除非必要,可以用NOT NULL+DEFAULT ...

  7. 学习动态性能表(3)--v$sql&v$sql_plan

    学习动态性能表 第三篇-(1)-v$sq 2007.5.25 V$SQL中存储具体的SQL语句. 一条语句可以映射多个cursor,因为对象所指的cursor可以有不同用户(如例1).如果有多个cur ...

  8. Markdown 中的目录自动生成功能 TOC

    目录 Markdown 中的目录自动生成功能 TOC 1. 标题一 1.1 标题二 1.标题二 2. 标题一 2.1 标题二 2.2 标题二 Markdown 中的目录自动生成功能 TOC 1. 标题 ...

  9. 参数化防SQL注入

    private void AddStudent(){ string strName =txtName.Text.Trim(); string strPwd = txtPwd.Text.Trim(); ...

  10. 获取sonar扫描结果

    api通过抓包获取 java 1.get和post方法 package com.tools.httpUtil; import java.io.BufferedReader; import java.i ...