我的编程空间,编程开发者的网络收藏夹
学习永远不晚

Java并发编程(03):多线程并发访问,同步控制

短信预约 -IT技能 免费直播动态提醒
省份

北京

  • 北京
  • 上海
  • 天津
  • 重庆
  • 河北
  • 山东
  • 辽宁
  • 黑龙江
  • 吉林
  • 甘肃
  • 青海
  • 河南
  • 江苏
  • 湖北
  • 湖南
  • 江西
  • 浙江
  • 广东
  • 云南
  • 福建
  • 海南
  • 山西
  • 四川
  • 陕西
  • 贵州
  • 安徽
  • 广西
  • 内蒙
  • 西藏
  • 新疆
  • 宁夏
  • 兵团
手机号立即预约

请填写图片验证码后获取短信验证码

看不清楚,换张图片

免费获取短信验证码

Java并发编程(03):多线程并发访问,同步控制

本文源码: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=60var=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=0Thread-1;count=0

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

Thread-0;count=0Thread-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-parentGitEE·地址https://gitee.com/cicadasmile/java-base-parent

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

Java并发编程(03):多线程并发访问,同步控制

下载Word文档到电脑,方便收藏和打印~

下载Word文档

猜你喜欢

Java并发编程(03):多线程并发访问,同步控制

本文源码:GitHub·点这里 || GitEE·点这里一、并发问题多线程学习的时候,要面对的第一个复杂问题就是,并发模式下变量的访问,如果不理清楚内在流程和原因,经常会出现这样一个问题:线程处理后的变量值不是自己想要的,可能还会一脸懵的
2023-06-02

java线程并发控制同步工具CountDownLatch

这篇文章主要为大家介绍了java线程并发控制同步工具CountDownLatch使用示例详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
2022-11-13

Linux协程的并发访问控制

在Linux系统中,协程的并发访问控制可以通过多种方式实现,以下是一些常见的方法:互斥锁(Mutex):通过在协程访问共享资源之前获取互斥锁,可以确保同一时间只有一个协程能够访问该资源,并避免数据竞争问题。信号量(Semaphore):信号
Linux协程的并发访问控制
2024-08-09

C#并发编程和线程同步机制是什么

C#并发编程是指在C#程序中同时执行多个任务的能力。线程同步机制是确保多个线程能够安全地访问共享资源的方法。在C#中,线程同步可以通过以下方式实现:使用锁机制(lock):通过在代码块中使用lock关键字来锁定共享资源,确保在任意时刻只有一
C#并发编程和线程同步机制是什么
2024-03-07

C#开发注意事项:多线程编程与并发控制

在C#开发中,面对不断增长的数据和任务,多线程编程和并发控制显得尤为重要。本文将从多线程编程和并发控制两个方面,为大家介绍一些在C#开发中需要注意的事项。一、多线程编程多线程编程是一种利用CPU多核心资源提高程序效率的技术。在C#程序中,多
C#开发注意事项:多线程编程与并发控制
2023-11-22

C#开发中如何处理线程同步和并发访问问题

C#开发中如何处理线程同步和并发访问问题,需要具体代码示例在C#开发中,线程同步和并发访问问题是一个常见的挑战。由于多个线程可以同时访问和操作共享数据,可能会出现竞态条件和数据不一致的问题。为了解决这些问题,我们可以使用各种同步机制和并发控
2023-10-22

理解Java多线程之并发编程

这篇文章主要介绍了理解Java多线程之并发编程的相关资料,需要的朋友可以参考下
2023-02-02

C#开发中如何处理并发编程和线程同步问题

C#开发中如何处理并发编程和线程同步问题,需要具体代码示例在C#开发中,处理并发编程和线程同步问题是非常重要的。并发编程是指在程序中同时执行多个任务或操作,而线程同步则是指多个线程在访问共享资源时的协调和同步。为了解决并发编程和线程同步问题
2023-10-22

Java多线程并发编程 Synchronized关键字

synchronized 关键字解析同步锁依赖于对象,每个对象都有一个同步锁。现有一成员变量 Test,当线程 A 调用 Test 的 synchronized 方法,线程 A 获得 Test 的同步锁,同时,线程 B 也去调用 Test
2023-05-31

Java多线程并发编程 Volatile关键字

volatile 关键字是一个神秘的关键字,也许在 J2EE 上的 JAVA 程序员会了解多一点,但在 Android 上的 JAVA 程序员大多不了解这个关键字。只要稍了解不当就好容易导致一些并发上的错误发生,例如好多人把 volatil
2023-05-31

Java线程同步与互斥:揭秘并发编程的秘密

Java线程同步与互斥揭秘并发编程的秘密 Java、线程、同步、互斥、并发编程 Java线程同步和互斥机制提供了对共享资源的访问控制,确保了并发程序的正确性和一致性。本文将详细探讨这两种机制,并通过演示代码讲解其工作原理和应用场景。
Java线程同步与互斥:揭秘并发编程的秘密
2024-02-09

Python并发编程中的锁机制,揭秘并发编程中的同步之道

Python并发编程中,锁机制是实现线程安全和数据同步的有效手段。通过对不同锁的特性和应用场景的深入解析,掌握锁的正确使用技巧,可以有效地提高并发程序的性能和可靠性。
Python并发编程中的锁机制,揭秘并发编程中的同步之道
2024-02-05

编程热搜

  • Python 学习之路 - Python
    一、安装Python34Windows在Python官网(https://www.python.org/downloads/)下载安装包并安装。Python的默认安装路径是:C:\Python34配置环境变量:【右键计算机】--》【属性】-
    Python 学习之路 - Python
  • chatgpt的中文全称是什么
    chatgpt的中文全称是生成型预训练变换模型。ChatGPT是什么ChatGPT是美国人工智能研究实验室OpenAI开发的一种全新聊天机器人模型,它能够通过学习和理解人类的语言来进行对话,还能根据聊天的上下文进行互动,并协助人类完成一系列
    chatgpt的中文全称是什么
  • C/C++中extern函数使用详解
  • C/C++可变参数的使用
    可变参数的使用方法远远不止以下几种,不过在C,C++中使用可变参数时要小心,在使用printf()等函数时传入的参数个数一定不能比前面的格式化字符串中的’%’符号个数少,否则会产生访问越界,运气不好的话还会导致程序崩溃
    C/C++可变参数的使用
  • css样式文件该放在哪里
  • php中数组下标必须是连续的吗
  • Python 3 教程
    Python 3 教程 Python 的 3.0 版本,常被称为 Python 3000,或简称 Py3k。相对于 Python 的早期版本,这是一个较大的升级。为了不带入过多的累赘,Python 3.0 在设计的时候没有考虑向下兼容。 Python
    Python 3 教程
  • Python pip包管理
    一、前言    在Python中, 安装第三方模块是通过 setuptools 这个工具完成的。 Python有两个封装了 setuptools的包管理工具: easy_install  和  pip , 目前官方推荐使用 pip。    
    Python pip包管理
  • ubuntu如何重新编译内核
  • 改善Java代码之慎用java动态编译

目录