【Java】线程同步的方式

本地部署 Bolt.new - 小学生都能轻松搭建AI编程环境 阅读详情

一、为什么要线程同步

  因为当我们有多个线程要同时访问一个变量或对象时,如果这些线程中既有读又有写操作时,就会导致变量值或对象的状态出现混乱,从而导致程序异常。举个例子,如果一个统计变量count同时被两个线程操作,一个加6,一个减6。假设变量初始值为0,如果加数线程和减数线程同时发生,会出现什么结果呢?减数不成功,统计变量是6。减数成功了,统计变量是0。然而到底是哪个很难说清楚,因此多线程同步就是要解决这个问题。

public class Solution {
    public static void main(String[] args) {
        final Digit digit = new Digit();
        //增加统计量count值的线程
        Thread addThread = new Thread(new Runnable() {
            @Override
            public void run() {
                while (true) {
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    digit.addCount(6);
                    digit.queryCount();
                    System.out.println("add over>>>>>>>>>>>>>>>>>>>>>>" + "\n");
                }
            }
        });

        //减少统计量count值的线程
        Thread reduceThread = new Thread(new Runnable() {
            @Override
            public void run() {
                while (true) {
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    digit.reduceCount(6);
                    digit.queryCount();
                    System.out.println("reduce over>>>>>>>>>>>>>>>>>>>" + "\n");
                }
            }
        });

        addThread.start();
        reduceThread.start();
    }
}

class Digit {
    private int count = 0;

    public void addCount(int value) {
        count += value;
        System.out.println("output: count增加了" + value);
    }

    public void reduceCount(int value) {
        if (count - value < 0) {
            System.out.println("count不能为负。");
            return;
        }
        count -= value;
        System.out.println("output:count减少了" + value);
    }

    public void queryCount() {
        System.out.println("此时count值为:" + count);
    }
}


  如下运行结果是可能出现的四种情况。根据结果可以看出线程执行出现了混乱。因而此时需要引入线程同步。

count不能为负。
此时count值为:6
reduce over>>>>>>>>>>>>>>>>>>>

output: count增加了6
此时count值为:6
add over>>>>>>>>>>>>>>>>>>>>>>

output:count减少了6
此时count值为:0
reduce over>>>>>>>>>>>>>>>>>>>

output: count增加了6
此时count值为:6
add over>>>>>>>>>>>>>>>>>>>>>>

二、线程同步的几种方式

  1. synchronized同步方法:即有synchronized关键字修饰的方法。 由于java的每个对象都有一个内置锁,当用此关键字修饰方法时,内置锁会保护整个方法。在调用该方法前,需要获得内置锁,否则就处于阻塞状态。synchronized关键字也可以修饰静态方法,此时如果调用该静态方法,将会锁住整个类。
      将Digit.java代码中的方法用synchronized关键字同步,即:
class Digit {
    private int count = 0;

    public synchronized void addCount(int value) {
        count += value;
        System.out.println("output: count增加了" + value);
    }

    public synchronized void reduceCount(int value) {
        if (count - value < 0) {
            System.out.println("count不能为负。");
            return;
        }
        count -= value;
        System.out.println("output:count减少了" + value);
    }

    public void queryCount() {
        System.out.println("此时count值为:" + count);
    }
}

  此时的输出结果就不会混乱了,如下:

output: count增加了6
此时count值为:6
add over>>>>>>>>>>>>>>>>>>>>>>

output: count增加了6
此时count值为:12
add over>>>>>>>>>>>>>>>>>>>>>>

output:count减少了6
此时count值为:6
reduce over>>>>>>>>>>>>>>>>>>>

output:count减少了6
此时count值为:0
reduce over>>>>>>>>>>>>>>>>>>>

  1. synchronized同步代码块:同步是一种高开销的操作,因此应该尽量减少同步的内容。通常没有必要同步整个方法,使用synchronized同步关键代码块即可,如下所示:
class Digit {
    private int count = 0;

    public void addCount(int value) {
        synchronized (this) {
            count += value;
        }
        System.out.println("output: count增加了" + value);
    }

