本文源码:GitHub·点这里 || GitEE·点这里

一、并发问题

多线程学习的时候,要面对的第一个复杂问题就是,并发模式下变量的访问,如果不理清楚内在流程和原因,经常会出现这样一个问题:线程处理后的变量值不是自己想要的,可能还会一脸懵的说:这不合逻辑吧?

1、成员变量访问

多个线程访问类的成员变量,可能会带来各种问题。

public class AccessVar01 {
public static void main(String[] args) {
Var01Test var01Test = new Var01Test() ;
VarThread01A varThread01A = new VarThread01A(var01Test) ;
varThread01A.start();
VarThread01B varThread01B = new VarThread01B(var01Test) ;
varThread01B.start();
}
}
class VarThread01A extends Thread {
Var01Test var01Test = new Var01Test() ;
public VarThread01A (Var01Test var01Test){
this.var01Test = var01Test ;
}
@Override
public void run() {
var01Test.addNum(50);
}
}
class VarThread01B extends Thread {
Var01Test var01Test = new Var01Test() ;
public VarThread01B (Var01Test var01Test){
this.var01Test = var01Test ;
}
@Override
public void run() {
var01Test.addNum(10);
}
}
class Var01Test {
private Integer num = 0 ;
public void addNum (Integer var){
try {
if (var == 50){
num = num + 50 ;
Thread.sleep(3000);
} else {
num = num + var ;
}
System.out.println("var="+var+";num="+num);
} catch (Exception e){
e.printStackTrace();
}
}
}

这里案例的流程就是并发下运算一个成员变量,程序的本意是:var=50,得到num=50,可输出的实际结果是:

var=10;num=60
var=50;num=60

VarThread01A线程处理中进入休眠,休眠时num已经被线程VarThread01B进行一次加10的运算,这就是多线程并发访问导致的结果。

2、方法私有变量

修改上述的代码逻辑,把num变量置于方法内,作为私有的方法变量。

class Var01Test {
// private Integer num = 0 ;
public void addNum (Integer var){
Integer num = 0 ;
try {
if (var == 50){
num = num + 50 ;
Thread.sleep(3000);
} else {
num = num + var ;
}
System.out.println("var="+var+";num="+num);
} catch (Exception e){
e.printStackTrace();
}
}
}

方法内部的变量是私有的,且和当前执行方法的线程绑定,不会存在线程间干扰问题。

二、同步控制

1、Synchronized关键字

使用方式:修饰方法,或者以控制同步块的形式,保证多个线程并发下,同一时刻只有一个线程进入方法中,或者同步代码块中,从而使线程安全的访问和处理变量。如果修饰的是静态方法,作用的是这个类的所有对象。

独占锁属于悲观锁一类,synchronized就是一种独占锁,假设处于最坏的情况,只有一个线程执行,阻塞其他线程,如果并发高,处理耗时长,会导致多个线程挂起,等待持有锁的线程释放锁。

2、修饰方法

这个案例和第一个案例原理上是一样的,不过这里虽然在修改值的地方加入的同步控制,但是又挖了一个坑,在读取的时候没有限制,这个现象俗称脏读。

