原子性问题的源头是线程切换

Q:如果禁用 CPU 线程切换是不是就解决这个问题了?

A:单核 CPU 可行,但到了多核 CPU 的时候,有可能是不同的核在处理同一个变量,即便不切换线程,也有问题。

所以,解决原子性的关键是「同一时刻只有一个线程处理该变量,也被称为互斥」。

如何做到呢?用「锁」。

一、锁模型

一)简易锁模型

一般看到的锁模型长下面这样。

但对于这个模型,会有几个疑问:

  • 锁的是什么?
  • 临界区的这一堆代码相关的都被锁了?
  • 保护的又是什么?

二)改进后的锁模型

用下面这个模型来解释就解答了上面几个问题:

  • 要保护的是临界区中的资源 R
  • 因此要为 R 创建一个对应的锁 LR
  • 需要处理资源 R 的时候先加锁,处理完之后解锁

要注意的是:

  • 一个资源必须和锁对应,不能用 A 锁去锁 B 资源

二、Java 提供的锁技术

Java 提供了多种技术,这里仅谈及 Synchronized

Synchronized 关键字

Java 语言提供的 synchronized 关键字,就是锁的一种实现。synchronized 关键字可以用来修饰方法,也可以用来修饰代码块。

class X {
// 修饰非静态方法
synchronized void foo() {
// 临界区
}
// 修饰静态方法
synchronized static void bar() {
// 临界区
}
// 修饰代码块
Object obj = new Object();
void baz() {
synchronized(obj) {
// 临界区
}
}
}

Q:synchronized 没看到 lock 和 unlock?

A:在编译的时候会做转换,synchronized起始的地方加锁,结束的地方解锁。

Q:那么 synchronized 锁的是什么呢?

A:当修饰静态方法时,锁定的是当前类的 Class 对象,在上面的例子中就是 Class X;

当修饰非静态方法时,锁定的是当前实例对象 this。

当修饰代码块时,括号中写的是啥就锁啥。

(可能不准确)

Class 对象是用来保存类信息的,可以理解为元数据?

实例对象则是每一个 new 出来的特殊的个体

Synchronized 实例

public class SynchronizedTT  {
private int value = 0; //public void printValue() {
public synchronized void printValue() {
System.out.println(this.value);
} public synchronized void addValue() throws InterruptedException {
Thread.sleep(1000);
this.value += 1;
}
} // 开两个线程,一个先调用 addValue(),另一个后调用 printValue()

思考:如果 printValue() 不添加 synchronized 关键字,会造成什么样的结果?

A:有可能会先执行了 addValue 在执行 print 但得到的却是增加之前的数值。

三、锁和受保护资源的关系

要点:

  • 一把锁可以保护多个资源
  • 但是一个资源只能用一把锁保护
  • 受保护资源和锁之间的关联关系是 N:1 的关系

思考:如果用多把锁锁同一个资源会出现什么情况?

下面例子:

synchronized 是不同的锁,就和没锁一样。

public class SynchronizedTT  {
private static int value = 0; public synchronized void printValue() {
System.out.println(value);
} public synchronized static void addValue() throws InterruptedException {
Thread.sleep(1000);
value += 1;
}
}