    public void reduceCount(int value) {
        synchronized (this) {
            if (count - value < 0) {
                System.out.println("count不能为负。");
                return;
            }
            count -= value;
        }
        System.out.println("output:count减少了" + value);
    }

    public void queryCount() {
        System.out.println("此时count值为:" + count);
    }
}

  此时对应输出如下,可以看到同步代码块和同步方法的效果是一致的,但是同步非常消耗资源,原则上能用同步代码块就不要用同步方法。

output: count增加了6
此时count值为:6
add over>>>>>>>>>>>>>>>>>>>>>>

output:count减少了6
此时count值为:0
reduce over>>>>>>>>>>>>>>>>>>>

output: count增加了6
此时count值为:6
add over>>>>>>>>>>>>>>>>>>>>>>

output:count减少了6
此时count值为:0
reduce over>>>>>>>>>>>>>>>>>>>
  1. volatile实现同步:保证操作的可见性,访问volatile变量时不会执行加锁操作,因此也就不会使执行线程阻塞,因此volatile变量是一种比sychronized关键字更轻量级的同步机制。volatile不会提供任何原子操作,它也不能用来修饰final类型的变量。volatile不能保证原子操作导致的,因此volatile不能代替synchronized。此外volatile会组织编译器对代码优化,因此能不使用它就不适用它吧。它的原理是每次要线程要访问volatile修饰的变量时都是从内存中读取,而不是存缓存当中读取,因此每个线程访问到的变量值都是一样的。这样就保证了同步。

  2. 使用可重入锁实现线程同步:
      JDK1.5之后引入了java.util.concurrent包,可用来支持同步。ReentrantLock类是可重入、互斥、实现了Lock接口的锁, 它与使用synchronized的方式具有相同的基本行为和语义,并且扩展了其能力。
    ReenreantLock类的常用方法有:
    ReentrantLock() : 创建一个ReentrantLock实例
    lock() : 获得锁
    unlock() : 释放锁
    注:ReentrantLock()还有一个可以创建公平锁的构造方法,但由于能大幅度降低程序运行效率,不推荐使用。将Digit.java对应的修改为:

class Digit {
    private int count = 0;
    private ReentrantLock reentrantLock = new ReentrantLock();

    public void addCount(int value) {
        reentrantLock.lock();
        try {
            count += value;
            System.out.println("output: count增加了" + value);
        } finally {
            reentrantLock.unlock();
        }
    }

    public void reduceCount(int value) {
        reentrantLock.lock();
        try {
            if (count - value < 0) {
                System.out.println("count不能为负。");
                return;
            }
            count -= value;
            System.out.println("output:count减少了" + value);
        } finally {
            reentrantLock.unlock();
        }
    }

    public void queryCount() {
        System.out.println("此时count值为:" + count);
    }
}

  如下为运行结果,可以看到效果和使用synchronized一致。如果synchronized关键字能满足用户的需求,就用synchronized,因为它能简化代码 。如果需要更高级的功能,就用ReentrantLock类,但一定要注意及时释放锁,否则会出现死锁,通常在finally{}代码释放锁。

output: count增加了6
此时count值为:6
add over>>>>>>>>>>>>>>>>>>>>>>

output:count减少了6
此时count值为:0
reduce over>>>>>>>>>>>>>>>>>>>

output: count增加了6
此时count值为:6
add over>>>>>>>>>>>>>>>>>>>>>>

output: count增加了6
此时count值为:12
add over>>>>>>>>>>>>>>>>>>>>>>
  1. 使用线程局部变量实现同步:如果使用ThreadLocal管理变量,则每一个使用该变量的线程都获得该变量的副本,副本之间相互独立,这样每一个线程都可以随意修改自己的变量副本,而不会对其他线程产生影响。
      ThreadLocal 类的常用方法
      ThreadLocal() : 创建一个线程本地变量
      get() : 返回此线程局部变量的当前线程副本中的值
      initialValue() : 返回此线程局部变量的当前线程的"初始值"
      set(T value) : 将此线程局部变量的当前线程副本中的值设置为value
    对应的Digit.java修改为:
class Digit {
    private static ThreadLocal<Integer> count = new ThreadLocal<Integer>() {
        @Override
        protected Integer initialValue() {
            return 0;
        }
    };

    public void addCount(int value) {
        count.set(count.get() + value);
        System.out.println("output: count增加了" + value);
    }

    public void reduceCount(int value) {
        if (count.get() - value < 0) {
            System.out.println("count不能为负。");
            return;
        }
        count.set(count.get() - value);
        System.out.println("output:count减少了" + value);
    }


    public void queryCount() {
        System.out.println("此时count值为:" + count.get());
    }
}

此时的运行结果为:

count不能为负。
此时count值为:0
reduce over>>>>>>>>>>>>>>>>>>>

output: count增加了6
此时count值为:6
add over>>>>>>>>>>>>>>>>>>>>>>

count不能为负。
此时count值为:0
reduce over>>>>>>>>>>>>>>>>>>>

output: count增加了6
此时count值为:12
add over>>>>>>>>>>>>>>>>>>>>>>

  但是仔细观察不难发现,使用ThreadLocal时,只让加不让减,这是为什么呢?先来看看ThreadLocal的原理:
如果使用ThreadLocal管理变量,则每一个使用该变量的线程都获得该变量的副本,副本之间相互独立,这样每一个线程都可以随意修改自己的变量副本,而不会对其他线程产生影响。现在明白了吧,原来每个线程运行的都是一个副本,也就是说加数和减数是两个不同的统计变量,只是名字相同而已。所以就会发生上面的效果。
引申
ThreadLocal与同步机制
a.ThreadLocal与同步机制都是为了解决多线程中相同变量的访问冲突问题;
b.前者采用以"空间换时间"的方法,后者采用以"时间换空间"的方式 。
6. wait()和notify():
  wait():使一个线程处于等待状态,并且释放所持有的对象的lock。
  sleep():使一个正在运行的线程处于睡眠状态,是一个静态方法,调用此方法要捕捉InterruptedException异常。
  notify():唤醒一个处于等待状态的线程,注意的是在调用此方法的时候,并不能确切的唤醒某一个等待状态的线程,而是由JVM确定唤醒哪个线程,而且不是按优先级。

详见:Java基础—wait(),sleep(),notify()和notifyAll()方法详解