public class AccessVar02 {
public static void main(String[] args) {
Var02Test var02Test = new Var02Test ();
VarThread02A varThread02A = new VarThread02A(var02Test) ;
varThread02A.start();
VarThread02B varThread02B = new VarThread02B(var02Test) ;
varThread02B.start();
var02Test.readValue();
}
}
class VarThread02A extends Thread {
Var02Test var02Test = new Var02Test ();
public VarThread02A (Var02Test var02Test){
this.var02Test = var02Test ;
}
@Override
public void run() {
var02Test.change("my","name");
}
}
class VarThread02B extends Thread {
Var02Test var02Test = new Var02Test ();
public VarThread02B (Var02Test var02Test){
this.var02Test = var02Test ;
}
@Override
public void run() {
var02Test.change("you","age");
}
}
class Var02Test {
public String key = "cicada" ;
public String value = "smile" ;
public synchronized void change (String key,String value){
try {
this.key = key ;
Thread.sleep(2000);
this.value = value ;
System.out.println("key="+key+";value="+value);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public void readValue (){
System.out.println("读取:key="+key+";value="+value);
}
}

在线程中,逻辑上已经修改了,只是没执行到,但是在main线程中读取的value毫无意义,需要在读取方法上也加入同步的线程控制。

3、同步控制逻辑

同步控制实现是基于Object的监视器。

  • 线程对Object的访问,首先要先获得Object的监视器 ;
  • 如果获取成功,则会独占该对象 ;
  • 其他线程会掉进同步队列,线程状态变为阻塞 ;
  • 等Object的持有线程释放锁,会唤醒队列中等待的线程,尝试重启获取对象监视器;

4、修饰代码块

说明一点,代码块包含方法中的全部逻辑,锁定的粒度和修饰方法是一样的,就写在方法上吧。同步代码块一个很核心的目的,减小锁定资源的粒度,就如同表锁和行级锁。

public class AccessVar03 {
public static void main(String[] args) {
Var03Test var03Test1 = new Var03Test() ;
Thread thread1 = new Thread(var03Test1) ;
thread1.start();
Thread thread2 = new Thread(var03Test1) ;
thread2.start();
Thread thread3 = new Thread(var03Test1) ;
thread3.start();
}
}
class Var03Test implements Runnable {
private Integer count = 0 ;
public void countAdd() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized(this) {
count++ ;
System.out.println("count="+count);
}
}
@Override
public void run() {
countAdd() ;
}
}

这里就是锁定count处理这个动作的核心代码逻辑,不允许并发处理。

5、修饰静态方法

静态方法属于类层级的方法,对象是不可以直接调用的。但是synchronized修饰的静态方法锁定的是这个类的所有对象。

public class AccessVar04 {
public static void main(String[] args) {
Var04Test var04Test1 = new Var04Test() ;
Thread thread1 = new Thread(var04Test1) ;
thread1.start();
Var04Test var04Test2 = new Var04Test() ;
Thread thread2 = new Thread(var04Test2) ;
thread2.start();
}
}
class Var04Test implements Runnable {
private static Integer count ;
public Var04Test (){
count = 0 ;
}
public synchronized static void countAdd() {
System.out.println(Thread.currentThread().getName()+";count="+(count++));
}
@Override
public void run() {
countAdd() ;
}
}

如果不是使用同步控制,从逻辑和感觉上,输出的结果应该如下:

Thread-0;count=0
Thread-1;count=0

加入同步控制之后,实际测试输出结果:

Thread-0;count=0
Thread-1;count=1

6、注意事项

