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写入到主存中

目录
相关文章
|
5天前
|
存储 安全 Java
Java中的线程安全与同步技术
Java中的线程安全与同步技术
|
1天前
|
Java 数据处理 调度
Java多线程编程入门指南
Java多线程编程入门指南
|
2天前
|
Java 调度
Java多线程编程与并发控制策略
Java多线程编程与并发控制策略
|
2天前
|
安全 Java 开发者
Java并发编程:理解并发与多线程
在当今软件开发领域,Java作为一种广泛应用的编程语言,其并发编程能力显得尤为重要。本文将深入探讨Java中的并发编程概念,包括多线程基础、线程安全、并发工具类等内容,帮助开发者更好地理解和应用Java中的并发特性。
6 1
|
3天前
|
监控 Java 调度
Java并发编程:深入理解线程池
【6月更文挑战第26天】在Java并发编程的世界中,线程池是提升应用性能、优化资源管理的关键组件。本文将深入探讨线程池的内部机制,从核心概念到实际应用,揭示如何有效利用线程池来处理并发任务,同时避免常见的陷阱和错误实践。通过实例分析,我们将了解线程池配置的策略和对性能的影响,以及如何监控和维护线程池的健康状况。
7 1
|
4天前
|
数据采集 Java Unix
10-多线程、多进程和线程池编程(2)
10-多线程、多进程和线程池编程
|
4天前
|
安全 Java 调度
10-多线程、多进程和线程池编程(1)
10-多线程、多进程和线程池编程
|
1天前
|
安全 Java 数据处理
Android多线程编程实践与优化技巧
Android多线程编程实践与优化技巧
|
1天前
|
并行计算 安全 Java
多线程编程中的线程安全问题与解决方案*
多线程编程中的线程安全问题与解决方案*
|
1天前
|
安全 Java 开发者
Java并发编程中的线程安全策略
在现代软件开发中,Java语言的并发编程特性使得多线程应用成为可能。然而,随着线程数量的增加,如何确保数据的一致性和系统的稳定性成为开发者面临的挑战。本文将探讨Java并发编程中实现线程安全的几种策略,包括同步机制、volatile关键字的使用、以及java.util.concurrent包提供的工具类,旨在为Java开发者提供一系列实用的方法来应对并发问题。
8 0