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

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

多线程应用中,两个或两个以上的线程需要共享对同一个数据的存取。如果两个线程存取相同的对象,并且每一个线程都调用了修改该对象的方法,这种情况通常被称为竞争条件。而解决这种问题的办法通常是当线程A调用修改对象方法时,我们就交给它一把锁,等他处理完后在把锁给另一个要调用这个方法的线程。

重入锁和条件对象

synchronized 关键字提供了锁以及相关的条件。大多数需要显示锁的情况使用 synchronized 非常方便,但是等我们了解重入锁和条件对象时,能更好的理解 synchronized 关键字。重入锁 ReentrantLock 是Java se5.0引入的,就是支持重进入的锁,他表示锁能够支持一个线程对资源的重复加锁。

 Lock  lock = new ReentrantLock();
        try {
            ...
        }catch (Exception e){
            lock.unlock();
        }

Demo如下

class Test implements Runnable {
    private Boolean on;
    public Test(Boolean on) {
        this.on = on;
    }
    @Override
    public void run() {
        new MyClass().getData(on);
    }
}
public class MyClass {
    private static Lock lock;
    private static Condition condition;
    public static void main(String[] args) {
        lock = new ReentrantLock();
        //我们这个线程已经获取了锁,具有排他性,别的线程无法获取锁,此时我们需要引入条件对象
        //一个锁对象拥有多个相关的条件对象。
        //得到条件对象
        condition = lock.newCondition();
        Thread a = new Thread(new Test(true));
        Thread b = new Thread(new Test(false));
        a.start();
        b.start();
    }
    void getData(Boolean on) {
        lock.lock();
        System.out.println("我被锁住了");
        try {
            if (on) {
                System.out.println("我阻塞了,放弃锁");
                condition.await();
            }
             condition.signalAll();
            System.out.println("解除阻塞");
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
}

一旦一个线程调用await 方法,他就会进入该条件的等待集并处于阻塞状态,直到另一个线程调用了同一个条件的 signalAll 方法时为止。

当调用 singalAll 方法时并不是立即激活一个等待线程,他仅仅解除了等待线程的阻塞,以便这些线程能够在当前线程退出同步方法后,通过竞争实现对对象的访问。还有一个方法时 sinal ,它则是随机解除某个线程的阻塞,如果该线程任然不能运行,则再次被阻塞。 如果没有其他线程再次调用 singal ,那么系统就死锁了

同步方法

Java 的每一个对象都有一个内部锁,如果一个方法用 synchronized 关键字声明,那么对象的锁将保护整个方法。也就是说,要调用该方法,线程必须获得内部的对象锁。

 public synchronized  void method(){
    }
    等价于下面这个Demo
    Lock lock=new ReentrantLock();
    public void method(){
        try {
            ...
        }finally {
            lock.unlock();
        }
    }

对于上面的例子,我们可以用 synchronized 修饰getData ,而不是使用一个显示锁。内部对象锁只有一个相关条件, wait 方法将一个线程添加到等待集中, notifyAll 或者 notify 方法解除等待线程的阻塞状态。也就是说 wait相当于调用 await(), notifyAll 等价于 condition.signalAll(), 上面的Demo改为下面这样

class Test implements Runnable {
    private Boolean on;
    public Test(Boolean on) {
        this.on = on;
    }
    @Override
    public void run() {
        new MyClass().getData(on);
    }
}
public class MyClass {
    public static void main(String[] args) {
        Thread a = new Thread(new Test(true));
        Thread b = new Thread(new Test(false));
        a.start();
        b.start();
    }
   synchronized  void getData(Boolean on) {
        System.out.println("我被锁住了");
        try {
            while (on) {
                System.out.println("我阻塞了,放弃锁");
                wait();
            }
             notifyAll();
            System.out.println("解除阻塞");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

同步代码块

每一个java对象都有一个锁,线程可以调用同步方法来获得锁。还有一种机制可以获得锁,那就是使用一个同步代码块。

 synchronized (this){
 }

同步代码块是非常脆弱的,通常不推荐使用。一般实现同步最好使用 java.util.concurrent包下提供的类,比如阻塞队列。如果同步方法适合你的程序,那么请尽量使用 同步方法,这样可以减少编写代码的数量,减少出错的概率。如果特别需要使用Lock/Condition结构提供的独有特性时,才使用Lock/Condition.

volatile

有时仅仅为了读写一个或两个实例域就使用同步的话,显得开销过大;而volatile 关键字为实例域的同步访问提供了免锁的机制。如果声明一个域 为 volatile ,那么编译器和虚拟机就知道该域是可能被另一个线程并发更新的。

学习volatile之前,我们需要了解一下内存模型的相关概念以及并发编程中的3个特性:原子性,可见性,有序性

Java的内存模型

Java中的堆内存用来存储对象实例,堆内存是被所有线程共享的运行时内存区域,因此,他存在内存可见性的问题。而局部变量,方法定义的参数则不会再线程之间共享,他们不会有内存可见性的问题,也不受内存模型的影响。

Java内存模型定义了线程和主存之间的抽象关系:线程之间的共享变量存储在主存中,每一个线程都有一个私有的本地内存,本地内存中存储了该线程共享变量的副本需要注意的是本地内存是Java 内存模型的一个抽象概念,其实并不真实存在,它涵盖了缓存,写缓冲区,寄存器等区域。Java内存模型控制线程之间的通信,他决定一个线程对主存共享变量的写入核实对另一个线程可见。

image.png

线程A 和 线程B 之间若要通信的话,必须要经历下面两个步骤:

  1. 线程A把线程A本地内存中更新过的共享内存刷新到主存中去。
  2. 线程 B到主存中去读取线程A之前已更新过的共享变量。由此可见,如果我们执行下面的语句: i=3;

执行线程必须先在自己的工作线程中对变量 i 所在的缓存进行赋值操作,然后再写入主存中,而不是直接将数值3写入到主存中

目录
相关文章
|
7天前
|
Java 数据库 Android开发
一个Android App最少有几个线程?实现多线程的方式有哪些?
本文介绍了Android多线程编程的重要性及其实现方法,涵盖了基本概念、常见线程类型(如主线程、工作线程)以及多种多线程实现方式(如`Thread`、`HandlerThread`、`Executors`、Kotlin协程等)。通过合理的多线程管理,可大幅提升应用性能和用户体验。
25 15
一个Android App最少有几个线程?实现多线程的方式有哪些?
|
2天前
|
Java
深入理解Java中的多线程编程
本文将探讨Java多线程编程的核心概念和技术,包括线程的创建与管理、同步机制以及并发工具类的应用。我们将通过实例分析,帮助读者更好地理解和应用Java多线程编程,提高程序的性能和响应能力。
15 4
|
10天前
|
Java 调度 开发者
Java并发编程:深入理解线程池
在Java的世界中,线程池是提升应用性能、实现高效并发处理的关键工具。本文将深入浅出地介绍线程池的核心概念、工作原理以及如何在实际应用中有效利用线程池来优化资源管理和任务调度。通过本文的学习,读者能够掌握线程池的基本使用技巧,并理解其背后的设计哲学。
|
1天前
|
安全 Java 调度
Java 并发编程中的线程安全和性能优化
本文将深入探讨Java并发编程中的关键概念,包括线程安全、同步机制以及性能优化。我们将从基础入手,逐步解析高级技术,并通过实例展示如何在实际开发中应用这些知识。阅读完本文后,读者将对如何在多线程环境中编写高效且安全的Java代码有一个全面的了解。
|
9天前
|
Java 数据库 Android开发
一个Android App最少有几个线程?实现多线程的方式有哪些?
本文介绍了Android应用开发中的多线程编程,涵盖基本概念、常见实现方式及最佳实践。主要内容包括主线程与工作线程的作用、多线程的多种实现方法(如 `Thread`、`HandlerThread`、`Executors` 和 Kotlin 协程),以及如何避免内存泄漏和合理使用线程池。通过有效的多线程管理,可以显著提升应用性能和用户体验。
29 10
|
7天前
|
API Android开发 iOS开发
安卓与iOS开发中的线程管理对比
【9月更文挑战第12天】在移动应用的世界中,安卓和iOS平台各自拥有庞大的用户群体。开发者们在这两个平台上构建应用时,线程管理是他们必须面对的关键挑战之一。本文将深入探讨两大平台在线程管理方面的异同,通过直观的代码示例,揭示它们各自的设计理念和实现方式,帮助读者更好地理解如何在安卓与iOS开发中高效地处理多线程任务。
|
9天前
|
Java Android开发 开发者
安卓应用开发中的线程管理优化技巧
【9月更文挑战第10天】在安卓开发的海洋里,线程管理犹如航行的风帆,掌握好它,能让应用乘风破浪,反之则可能遭遇性能的暗礁。本文将通过浅显易懂的语言和生动的比喻,带你探索如何优雅地处理安卓中的线程问题,从基础的线程创建到高级的线程池运用,让你的应用运行更加流畅。
|
11天前
|
算法 Java 数据处理
Java并发编程:解锁多线程的力量
在Java的世界里,掌握并发编程是提升应用性能和响应能力的关键。本文将深入浅出地探讨如何利用Java的多线程特性来优化程序执行效率,从基础的线程创建到高级的并发工具类使用,带领读者一步步解锁Java并发编程的奥秘。你将学习到如何避免常见的并发陷阱,并实际应用这些知识来解决现实世界的问题。让我们一起开启高效编码的旅程吧!
|
1天前
|
安全 数据库连接 API
C#一分钟浅谈:多线程编程入门
在现代软件开发中,多线程编程对于提升程序响应性和执行效率至关重要。本文从基础概念入手,详细探讨了C#中的多线程技术,包括线程创建、管理及常见问题的解决策略,如线程安全、死锁和资源泄露等,并通过具体示例帮助读者理解和应用这些技巧,适合初学者快速掌握C#多线程编程。
11 0
|
10天前
|
安全 Java UED
Java并发编程:解锁多线程的潜力
在Java的世界里,并发编程如同一场精心编排的交响乐,每个线程扮演着不同的乐手,共同奏响性能与效率的和声。本文将引导你走进Java并发编程的大门,探索如何在多核处理器上优雅地舞动多线程,从而提升应用的性能和响应性。我们将从基础概念出发,逐步深入到高级技巧,让你的代码在并行处理的海洋中乘风破浪。