了解Java多线程的可见性与有序性

了解Java多线程的可见性与有序性

可见性

在Java多线程中,可见性问题是指当多个线程访问共享数据时,其中一个线程对数据进行了修改,导致其他线程无法立即看到这个修改的结果。

原因

可见性问题的产生是因为java内存模型中存在主内存和工作内存的缓存机制,不同的线程可能会将共享数据拷贝到自己的工作内存中进行修改,修改后的结果,在没有及时写回主内存的情况下,其他线程是看不到的。

解决方案

Java提供了volatile关键字来保证多线程间共享变量的可见性。

被volatile修饰的变量,操作之后都会立即写入主内存,而其他线程在进行操作之前,会先访问主内存中的这个变量,从而保证操作都是对主内存的同一个变量进行的,因此能够避免了可见性问题的发生。

public class volatileDemo {

    public volatile boolean flag = false;
    public void setFlag(){
        flag = true;
    }
    public void printFlag() {
        System.out.println("flag:" + flag);
    }

    public static void main(String[] args) {
        final volatileDemo demo = new volatileDemo();
        new Thread(new Runnable() {
            public void run() {
                demo.setFlag();
            }
        }).start();
        new Thread(new Runnable() {
            public void run() {
                while(!demo.flag) {
                    System.out.println("flag is false.");
                    Thread.yield();
                }
                System.out.println("flag is true.");
            }
        }).start();
    }
}

示例说明

假设有两个线程T1、T2,并且他们共享了一个布尔类型的变量flag,初始值均为false。

在T1线程中,调用setFlag()方法将flag设置为true。

在T2线程中,通过自旋的方式(即反复执行一个简单的操作等待目标操作完成)来读取flag的值,如果值为false,就继续等待,一旦值为true,就输出flag is true.的语句。

如果没有使用volatile修饰flag的话,T2线程可能永远看不到T1线程所做的修改,因为T2线程所读取的flag是从自己的工作内存中获取的。而使用volatile修饰flag的话,相当于保证了T2线程所读取的flag是从主内存中获取的,因此能够保证可见性的问题。

有序性

Java内存模型中的有序性,是指JMM规定的代码执行顺序和程序员编写代码时期望的有序执行顺序不同的问题。

原因

由于JMM允许编译器和处理器对指令进行重排序的优化,因此可能会导致程序员编写的代码执行顺序和实际执行顺序不一致的问题。

例如,代码A中包含修改共享变量和判断共享变量的操作,如果编译器或处理器对代码进行重排序,可能会导致先判断共享变量再修改共享变量的情况出现,因此就会导致程序出现错误的结果。

解决方案

Java为程序员提供了很多手段来保证多线程的有序性:

synchronized

可以使用synchronized关键字来保证多线程的有序性,synchronized具有原子性、可见性、有序性等特性,能够避免编译器和处理器对指令进行重排序的优化。

volatile

被volatile修饰的变量能够保证线程之间的有序性,每个线程在访问volatile变量之前,都会先刷新本地内存中的变量,保证所有的线程都能够访问到最新的变量值。

final关键字

final关键字在多线程中也具有有序性,final域的初始化不会被重排序,并且对于所有线程可见,因此能够保持有序性。

public class Singleton {

    private static volatile Singleton instance;
    private Singleton() {}

    public static Singleton getInstance() {
        if (instance == null) {
            synchronized(Singleton.class) {
                if (instance == null) {
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}

示例说明

在单例模式中,为了确保线程安全性,getInstance方法需要保证只能同时一个线程进入到创建实例的阶段。

如果不使用volatile修饰instance的话,可能会出现重排序的问题,比如初始化对象的时候分3个步骤:1)分配对象的内存空间;2)初始化对象;3)将对象指向刚分配的内存地址。但是由于2和3的顺序不需要保证,因此可能会出现先将对象指向地址,但是还没有初始化的情况,这样会导致其他访问instance的线程得到的是一个还没初始化的实例,从而导致程序出错。

而使用volatile修饰instance的话,能够避免这种重排序的问题,能够保证有序性的正确性。

本站文章如无特殊说明,均为本站原创,如若转载,请注明出处:了解Java多线程的可见性与有序性 - Python技术站

(0)
上一篇 2023年5月17日
下一篇 2023年5月17日

相关文章