【Java并发入门】03 互斥锁(上):解决原子性问题的更多相关文章

  1. Java并发编程实战 03互斥锁 解决原子性问题

    文章系列 Java并发编程实战 01并发编程的Bug源头 Java并发编程实战 02Java如何解决可见性和有序性问题 摘要 在上一篇文章02Java如何解决可见性和有序性问题当中,我们解决了可见性和 ...

  2. java 并发多线程显式锁概念简介 什么是显式锁 多线程下篇(一)

    目前对于同步,仅仅介绍了一个关键字synchronized,可以用于保证线程同步的原子性.可见性.有序性 对于synchronized关键字,对于静态方法默认是以该类的class对象作为锁,对于实例方 ...

  3. java并发多线程显式锁Condition条件简介分析与监视器 多线程下篇(四)

    Lock接口提供了方法Condition newCondition();用于获取对应锁的条件,可以在这个条件对象上调用监视器方法 可以理解为,原本借助于synchronized关键字以及锁对象,配备了 ...

  4. Java并发入门之FutureTask

    Java并发入门之FutureTask 前言: 最近遇到一个项目需要上传图片到服务器,API要求是二进制流,那就跑慢点一点点上传. 于是对多线程从没有应用过的我,决定拿多线程直接应用于代码. 应用Ex ...

  5. Java并发编程:Concurrent锁机制解析

    Java并发编程:Concurrent锁机制解析 */--> code {color: #FF0000} pre.src {background-color: #002b36; color: # ...

  6. python 并发编程 多进程 互斥锁 目录

    python 并发编程 多进程 互斥锁 模拟抢票 互斥锁与join区别

  7. Java并发编程之验证volatile不能保证原子性

    Java并发编程之验证volatile不能保证原子性 通过系列文章的学习,凯哥已经介绍了volatile的三大特性.1:保证可见性 2:不保证原子性 3:保证顺序.那么怎么来验证可见性呢?本文凯哥(凯 ...

  8. Java并发基础03. 传统线程互斥技术—synchronized

    在多个线程同时操作相同资源的时候,就会遇到并发的问题,如银行转账啊.售票系统啊等.为了避免这些问题的出现,我们可以使用synchronized关键字来解决,下面针对synchronized常见的用法做 ...

  9. Java并发编程:同步锁、读写锁

    之前我们说过线程安全问题可以用锁机制来解决,即线程必要要先获得锁,之后才能进行其他操作.其实在 Java 的 API 中有这样一些锁类可以提供给我们使用,与其他对象作为锁相比,它们具有更强大的功能. ...

  10. 悲观的并发策略——Synchronized互斥锁

    volatile既然不足以保证数据同步,那么就必须要引入锁来确保.互斥锁是最常见的同步手段,在并发过程中,当多条线程对同一个共享数据竞争时,它保证共享数据同一时刻只能被一条线程使用,其他线程只有等到锁 ...

随机推荐

  1. JUC在深入面试题——三种方式实现线程等待和唤醒(wait/notify,await/signal,LockSupport的park/unpark)

    一.前言 在多线程的场景下,我们会经常使用加锁,来保证线程安全.如果锁用的不好,就会陷入死锁,我们以前可以使用Object的wait/notify来解决死锁问题.也可以使用Condition的awai ...

  2. kubeoperator 使用外部mysql

    1.导出 kubeoperator 的数据库 sql 文件,然后导入到外部mysql 2.正常关闭 kubeoperator 3.关闭 kubeoperator 不会影响已经部署的 k8s 集群 4. ...

  3. 单机部署minio,设置Nginx代理,配置https(TLS)访问

    安装 下载地址:https://dl.min.io/ # 创建目录 mkdir -p /usr/local/minio/{data,bin,etc} # 下载minio wget https://dl ...

  4. CentOS7.x安装VNC

    VNC需要系统安装的有桌面,如果是生产环境服务器,安装时使用的最小化安装,那么进行下面操作安装GNOME 桌面. # 列出的组列表里有GNOME Desktop. yum grouplist #安装 ...

  5. DeepHyperX代码理解-HamidaEtAl

    代码复现自论文<3-D Deep Learning Approach for Remote Sensing Image Classification> 先对部分基础知识做一些整理: 一.局 ...

  6. 一个C#开发者学习SpringCloud搭建微服务的心路历程

    前言 Spring Cloud很火,很多文章都有介绍如何使用,但对于我这种初学者,我需要从创建项目开始学起,所以这些文章对于我的启蒙,帮助不大,所以只好自己写一篇文章,用于备忘. SpringClou ...

  7. P2680 [NOIP2015 提高组] 运输计划 (树上差分-边差分)

    P2680 题目的大意就是走完m条路径所需要的最短时间(边权是时间), 其中我们可以把一条边的权值变成0(也就是题目所说的虫洞). 可以考虑二分答案x,找到一条边,使得所有大于x的路径都经过这条边(差 ...

  8. POJ2763 Housewife Wind (树链剖分)

    差不多是模板题,不过要注意将边权转化为点权,将边的权值赋给它所连的深度较大的点. 这样操作过后,注意查询ask()的代码有所改变(见代码注释) 1 #include<cstdio> 2 # ...

  9. RAID5 IO处理之写请求代码详解

    我们知道RAID5一个条带上的数据是由N个数据块和1个校验块组成,其校验块由N个数据块通过异或运算得出,这样才能在任意一个成员磁盘失效时通过其他N个成员磁盘恢复出用户写入的数据.这也就要求RAID5条 ...

  10. 深入剖析Sgementation fault原理

    深入剖析Sgementation fault原理 前言 我们在日常的编程当中,我们很容易遇到的一个程序崩溃的错误就是segmentation fault,在本篇文章当中将主要分析段错误发生的原因! S ...