  • 继承中子类覆盖父类方法,synchronized关键字特性不能继承传递,必须显式声明;
  • 构造方法上不能使用synchronized关键字,构造方法中支持同步代码块;
  • 接口中方法,抽象方法也不支持synchronized关键字 ;

三、Volatile关键字

1、基本描述

Java内存模型中,为了提升性能,线程会在自己的工作内存中拷贝要访问的变量的副本。这样就会出现同一个变量在某个时刻,在一个线程的环境中的值可能与另外一个线程环境中的值,出现不一致的情况。

使用volatile修饰成员变量,不能修饰方法,即标识该线程在访问这个变量时需要从共享内存中获取,对该变量的修改,也需要同步刷新到共享内存中,保证了变量对所有线程的可见性。

2、使用案例

class Var05Test {
private volatile boolean myFlag = true ;
public void setFlag (boolean myFlag){
this.myFlag = myFlag ;
}
public void method() {
while (myFlag){
try {
System.out.println(Thread.currentThread().getName()+myFlag);
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}

3、注意事项

  • 可见性只能确保每次读取的是最新的值,但不支持变量操作的原子性;
  • volatile并不会阻塞线程方法,但是同步控制会阻塞;
  • Java同步控制的根本:保证并发下资源的原子性和可见性;

四、源代码地址

GitHub·地址
https://github.com/cicadasmile/java-base-parent
GitEE·地址
https://gitee.com/cicadasmile/java-base-parent

Java并发编程(03):多线程并发访问,同步控制的更多相关文章

  1. Java并发编程 (十) 多线程并发拓展

    个人博客网:https://wushaopei.github.io/    (你想要这里多有) 一.死锁 1.死锁的定义 所谓的死锁是指两个或两个以上的线程在等待执行的过程中,因为竞争资源而造成的一种 ...

  2. Java并发编程系列-(1) 并发编程基础

    1.并发编程基础 1.1 基本概念 CPU核心与线程数关系 Java中通过多线程的手段来实现并发,对于单处理器机器上来讲,宏观上的多线程并行执行是通过CPU的调度来实现的,微观上CPU在某个时刻只会运 ...

  3. java并发编程与高并发解决方案

    下面是我对java并发编程与高并发解决方案的学习总结: 1.并发编程的基础 2.线程安全—可见性和有序性 3.线程安全—原子性 4.安全发布对象—单例模式 5.不可变对象 6.线程封闭 7.线程不安全 ...

  4. java并发编程--第一章并发编程的挑战

    一.java并发编程的挑战 并发编程需要注意的问题: 并发编程的目的是让程序运行的更快,然而并不是启动更多的线程就能让程序最大限度的并发执行.若希望通过多线程并发让程序执行的更快,会受到如下问题的挑战 ...

  5. Python并发编程04 /多线程、生产消费者模型、线程进程对比、线程的方法、线程join、守护线程、线程互斥锁

    Python并发编程04 /多线程.生产消费者模型.线程进程对比.线程的方法.线程join.守护线程.线程互斥锁 目录 Python并发编程04 /多线程.生产消费者模型.线程进程对比.线程的方法.线 ...

  6. Python并发编程03 /僵孤进程,孤儿进程、进程互斥锁,进程队列、进程之间的通信

    Python并发编程03 /僵孤进程,孤儿进程.进程互斥锁,进程队列.进程之间的通信 目录 Python并发编程03 /僵孤进程,孤儿进程.进程互斥锁,进程队列.进程之间的通信 1. 僵尸进程/孤儿进 ...

  7. 并发编程概述--C#并发编程经典实例

    优秀软件的一个关键特征就是具有并发性.过去的几十年,我们可以进行并发编程,但是难度很大.以前,并发性软件的编写.调试和维护都很难,这导致很多开发人员为图省事放弃了并发编程.新版.NET 中的程序库和语 ...

  8. Java并发编程、多线程、线程池…

    <实战java高并发程序设计>源码整理https://github.com/petercao/concurrent-programming/blob/master/README.md Ja ...

  9. 4、Java并发性和多线程-并发编程模型

    以下内容转自http://ifeve.com/%E5%B9%B6%E5%8F%91%E7%BC%96%E7%A8%8B%E6%A8%A1%E5%9E%8B/: 并发系统可以采用多种并发编程模型来实现. ...

随机推荐

  1. Ubuntu18.04安装OpenStack

    Ubuntu18.04 安装Queens版本OpenStack 安装环境 系统 系统使用的是Ubuntu18,最少4核8G内存,20G硬盘空间. 工具 devstack DevStack是一系列可扩展 ...

  2. Java实体映射工具MapStruct的使用

    官网地址:http://mapstruct.org/ MapStruct 是一个代码生成器,简化了不同的 Java Bean 之间映射的处理,所谓的映射指的就是从一个实体变化成一个实体.例如我们在实际 ...

  3. 安卓之父造手机:该紧张的只有iPhone?

    近日,"安卓之父" Andy Rubin正式带来他潜心打造的新款智能手机--Essential.这款设计新颖.配置强大的手机刚一发布,就引起全球科技界的广泛关注.对iPhone.三 ...

  4. HDU 5894 hannnnah_j’s Biological Test【组合数学】

    题目链接: http://acm.hdu.edu.cn/showproblem.php?pid=5894 题意: 一个圆桌上有$n$个不同的位置,$m$个相同的人安排到这$n$个位置上,要求两人相邻的 ...

  5. 达拉草201771010105《面向对象程序设计(java)》第十一周学习总结

    达拉草201771010105<面向对象程序设计(java)>第十一周学习总结 实验十一   集合 实验时间 2018-11-8 第一部分:理论知识 1.集合(Collection或称为容 ...

  6. python自己做计算器

    题目: exp = '1 - 2 * ( (60-30 +(-40/5) * (9-2*5/3 + 7 /3*99/4*2998 +10 * 568/14 )) - (-4*3)/ (16-3*2) ...

  7. 配置VSCode的C/C++语言功能

    0. 前言 主要是在网上找的方法都没试成功过,在各种机缘巧合下终于成功了. 这篇文章基于个人经验,而且没有走寻常路. 1. 需要的软件和插件 软件: VSCode (https://code.visu ...

  8. MVC07

    1. 讲解ASP.net MVC的I/O操作 新建一个控制台程序,输入代码如下 using System; using System.IO; namespace IO { class Program ...

  9. 如何使用API提交转码任务?

    摘要: 当常规的转码工作流无法满足用户的场景时,需用户自己判断业务逻辑,并使用API提交转码任务.例如:并不是所有的视频都需要转码,不同视频需要设置不同的转码配置.本文将介绍API提交转码任务的方法. ...

  10. C++冒险攻略(持续更新中。。。)

    C++语言程序设计 我的C++冒险之旅 绪论 计算机系统基本概念 计算机硬件 计算机程序语言 计算机解决问题是程序控制的 程序就是操作步骤 程序要使用语言来表达 机器语言 计算机能识别的是机器语言 机 ...