  • 【java 多线程】守护线程与非守护线程的详解

    Java多线程:守护线程与非守护线程的详解 什么是守护线程? 在Java多线程中,守护线程是一种在后台运行的线程,它不会阻止程序的结束,也不会执行任何没有被其他非守护线程阻止的操作。 换句话说,当程序中最后一个非守护线程结束时,JVM会强制退出来,即使守护线程还在运行。 如何创建守护线程? 可以通过Thread类的setDaemon()方法来创建守护线程,示…

    多线程 2023年5月17日
    00
  • C#多线程之线程池ThreadPool用法

    C#多线程之线程池ThreadPool用法 线程池ThreadPool是什么 在程序运行过程中,有时会出现需要进行并发处理的情况。与传统的线程操作(Thread类)相比,线程池可以更好地管理线程资源,提高线程的复用率,避免了频繁创建和销毁线程的开销,从而提高了程序的性能和稳定性。 线程池通过预先创建一组线程并维护这些线程,让它们在没有工作时处于等待状态,一旦…

    多线程 2023年5月16日
    00
  • java基于C/S结构实现多线程聊天室

    Java基于C/S结构实现多线程聊天室的攻略如下: 确定需求和功能点 首先需要明确聊天室的功能,如聊天、发送文件、创建和加入房间等。然后确定需要实现的功能点,例如登录验证、用户管理、消息广播等。 选择合适的框架和技术 选择适合的框架和技术可以提高开发效率和应用性能。Java中可以选择基于Socket和ServerSocket类实现TCP连接,或者使用Nett…

    多线程 2023年5月16日
    00
  • springboot内置的tomcat支持最大的并发量问题

    当使用Spring Boot时,自带Tomcat作为默认的Web服务器,但Tomcat的并发限制可能会在某些情况下成为瓶颈。在这里,我们将讲解如何配置Tomcat以支持更大的并发量。 1. 增加Tomcat的线程数 默认情况下,Spring Boot内置的Tomcat服务器使用200个线程作为最大并发数。如果需要更多的并发请求可以使用以下方式增加Tomcat…

    多线程 2023年5月17日
    00
  • Python使用asyncio包处理并发详解

    当今网络服务越来越注重并发访问的处理,常见的异步框架有 gevent, twisted, tornado等,而作为一个优秀的异步框架,Python的asyncio更是备受关注。Asyncio 是 Python 3.4 新增的异步IO模块,它提供了基于协程的异步编程方式,使得异步编程更加易用、高效、可控。 下面我们来详细介绍Python中使用asyncio包进…

    多线程 2023年5月17日
    00
  • C# 多线程学习之基础入门

    关于”C#多线程学习之基础入门”的攻略,我可以从以下几个部分进行讲解: 一、多线程的基础概念 多线程指的是在一个应用程序中同时运行多个线程,它们可以同时进行不同的任务。在C#中,要实现多线程的操作,需要用到System.Threading命名空间下的相关类。在多线程的编程中,常用的概念包括线程的状态、互斥锁、信号量等。 二、创建和启动线程 在C#中,可以通过…

    多线程 2023年5月17日
    00
  • Java多线程之锁学习(增强版)

    Java多线程之锁学习(增强版)攻略 什么是锁? 锁是一种同步机制,用于协调对共享资源的访问。在Java中,可以使用synchronized关键字或Lock接口来实现锁。 synchronized锁 synchronized关键字可以修饰方法和代码块。当多个线程访问被synchronized修饰的方法或代码块时,只有一个线程可以执行,其他线程需要等待。 修饰…

    多线程 2023年5月16日
    00
  • Java并发编程之阻塞队列(BlockingQueue)详解

    Java并发编程之阻塞队列(BlockingQueue)详解 什么是阻塞队列? 阻塞队列,顾名思义就是在队列的基础上加入了阻塞的特性。当队列满时,阻塞队列会自动阻塞写入线程,直到队列中有元素被移除,而当队列为空时,阻塞队列会自动阻塞读取线程,直到队列中有元素被添加。 Java中的阻塞队列是一个线程安全的队列,实现了如同锁的机制,可以保证多个线程同时访问是安全…

    多线程 2023年5月16日
    00
合作推广
合作推广
分享本页
返回顶部