7. 使用阻塞队列实现线程同步:使用LinkedBlockingQueue<E>来实现线程的同步 LinkedBlockingQueue<E>是一个基于已连接节点的,范围任意的blocking queue。 队列是先进先出的顺序(FIFO,LinkedBlockingQueue 类常用方法 LinkedBlockingQueue() : 创建一个容量为Integer.MAX_VALUE的LinkedBlockingQueue put(E e) : 在队尾添加一个元素,如果队列满则阻塞 size() : 返回队列中的元素个数 take() : 移除并返回队头元素,如果队列空则阻塞代码。
  注:BlockingQueue<E>定义了阻塞队列的常用方法,尤其是三种添加元素的方法,我们要多加注意,当队列满时:
  add()方法会抛出异常
  offer()方法返回false
  put()方法会阻塞

【LaTeX PPT模板集】Beamer主题与配色使用教程 阅读详情

相关推荐

IDEA系列(四):IDEA导入Maven工程项目并配置Tomcat启动

本片文章主要介绍如何导入一个存在的maven项目并配置Tomcat启动项目,方便自己使用,也分享出来,希望可以帮助到其他人。

低调D树苗的博客 1243

java 同步线程方式

Java并发编程包含三个基本概念: 原子性:一(多)个操作要么全部执行要么不执行,中途不会被打断; 可见性:一个线程对某变量的修改对其他线程来说是可见的,即能知道值进行过修改; 有序性:程序执行按照代码的顺序执行; 1.同步方法 使用synchronized关键字 public static void main(String[] args){ Increase increase = new Increase(); int count = 10; whil

延成的博客 3277

解决Docker拉取镜像报错:Get https://registry-1.docker.io/v2/: 报错

解决Docker拉取镜像报错: Get “https://registry-1.docker.io/v2/”: net/http: request canceled while waiting for connection问题描述Get "https://registry-1.docker.io/v2/"是docker官方的镜像源,下载很慢的,一般会自己指定国内映射的加速镜像源。

qq_33650333的博客 4172

LinkedBlockingQueue 怎么实现的线程安全

实现线程安全的关键在于使用了锁和条件变量来控制对队列的访问。下面是 LinkedBlockingQueue内部使用了一个锁来控制对队列的并发访问。具体来说,它使用了两个不同的锁,一个是putLock,用于;另一个是takeLock,用于。这种分离锁的设计可以。使用了两个条件变量notFull和notEmpty来实现阻塞式的添加和移除元素操作。notFull;notEmpty。条件变量的使用使得线程可以在条件不满足时等待,在条件满足时被唤醒,从而实现了阻塞式的操作。

WaaTong的博客 980

Apk脱壳圣战之---脱掉“梆梆加固”的保护壳

一、前言 现如今Android用户的安全意识不是很强,又有一些恶意开发者利用应用的名字吸引眼球,包装一个恶意锁机收费的应用,在用户被骗的安装应用之后,立马手机锁机,需要付费方可解锁。这样的恶意软件是非常让人痛恨的。所以本文就用一个案例来分析如何破解这类应用,获取解锁密码,让被骗的用户可以找回爽快! 二、分析软件锁机原理 本文用的是一款叫做:安卓性能激活.apk,关于样本apk文件后面会...

尼古拉斯.赵四的博客 5060

stc8g1k08硬件iic读取aht20温湿度及0.91寸oled显示

使用stc8g1k08读取aht20温湿度及0.91寸oled显示。

qq_61688416的博客 2771

Java基础——线程同步

1.同步/异步编程 异步编程模型:t1线程与t2线程各自执行,两个线程之间互不干扰; 同步编程模型:t1线程与t2线程执行,当t1线程必须等t2线程结束之后才能执行,即同步编程模型。 2.什么时候进行同步,为什么引入同步机制 为了数据的安全性,尽管应用程序的使用率降低,但是为了保证数据是安全的,必须加入线程同步机制,线程同步机制使程序变成了(等同)单线程; 什么条件下使用线程同步:必须是...

Yuhang_Z的博客 301

数字IC设计(8)之AHB-Lite总线(持续更新)

AHB总线全称Advanced High-performance Bus,意为先进高性能总线。推出该总线的目的是补充APB总线在性能上的不足,APB总线适合挂载低速外设,所以AHB总线用来挂载高性能的外设,这样子可以分工合作,节约资源。AHB总线可以实现多主多从的连接,此时需要设计仲裁机和matrix(矩阵),AHB-Lite一般用于一主多从,此时则不需要仲裁机和matrix,但是也可以用AHB-Lite来实现多主多从,就是设计多个layer,每个layer是一主多从,再把各个layer连起来;

qq_69440734的博客 2255

java线程同步的实现方式

这里抛砖引玉,为何要使用同步? 当多个线程同时操作一个可共享的资源时会出现线程安全问题,将会导致数据不一致,因此使用同步锁来防止该操作执行完之前不许被其他线程执行,从而保证了该变量的唯一性和准确性。下面总结一些java线程实现同步方式,大致有下面几种: 1.同步方法 使用 synchronized关键字,可以修饰普通方法、静态方法,以及语句块。由于java的每个对象都有一个内置锁...

SlagSea 1万+

物联网应用开发教程专栏介绍与说明——列表目录查阅(持续更新)

为方便查阅学习本专栏,特整理专栏介绍与说明及提供列表目录便于查阅。阿齐Archie《物联网应用开发教程》专栏。本专栏为物联网应用开发教程,将更新涵盖常见的WIFI、蓝牙、蜂窝网、以太网、Lora、NB-iot等物联网技术实战应用及详解其技术知识。专栏为技术文章,订阅一个文章即可本专栏内所有文章可看,实战开发,亲测无误,附视频演示效果,会持续更新本专栏。本专栏内有理论篇也有完整实战应用篇。

嵌入式阿齐Archie 2万+

Docker 环境 Playwright MCP 部署全流程指南

Docker部署Playwright MCP全流程指南 本文详细介绍了使用Docker容器化部署Playwright MCP的完整流程,涵盖镜像拉取、容器启动、配置优化和问题排查。主要内容包括: 核心部署步骤:通过官方镜像快速搭建环境,支持自定义端口和数据持久化 关键配置要点:详解网络设置、持久化目录挂载和环境变量配置 服务验证方法:通过日志检查和服务连通性测试确保部署成功 客户端集成:提供VS Code等IDE的配置示例 常见问题解决:针对浏览器启动超时、跨域限制等问题给出解决方案 实战案例:演示自动化爬

勤奋的码农 1473

Java线程同步的几种方式

Java线程同步的几种方式 1.使用synchronized关键字  它的工作是对同步的代码加锁,使得每一次只能有一个线程进入同步块,从而保证线程间的安全性。  synchronized关键字的用法: (1)同步方法  即有synchronized修饰的方法,由于java的每个对象都有一个内置锁,当用此关键字修饰方法时,内置锁会保护整个方法。在调用该方法前,需要获得内置锁,否则就处于阻塞状态。代码如:public synchronized void save(){}。  synchronized也可以修饰静

loser与你 2276

中国石油招标投标网 — 中标候选人爬虫技术分析

复制真实浏览器的 TLS 握手参数(密码套件顺序、TLS 扩展、ALPN 等),绕过基于 JA3/JA3S 指纹的服务端检测。所有 API 请求体使用 RSA-1024 公钥加密,响应体使用私钥解密,HTTP body 全程为密文。库使用 urllib3 的默认 TLS 握手参数,与真实浏览器差异明显,易被 JA3 指纹检测工具识别。密钥隐藏在 CSS 文件中,这是一种隐蔽分发技术,防止简单的密钥抓取。切换次数越多,等待时间越长,最大 60 秒,避免被代理 API 限流。为空,也返回包含基础信息的空行。

猫敷雪 285

Java线程同步实现方式

【代码】Java线程同步实现方式。

bestwinner的专栏 426

复分析 阿尔福斯(Ahlfors) 第三版答案 英文

答案分为十章,有部分习题没有答案,全英文复分析阿尔福斯(Ahlfors) 第三版答案

汉书HE8,好用的汇编代码编写工具

汉书HE8,好用的汇编代码编写工具早期编写汇编代码所用功能强大

Java多线程:线程同步以及加锁的三种方式(同步代码块、同步方法、Lock锁)

解决线程安全问题的方案让多个线程先后依次访问共享资源,这样就解决了线程安全问题锁对象随便选择一个唯一的对象好不好呢不好,会影响其他无关线程的执行锁对象使用规范建议使用共享资源作为锁对象,对于实例方法建议使用this作为锁对象对于**静态方法建议使用字节码(类名.class)**对象作为锁对象。

后端小白个人学习记录 1929

计算机控制技术及应用

《计算机控制技术及应用》通过总结作者的科研成果和吸收国内外的先进理论、方法和技术,以计算机控制系统的网络化、开放化、智能化和集成化发展趋势为主线索,主要介绍计算机控制系统的基本概念和计算机控制系统的典型形式,计算机控制系统的数学描述与基本分析方法,以及数字PID控制器、最少拍控制系统、达林算法、状态变量设计法等设计方法及其各种改进算法。重点突出计算机控制系统输入输出通道接口的设计方法和典型控制系统的设计案例与实现方法等。

Java中常见的线程同步方式

在Java中,当多个线程同时访问共享资源时,为了防止数据不一致或损坏的问题,需要进行线程同步。

dusked 503

visio安装包

Microsoft Office Visio 是一款专业的制图软件。利用经过更新的形状、协作工具和数据链接图表,创建专业级图表以简化复杂的信息。

Java线程同步的方式

01 使用synchronized关键字 有synchronized关键字修饰的方法 有synchronized关键字修饰的语句块 注意:synchronized关键字用于保护“共享数据” 02 wait和notify wait() ——使线程处于等待状态,并且释放所持有对象的lock。 sl...

chiwu4496的博客 233

java之线程同步的三种方式

同步代码 1.语法结构 synchronized (同步监听对象) { 可能引发线程安全问题的代码 } 上面的结构相当于把{ }中的代码捆绑成一个整体,线程只能够一个一个的进来,执行完一个,下一个才能进来。 2.语法特点 上面的同步监听对象可以是任意的对象(必须确保大家用的是同一把锁); 保证所有的线程共享一个同步监听对象的;也就是保证被同步监听对象是被所有线程共享的。 很多时...

weixin_45433052的博客 185

Java线程同步方式

本文部分内容来自http://www.cnblogs.com/XHJT/p/3897440.html 为什么要使用同步?   Java与允许多线程并发控制,当多个线程同时操作共享变量时,将会导致数据的不准确,相互之间产生冲突,使用同步可以保证共享变量的准确性和唯一性。 1、同步代码块   使用synchronized修饰的语句块。   被修饰的语句块会自动加上内置锁,从而实现同步。 synchro...

weixin_43766090的博客 161

【Java第75集】java实现线程同步的方式详解

线程安全问题是我们每个Java后端开发必知必会的知识点,今天我们就一文搞定如何实现线程同步,细讲目前常见的实现线程同步的方式。

学不可以已,活到老,学到老。 1807

Java线程同步的四种方式详解(建议收藏)

Java线程同步属于Java多线程与并发编程的核心点,需要重点掌握,下面我就来详解Java线程同步的4种主要的实现方式@mikechen

mikechen的互联网架构 2518

Java线程同步的实现方式

当多个线程同时操作一个可共享的资源时会出现线程安全问题,将会导致数据不一致,因此使用同步锁来防止该操作执行完之前不许被其他线程执行,从而保证了该变量的唯一性和准确性。下面总结一些java线程实现同步方式,大致有下面几种: 1.同步方法 使用 synchronized关键字,可以修饰普通方法、静态方法,以及语句块。由于java的每个对象都有一个内置锁,当用此关键字修饰方法时, 内置锁会保护整个方法。在调用该方法前,需要获得内置锁,否则就处于阻塞状态。需要注意的是调用静态方法时,锁住的不是对象,锁住的是类。

young_qin的博客 429

java多线程同步方法_Java线程同步方法以及多线程实现方式

如果我们只知道线程同步使用synchronized关键字,多线程使用Thread类和Runnable接口,是远远不够的。下面我们来全面介绍一下这些:线程同步实现:1.使用synchronized关键字同步方法即有synchronized关键字修饰的方法。由于java的每个对象都有一个内置锁,当用此关键字修饰方法时,内置锁会保护整个方法。在调用该方法前,需要获得内置锁,否则就处于阻塞状态。代码如:p...

weixin_39831039的博客 176

java中什么是线程同步_关于java实现线程同步的方式有哪些

关于java实现线程同步的方式有哪些当使用多个线程来访问同一个数据时,非常容易出现线程安全问题,所以我们用同步机制来解决这些问题。那么,java实现线程同步的方式有哪些?下面百分网小编带大家一起来看看详细内容,希望对大家有所帮助!想了解更多相关信息请持续关注我们应届毕业生考试网!什么是线程同步?当使用多个线程来访问同一个数据时,非常容易出现线程安全问题(比如多个线程都在操作同一数据导致数据不一致)...

weixin_36272649的博客 694

Java线程同步的四种方式详解

当使用多个线程来访问同一个数据时,将会导致数据不准确,相互之间产生冲突,非常容易出现线程安全问题,如下图所示:比如多个线程都在操作同一数据,都打算修改商品库存,这样就会导致数据不一致的问题。线程同步的真实意思,其实是“排队”:几个线程之间要排队,一个一个对共享资源进行操作,而不是同时进行操作。所以我们用同步机制来解决这些问题,加入同步锁以避免在该线程没有完成操作之前,被其他线程的调用,从而保证了该变量的唯一性和准确性。

Huangjiazhen711的博客 358
上一篇: 经典55句
下一篇: 【maven】maven中的properties标签
少侠露飞
博客等级 码龄9年 214粉丝 117原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值