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

目录
相关文章
|
5天前
|
Java 数据库 Android开发
【专栏】构建高效 Android 应用:探究 Kotlin 多线程优化策略
【4月更文挑战第27天】本文探讨了Kotlin在Android开发中的多线程优化,包括线程池、协程的使用,任务分解、避免阻塞操作以及资源管理。通过案例分析展示了网络请求、图像处理和数据库操作的优化实践。同时,文章指出并发编程的挑战,如性能评估、调试及兼容性问题,并强调了多线程优化对提升应用性能的重要性。开发者应持续学习和探索新的优化策略,以适应移动应用市场的竞争需求。
|
2月前
|
Java 云计算
Java多线程编程中的同步与互斥机制探析
在当今软件开发领域,多线程编程是一项至关重要的技能。本文将深入探讨Java中的同步与互斥机制,分析其在多线程环境下的应用及实现原理,帮助读者更好地理解并运用这一关键技术。
24 4
|
2月前
|
Python
在Python中,如何保证多个线程之间的同步?
在Python中,如何保证多个线程之间的同步?
24 4
|
2月前
|
Python
如何在Python中实现线程之间的同步和通信?
【2月更文挑战第17天】【2月更文挑战第51篇】如何在Python中实现线程之间的同步和通信?
|
2月前
|
Java 调度 Android开发
构建高效Android应用:探究Kotlin多线程编程
【2月更文挑战第17天】 在现代移动开发领域,性能优化一直是开发者关注的焦点。特别是在Android平台上,合理利用多线程技术可以显著提升应用程序的响应性和用户体验。本文将深入探讨使用Kotlin进行Android多线程编程的策略与实践,旨在为开发者提供系统化的解决方案和性能提升技巧。我们将从基础概念入手,逐步介绍高级特性,并通过实际案例分析如何有效利用Kotlin协程、线程池以及异步任务处理机制来构建一个更加高效的Android应用。
|
2月前
|
算法 调度 索引
什么是多任务和线程?用线程写的一个udp同步聊天器
什么是多任务和线程?用线程写的一个udp同步聊天器
30 0
|
8天前
|
监控 安全 Java
一文讲明白Java中线程与进程、并发与并行、同步与异步
一文讲明白Java中线程与进程、并发与并行、同步与异步
7 1
|
18天前
|
Java API 调度
安卓多线程和并发处理:提高应用效率
【4月更文挑战第13天】本文探讨了安卓应用中多线程和并发处理的优化方法,包括使用Thread、AsyncTask、Loader、IntentService、JobScheduler、WorkManager以及线程池。此外,还介绍了RxJava和Kotlin协程作为异步编程工具。理解并恰当运用这些技术能提升应用效率,避免UI卡顿,确保良好用户体验。随着安卓技术发展,更高级的异步处理工具将助力开发者构建高性能应用。
|
29天前
|
安全 Linux API
Android进程与线程
Android进程与线程
20 0