Android多线程编程__同步(下)

简介: 在多线程应用中,两个或两个以上的线程需要共享对同一个数据的存取。

原子性

对基本数据类型的变量的读取和赋值时原子性操作,即这些操作是不可以被中断的,要么执行完毕,要么就不执行。看一下下面的代码,如下:

x=3;    //语句1
y=x;    //语句2
x++;    //语句3

在上面3个语句中,只有语句1是原子性操作,其他两个语句都不是原子性操作。

语句2虽说很短,但他包含了两个操作,它先读取x的值,在将x的值写入工作内存。读取x的值以及将x的值写入工作内存这两个操作单拿出来都是原子性操作,但是合起来就不是原子性操作了。

语句3包含3个操作: 读取x的值,对x的值进行+1,向工作内存写入新值。

所以,当一个语句包含多个操作时,就不是原子性操作,只有简单的读取和赋值(将数字赋给某个变量)才是原子性操作。

可见性

可见性,是指线程之间的可见性,一个线程修改的状态对另一个线程是可见的。也就是一个线程修改的结果 ,另一个线程马上就可以看到。当一个共享变量被 volatie修饰时,它会保证修改的值立即被更新到主存,所以随其他线程是可见的。当有其他线程需要读取该值时,其他线程会去主存中读取新值。而普通的共享变量不能保证可见性,因为普通共享变量被修改之后,并不会立即写入主存,何时被写入主存也是不确定的。当其他线程去读取该值时,此时主存中可能还是原来的旧值,这样就无法保证可见性。

有序性

Java内存模型允许编译器和处理器对指令进行重排序,虽然重排过程不会影响到单线程执行的正确性,但是会影响到多线程并发执行的正确性这时可以通过 valatie 来保证有序性,除了 voliatie ,也可以通过 synchronized 和 Lock 来保证有序性。syncheonized 和 Lock 保证每个时刻只有一个线程执行同步代码,这相当于让线程顺序执行同步代码,从而保证了有序性。

Volatile 关键字

当一个共享变量被 volatile 修饰之后,其就具备了两个含义,一个是线程修改了变量的值时,变量的新值对其他线程是立即可见的。换句话说,就是不同线程对这个变量进行操作时具有可见性。另一个含义是禁止使用指令重排序。

看下面这个Demo

假设线程1先执行,线程2后执行

  //线程1
        boolean stop=false;
        while (!stop){
        }
        //线程2
        stop=true;

上面这段代码可以用于中断线程,但是这段代码不一定会将线程中断。虽然概率很小。

为什么说有可能无法中断线程呢? 每个线程在运行时都有私有的工作内存,因此线程1在运行时会将stop变量的值复制一份放在私有的工作内存中。当线程2更改了Stop变量的值后,线程2突然需要去做其他的操作,这时就无法将更改的Stop变量写入到主存中,这样线程1就不会知道线程2对Stop变量进行了更改,因此线程1就会一直循环下去。当Stop用volatile修饰之后,那么情况就变的不同了,当线程2进行修改时,会强制将修改的值立即写入主存,并且会导致线程1的工作内存中变量stop的缓存无效,这样线程1再次读取stop的值时就会去主存读取。

volatile不保证原子性

我们知道 volatile 保证了操作的可见性,但是是否能保证原子性呢?

class A{
    public volatile int a=0;
    public   void setA(){
        a++;
    }
}
public class  MyClass{
    public static void main(String[] args) {
        final A a=new A();
        for (int i=0;i<10;i++){
            new Thread(new Runnable() {
                @Override
                public void run() {
                    for (int i=0;i<1000;i++){
                        a.setA();
                    }
                }
            }).start();
        }
        //如果有子线程就让出资源,保证所有子线程都执行完
        while (Thread.activeCount()>2){
            Thread.yield();
        }
        System.out.println(a.a);
    }
}

这段代码每次运行,结果都不一致。因为自增不具备原子性,它包括读取变量的原始值,进行+1,写入工作内存。也就是说,自增操作的3个子操作可能会分隔开执行。假如某个时刻变量inc的值为9,线程1对变量进行自增操作,线程1先读取了变量inc的原始值,然后线程1被阻塞了。之后线程2对变量进行自增操作,线程2也去读取变量inc的原始值,然后进行加1操作,并把10写入工作内存,最后写入主存。随后线程1接着进行+1操作,因为线程1在此前已经读取了 inc 的值为9,所以不会再去主存读取最新的数值,线程1对 inc 进行+1操作后 inc 的值为10,然后将10 写入工作内存,最后写入主存。两个线程分别对 inc 进行了一次自增操作后,inc 的值只增加了1,因此自增操作不是原子性操作, volatile也无法保证对变量的操作是原子性的。

volatile保证有序性

volatile关键字能禁止指令重排序,因此 volatile 能保证有序性。volatile 关键字禁止指令重排序有两个含义:一个是当程序执行 volatile 变量的操作时,在其前面的操作已经全部执行完毕,并且结果会对后面的操作可见,在其后面的操作还没有进行,在进行指令优化时,在 volatile 变量之前的语句不能在 volatile 变量后面执行;同样,在volatile 变量之后的语句也不能在 volatile变量前面执行

正确使用volatile 关键字

