本文源码: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 ;}@Overridepublic void run() {var01Test.addNum(50);}
}
class VarThread01B extends Thread {Var01Test var01Test = new Var01Test() ;public VarThread01B (Var01Test var01Test){this.var01Test = var01Test ;}@Overridepublic 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 ;}@Overridepublic void run() {var02Test.change("my","name");}
}
class VarThread02B extends Thread {Var02Test var02Test = new Var02Test ();public VarThread02B (Var02Test var02Test){this.var02Test = var02Test ;}@Overridepublic 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);}}@Overridepublic 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++));}@Overridepublic 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. 并发编程-03线程安全性之原子性(Atomic包)及原理分析

    文章目录 线程安全性文章索引 脑图 线程安全性的定义 线程安全性的体现 原子性 使用AtomicInteger改造线程不安全的变量 incrementAndGet源码分析-UnSafe类 compar ...

  2. week6 day4 并发编程之多线程 理论

    week6 day4 并发编程之多线程 理论 一.什么是线程 二.线程的创建开销小 三.线程和进程的区别 四.为何要用多线程 五.多线程的应用举例 六.经典的线程模型(了解) 七.POSIX线程(了解 ...

  3. 01 - Java并发编程与高并发解决方案笔记-基础篇

    01 - Java并发编程与高并发解决方案笔记-基础篇 基础篇很重要!很重要!很重要!!!一定要理解和认真思考. 01 - Java并发编程与高并发解决方案笔记-基础篇 1.课程准备 2.并发编程基础 ...

  4. Java 高并发_JAVA并发编程与高并发解决方案 JAVA高并发项目实战课程 没有项目经验的朋友不要错过!...

    JAVA并发编程与高并发解决方案 JAVA高并发项目实战课程 没有项目经验的朋友不要错过! 1.JPG (37.82 KB, 下载次数: 0) 2018-12-3 09:40 上传 2.JPG (28 ...

  5. 安琪拉教百里守约学并发编程之多线程基础

    <安琪拉与面试官二三事>系列文章 一个HashMap能跟面试官扯上半个小时 一个synchronized跟面试官扯了半个小时 <安琪拉教鲁班学算法>系列文章 安琪拉教鲁班学算法 ...

  6. 《Java并发编程入门与高并发面试》or 《Java并发编程与高并发解决方案》笔记

    <Java并发编程入门与高并发面试>or <Java并发编程与高并发解决方案>笔记 参考文章: (1)<Java并发编程入门与高并发面试>or <Java并发 ...

  7. c+++11并发编程语言,C++11并发编程:多线程std:thread

    原标题:C++11并发编程:多线程std:thread 一:概述 C++11引入了thread类,大大降低了多线程使用的复杂度,原先使用多线程只能用系统的API,无法解决跨平台问题,一套代码平台移植, ...

  8. 高并发编程_高并发编程系列:7大并发容器详解(附面试题和企业编程指南)...

    不知道从什么时候起,在Java编程中,经常听到Java集合类,同步容器.并发容器,高并发编程成为当下程序员需要去了解掌握的技术之一,那么他们有哪些具体分类,以及各自之间的区别和优劣呢? 只有把这些梳理 ...

  9. JavaWeb 并发编程 与 高并发解决方案

    在这里写写我学习到和自己所理解的 Java高并发编程和高并发解决方案.现在在各大互联网公司中,随着日益增长的互联网服务需求,高并发处理已经是一个非常常见的问题,在这篇文章里面我们重点讨论两个方面的问题 ...

  10. 并发编程系列之并发编程的认识

    前言 今天我们开始接触并发编程的讲解,首先我们要知道并发编程的目的是什么?一个目的,为了让程序员运行的更快,但是有一点我们要知道,并不是启动越多的线程就能让程序更大限度的并发执行,在并发编程的开发中, ...

最新文章

  1. 百度地图API : 自定义标注图标
  2. 63.2. 配置 Postfix
  3. Linux中常用的操作指令(随时更新)
  4. 数据科学包14-matplotlib-6种图形的基本画法
  5. VS Tips (Basic part)
  6. UML工具 Astah Professional8.0下载
  7. linux oracle ora-12162,oracle ORA-12162: TNS:net service name is incorrectly specified
  8. 中国科学院大学2019年高等代数考研试题
  9. 淘宝API签名异常,如何正确计算SIGN参数?(error code:25 Invalid Signature)
  10. 向前迈进!走入GC世界:G1 GC原理深入解析
  11. 如何做服务器安全维护,网站安全维护怎么做好
  12. 七个好用且免费的在线代码编辑器,你喜欢哪个?
  13. 尚硅谷 VUE 尚品汇项目实战问题解决方式整理(Vue3 版)
  14. 【杂记】(input获取焦点失去焦点、获取input中输入的值、 键盘事件、js事件、Math 属性、手机自适应meta定义、 去掉input内置阴影、图片居中、去蓝色默认背景、点击去掉默认边框)
  15. 使用 Tableau 连接到 Hortonworks Hadoop Hive
  16. Matlab常用数学函数和数学运算符
  17. 人类一败涂地做图教程_人类一败涂地怎么捏人?自定义人物PS制作教程
  18. 一点Unity3D学习经验分享
  19. 【洛谷5069】纵使日薄西山【set】【树状数组】
  20. 7天掌握NIO和SOCKET,第三天,CharBuffer的API使用

热门文章

  1. 九度OJ : 1004 Median
  2. (王道408考研操作系统)第三章内存管理-第一节5:动态分区分配算法(首次适应、和邻近适应)
  3. /etc/bashrc和/etc/profile
  4. IO流练习题 实现图片的加密解密操作
  5. Python pip参数(精)
  6. 初识空中计算(Over-the-Air Computation)
  7. python制作饼状图
  8. 【C语言及程序设计】项目1-24-4:个人所得税计算器if语句版
  9. 01_13_JSP编译指令
  10. STM32 初学不知道外设对应的APB1还是APB2