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多线程编程常用方式包括:继承Thread类、实现Runnable接口、Callable接口(可返回结果)及使用线程池。推荐线程池以提升性能,避免频繁创建线程。结合同步与通信机制,可有效管理并发任务。
244 6
|
6月前
|
存储 消息中间件 人工智能
【03】AI辅助编程完整的安卓二次商业实战-本地构建运行并且调试-二次开发改注册登陆按钮颜色以及整体资源结构熟悉-优雅草伊凡
【03】AI辅助编程完整的安卓二次商业实战-本地构建运行并且调试-二次开发改注册登陆按钮颜色以及整体资源结构熟悉-优雅草伊凡
230 3
|
8月前
|
Java API 微服务
为什么虚拟线程将改变Java并发编程?
为什么虚拟线程将改变Java并发编程?
396 83
|
8月前
|
安全 算法 Java
Java 多线程:线程安全与同步控制的深度解析
本文介绍了 Java 多线程开发的关键技术,涵盖线程的创建与启动、线程安全问题及其解决方案,包括 synchronized 关键字、原子类和线程间通信机制。通过示例代码讲解了多线程编程中的常见问题与优化方法,帮助开发者提升程序性能与稳定性。
356 0
|
6月前
|
存储 消息中间件 人工智能
【05】AI辅助编程完整的安卓二次商业实战-消息页面媒体对象(Media Object)布局实战调整-按钮样式调整实践-优雅草伊凡
【05】AI辅助编程完整的安卓二次商业实战-消息页面媒体对象(Media Object)布局实战调整-按钮样式调整实践-优雅草伊凡
219 11
【05】AI辅助编程完整的安卓二次商业实战-消息页面媒体对象(Media Object)布局实战调整-按钮样式调整实践-优雅草伊凡
|
6月前
|
存储 消息中间件 人工智能
【08】AI辅助编程完整的安卓二次商业实战-修改消息聊天框背景色-触发聊天让程序异常终止bug牵涉更多聊天消息发送优化处理-优雅草卓伊凡
【08】AI辅助编程完整的安卓二次商业实战-修改消息聊天框背景色-触发聊天让程序异常终止bug牵涉更多聊天消息发送优化处理-优雅草卓伊凡
461 10
【08】AI辅助编程完整的安卓二次商业实战-修改消息聊天框背景色-触发聊天让程序异常终止bug牵涉更多聊天消息发送优化处理-优雅草卓伊凡
|
6月前
|
存储 消息中间件 人工智能
【04】AI辅助编程完整的安卓二次商业实战-寻找修改替换新UI首页图标-菜单图标-消息列表图标-优雅草伊凡
【04】AI辅助编程完整的安卓二次商业实战-寻找修改替换新UI首页图标-菜单图标-消息列表图标-优雅草伊凡
424 4
|
6月前
|
XML 存储 Java
【06】AI辅助编程完整的安卓二次商业实战-背景布局变更增加背景-二开发现页面跳转逻辑-替换剩余图标-优雅草卓伊凡
【06】AI辅助编程完整的安卓二次商业实战-背景布局变更增加背景-二开发现页面跳转逻辑-替换剩余图标-优雅草卓伊凡
173 3
【06】AI辅助编程完整的安卓二次商业实战-背景布局变更增加背景-二开发现页面跳转逻辑-替换剩余图标-优雅草卓伊凡
|
10月前
|
机器学习/深度学习 消息中间件 存储
【高薪程序员必看】万字长文拆解Java并发编程!(9-2):并发工具-线程池
🌟 ​大家好,我是摘星!​ 🌟今天为大家带来的是并发编程中的强力并发工具-线程池,废话不多说让我们直接开始。
389 0
|
5月前
|
Java 调度 数据库
Python threading模块:多线程编程的实战指南
本文深入讲解Python多线程编程,涵盖threading模块的核心用法:线程创建、生命周期、同步机制(锁、信号量、条件变量)、线程通信(队列)、守护线程与线程池应用。结合实战案例,如多线程下载器,帮助开发者提升程序并发性能,适用于I/O密集型任务处理。
511 0