synchronized关键字可防止多个线程同时执行一段代码,那么这就会很影响程序执行效率。而 volatile 关键字在某些情况下的性能要优于 synchronized 。但是要注意 volatile 关键字是无法替代 synchronized 关键字的,因为 volatile 关键字无法保证操作的原子性。通常来说,使用 volatile 必须具备以下两个条件:

  1. 对变量的写操作不会依赖于当前值
  2. 对变量没有包含在具有其他变量的不变式中。

第一个条件就是不能是自增,自减等操作,因为 volatile不保证原子性。

volatile使用场景

1.状态标识

 volatile  boolean on;
    public void setOn(){
        on=true;
    }
    public void Print(){
        while(!on){
            System.out.println("123");
        }
    }

2.双重检查模式 (DCL)  

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

在上面的单例模式中,为什么要用valoatile 修饰?

因为 instance=new Singleton(),并非是一个原子操作,事实上在 JVM中这句话大概做了3件事

  1. 给 instance 分配内存
  2. 调用 Singletion 的构造函数来初始化成员变量
  3. 将 instance 对象指向分配的内存空间。(执行完这步 instance 就为 非null 了)

但是JVM 的即时编译器中存在指令重排序的优化,也就是说上面的第二步和第三步顺序是不确定的,一旦2,3,顺序乱了,这时候有另一个线程调用了方法,结果虽然非null,但是未初始化,所以会直接报错。

目录
相关文章
|
15天前
|
安全 数据处理 开发者
Python中的多线程编程:从入门到精通
本文将深入探讨Python中的多线程编程,包括其基本原理、应用场景、实现方法以及常见问题和解决方案。通过本文的学习,读者将对Python多线程编程有一个全面的认识,能够在实际项目中灵活运用。
|
6天前
|
安全 程序员 API
|
3天前
|
存储 设计模式 分布式计算
Java中的多线程编程:并发与并行的深度解析####
在当今软件开发领域,多线程编程已成为提升应用性能、响应速度及资源利用率的关键手段之一。本文将深入探讨Java平台上的多线程机制,从基础概念到高级应用,全面解析并发与并行编程的核心理念、实现方式及其在实际项目中的应用策略。不同于常规摘要的简洁概述,本文旨在通过详尽的技术剖析,为读者构建一个系统化的多线程知识框架,辅以生动实例,让抽象概念具体化,复杂问题简单化。 ####
|
4天前
|
Java 开发者
在Java多线程编程的世界里,Lock接口正逐渐成为高手们的首选,取代了传统的synchronized关键字
在Java多线程编程的世界里,Lock接口正逐渐成为高手们的首选,取代了传统的synchronized关键字
22 4
|
4天前
|
消息中间件 供应链 Java
掌握Java多线程编程的艺术
【10月更文挑战第29天】 在当今软件开发领域,多线程编程已成为提升应用性能和响应速度的关键手段之一。本文旨在深入探讨Java多线程编程的核心技术、常见问题以及最佳实践,通过实际案例分析,帮助读者理解并掌握如何在Java应用中高效地使用多线程。不同于常规的技术总结,本文将结合作者多年的实践经验,以故事化的方式讲述多线程编程的魅力与挑战,旨在为读者提供一种全新的学习视角。
24 3
|
5天前
|
安全 Java 调度
Java中的多线程编程入门
【10月更文挑战第29天】在Java的世界中,多线程就像是一场精心编排的交响乐。每个线程都是乐团中的一个乐手,他们各自演奏着自己的部分,却又和谐地共同完成整场演出。本文将带你走进Java多线程的世界,让你从零基础到能够编写基本的多线程程序。
17 1
|
9天前
|
缓存 Java 调度
Java中的多线程编程:从基础到实践
【10月更文挑战第24天】 本文旨在为读者提供一个关于Java多线程编程的全面指南。我们将从多线程的基本概念开始,逐步深入到Java中实现多线程的方法,包括继承Thread类、实现Runnable接口以及使用Executor框架。此外,我们还将探讨多线程编程中的常见问题和最佳实践,帮助读者在实际项目中更好地应用多线程技术。
17 3
|
11天前
|
监控 安全 Java
Java多线程编程的艺术与实践
【10月更文挑战第22天】 在现代软件开发中,多线程编程是一项不可或缺的技能。本文将深入探讨Java多线程编程的核心概念、常见问题以及最佳实践,帮助开发者掌握这一强大的工具。我们将从基础概念入手,逐步深入到高级主题,包括线程的创建与管理、同步机制、线程池的使用等。通过实际案例分析,本文旨在提供一种系统化的学习方法,使读者能够在实际项目中灵活运用多线程技术。
|
9天前
|
缓存 安全 Java
Java中的多线程编程:从基础到实践
【10月更文挑战第24天】 本文将深入探讨Java中的多线程编程,包括其基本原理、实现方式以及常见问题。我们将从简单的线程创建开始,逐步深入了解线程的生命周期、同步机制、并发工具类等高级主题。通过实际案例和代码示例,帮助读者掌握多线程编程的核心概念和技术,提高程序的性能和可靠性。
10 2
|
10天前
|
Java
Java中的多线程编程:从基础到实践
本文深入探讨Java多线程编程,首先介绍多线程的基本概念和重要性,接着详细讲解如何在Java中创建和管理线程,最后通过实例演示多线程的实际应用。文章旨在帮助读者理解多线程的核心原理,掌握基本的多线程操作,并能够在实际项目中灵活运用多线程技术。