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,但是未初始化,所以会直接报错。

目录
相关文章
|
3天前
|
Java 数据库 Android开发
一个Android App最少有几个线程?实现多线程的方式有哪些?
本文介绍了Android多线程编程的重要性及其实现方法,涵盖了基本概念、常见线程类型(如主线程、工作线程)以及多种多线程实现方式(如`Thread`、`HandlerThread`、`Executors`、Kotlin协程等)。通过合理的多线程管理,可大幅提升应用性能和用户体验。
24 15
一个Android App最少有几个线程?实现多线程的方式有哪些?
|
6天前
|
Java 调度 开发者
Java并发编程:深入理解线程池
在Java的世界中,线程池是提升应用性能、实现高效并发处理的关键工具。本文将深入浅出地介绍线程池的核心概念、工作原理以及如何在实际应用中有效利用线程池来优化资源管理和任务调度。通过本文的学习,读者能够掌握线程池的基本使用技巧,并理解其背后的设计哲学。
|
5天前
|
Java 数据库 Android开发
一个Android App最少有几个线程?实现多线程的方式有哪些?
本文介绍了Android应用开发中的多线程编程,涵盖基本概念、常见实现方式及最佳实践。主要内容包括主线程与工作线程的作用、多线程的多种实现方法(如 `Thread`、`HandlerThread`、`Executors` 和 Kotlin 协程),以及如何避免内存泄漏和合理使用线程池。通过有效的多线程管理,可以显著提升应用性能和用户体验。
23 10
|
3天前
|
API Android开发 iOS开发
安卓与iOS开发中的线程管理对比
【9月更文挑战第12天】在移动应用的世界中,安卓和iOS平台各自拥有庞大的用户群体。开发者们在这两个平台上构建应用时,线程管理是他们必须面对的关键挑战之一。本文将深入探讨两大平台在线程管理方面的异同,通过直观的代码示例,揭示它们各自的设计理念和实现方式,帮助读者更好地理解如何在安卓与iOS开发中高效地处理多线程任务。
|
5天前
|
Java Android开发 开发者
安卓应用开发中的线程管理优化技巧
【9月更文挑战第10天】在安卓开发的海洋里,线程管理犹如航行的风帆,掌握好它,能让应用乘风破浪,反之则可能遭遇性能的暗礁。本文将通过浅显易懂的语言和生动的比喻,带你探索如何优雅地处理安卓中的线程问题,从基础的线程创建到高级的线程池运用,让你的应用运行更加流畅。
|
7天前
|
API Python
探索Python中的多线程编程
探索Python中的多线程编程
27 5
|
7天前
|
算法 Java 数据处理
Java并发编程:解锁多线程的力量
在Java的世界里,掌握并发编程是提升应用性能和响应能力的关键。本文将深入浅出地探讨如何利用Java的多线程特性来优化程序执行效率,从基础的线程创建到高级的并发工具类使用,带领读者一步步解锁Java并发编程的奥秘。你将学习到如何避免常见的并发陷阱,并实际应用这些知识来解决现实世界的问题。让我们一起开启高效编码的旅程吧!
|
9天前
|
Java 开发者
Java中的多线程编程基础与实战
【9月更文挑战第6天】本文将通过深入浅出的方式,带领读者了解并掌握Java中的多线程编程。我们将从基础概念出发,逐步深入到代码实践,最后探讨多线程在实际应用中的优势和注意事项。无论你是初学者还是有一定经验的开发者,这篇文章都能让你对Java多线程有更全面的认识。
15 1
|
12天前
|
存储 Ubuntu Linux
C语言 多线程编程(1) 初识线程和条件变量
本文档详细介绍了多线程的概念、相关命令及线程的操作方法。首先解释了线程的定义及其与进程的关系,接着对比了线程与进程的区别。随后介绍了如何在 Linux 系统中使用 `pidstat`、`top` 和 `ps` 命令查看线程信息。文档还探讨了多进程和多线程模式各自的优缺点及适用场景,并详细讲解了如何使用 POSIX 线程库创建、退出、等待和取消线程。此外,还介绍了线程分离的概念和方法,并提供了多个示例代码帮助理解。最后,深入探讨了线程间的通讯机制、互斥锁和条件变量的使用,通过具体示例展示了如何实现生产者与消费者的同步模型。
|
6天前
|
安全 Java UED
Java并发编程:解锁多线程的潜力
在Java的世界里,并发编程如同一场精心编排的交响乐,每个线程扮演着不同的乐手,共同奏响性能与效率的和声。本文将引导你走进Java并发编程的大门,探索如何在多核处理器上优雅地舞动多线程,从而提升应用的性能和响应性。我们将从基础概念出发,逐步深入到高级技巧,让你的代码在并行处理的海洋中乘风破浪。