Android 线程 (1)线程与线程安全

目录

  • 前言
  • 正文
  • 线程的使用
  • 线程安全
    • 关键字与函数
  • 总结

前言

在日常开发中,我们难免涉及多线程的使用,同时线程安全的问题也不得不去关心。在这里对我理解的一些线程安全的知识做一些总结。

正文

在Android程序中进程、线程的区别,及多线程、线程安全的概念:

  • 进程:指的是承载Android程序的Linux系统,为每一个运行的Android程序分配的一个独立的运行空间。每一个Android程序运行时都会新建一个Dalvik虚拟机,而每一个Dalvik虚拟机又在Linux系统中有一个唯一标识PID。所以我们可以理解为进程实则是为每一个虚拟机中的逻辑块打上唯一标识。一个程序运行时只有一个进程。
  • 线程:指的是进程中的线性代码块。由于逻辑代码在执行时都有先后顺序,依次执行的,我们将这种线性结构程序俗称为线程。每一个有逻辑性的程序运行时都至少会有一条线程去执行其代码块。
  • 多线程:由于硬件性能的不断提升,硬件可以同时执行不同的业务逻辑,所以多线程的概念也就应运而生。
  • 线程安全:由于多线程的执行往往会涉及同时对一些共有资源(如:全局变量)的获取或变更,如果不同线程同时去获取或更改相同的资源,就会造成 需要获取的资源已经被另一个线程改变但获取到的结果却是原始值或同一个内存中有两个值的情况,这样就会造成运行结果的混乱;对这种由线程原因引起的混乱,我们称之为线程不安全的(内存二义性);反之多线程执行的最终结果不会产生混乱我们称之为线程安全的(内存唯一性)。由此使用多线程时我们往往不得不考虑线程安全的问题。

线程的使用

在Android程序中,线程可以通过继承Thread类 子类重写run方法或实现Runnable接口中的run方法来实现;----------------- 巴拉巴拉一大堆,我发现尽是些废话。线程使用已经很熟悉了,就不细讲了,略过—

线程安全

这里只说我懂的,不说我了解的。

关键字与函数

  • synchronized
    该关键字译为同步,凡是用该关键字修饰的函数或代码块,就表示同一时间只能有一个线程去执行。
  1. 用该关键字修饰代码块时写作:synchronized(线程锁){代码块}。其中线程锁的部分是一个类的实例对象,代码块中就是我们需要同步的逻辑体。当有线程执行同步代码时,其它需要执行该代码块且持有相同线程锁的线程,会在同步代码之前阻塞,等到上一个线程执行完同步代码块释放掉占用的锁后,再由抢到锁的线程进入执行。这样依次执行就保证了内存的唯一性,也就是线程安全。
  2. 用该关键字修饰函数时,关键字必须在返回值之前,且不能用来修饰构造函数。如果函数被static修饰那么其线程锁为该类对象,如果函数没有被static修饰那么其线程锁为调用该方法的对象。如果线程进入同步代码后发现现有条件不允许继续执行后续代码,但又必须执行后续代码时,我们可以让该线程进入等待,直到条件允许后再继续执行,这里就涉及到后续要讲的 等待、睡眠等待、等待并延时唤醒、睡眠并延时唤醒、唤醒几个函数。
  3. 使用该关键字时需要注意:
    1. 通过 类名.this 获取到的是类对象,通过构造函数获取到的是该类对象的一个映射对象。总之其内存地址是不同的。如果两个函数,一个函数被static修饰一个没有,它们在不同线程但需要同步的代码中执行,那么这段代码任有可能是不安全的。例如:类A中两个同步方法,一个获取全员变量,一个改变全员变量,其中有一个方法被static修饰;线程B与线程C分别去调用这两个方法去改变或获取成员变量;如果他们中两个都用类的对象映射去调用获取或更改,那么这时线程是安全的;如果只是其中一个用类对象映射,另一用类对象去调用获取或更改的方法,那么这时线程是不安全的,因为两个方法用的锁是不同的。
    2. 由于一个对象只有一个内存地址,也就是说每个对象只有一把锁;如果多个同步代码都使用同一个对象锁,那么这些同步代码块有且只能有一个在运行中,其它的线程都会被阻塞,等待锁的释放;例如:有10个同步代码块使用同一个对象做锁,且这些代码块分散在不同方法中,有10个线程同时去分别调用这10个方法,那么只能有1个正常进入到同步代码中执行,其它9个线程会在同步代码之前等待锁的释放。这样就会造成内存资源的浪费,所以我们在做多线程的时候应尽量避免或减少使用同步关键字,或者尽量使用不同的对象锁,来实现线程安全的目的。
  • wait()
    该函数译为等待,其在Object类中与锁的机制配套实现,因此Object子类或其间接子类都可实现对象锁的机制,且wait函数必须在有关键字synchronized修饰的同步块中使用。其作用是使运行条件不足的线程进入等待状态,并释放掉对同步资源的占用,直到条件满足时有线程调用对象锁的唤醒机制或线程自身唤醒后继续执行。该函数有两个扩展函数wait(long millis)、wait(long millis,int nanos),这两个函数都实现了线程等待并延时唤醒的机制,前者的时间精度为秒,后者的时间精度为纳秒;这两个函数具体解释请看源码。这里的延时等待唤醒时间是不固定的,这是由于当线程被唤醒时任然需要去与其他同对象锁的线程抢夺执行权。
  • notify()与notifyAll()
    这两个函数译为 唤醒 和 唤醒所有。它们都实现了对该对象锁中等待线程的唤醒逻辑,不同的是前者只能随机唤醒该对象锁中一个等待的线程,后者则会唤醒全部该对象锁中等待的线程。被唤醒的线程并不会立即执行同步代码,其任然会与其他同对象锁的线程进行争夺执行权。
  • sleep()
    该函数译为 睡眠,作用是使线程进入睡眠阻塞状态。其为Thread类对Object类 阻塞等待机制的扩展,所以其底层也是实现的Object的阻塞等待机制,但又不必与同步关键字synchronized配合使用,让线程阻塞机制变得相对简便。其同时也支持wait()函数的其它两个函数的扩展,让其能实现延时睡眠唤醒的逻辑。因为方法被用static关键字声明,所以在使用时可以直接在线程中通过类名调用方法进行线程阻塞操作。sleep函数阻塞时不会释放掉内存占用,所以睡眠延时唤醒的时间是固定的。

注意:所有的线程阻塞操作都有可能抛出InterruptedException(中断异常)的异常指针,这是由于在线程阻塞的过程中,线程中断而不能继续执行导致的,所以我们在执行线程阻塞操作之前需要捕获并处理可能的异常状态。所有的线程阻塞操作都可被主动唤醒。

总结

以上就是我对线程及线程安全,还有其中的一些关键字的理解。当然还有一些其它的线程机制没有讲到如Looper的消息循环块等,因为不常用所以只是了解还没到懂的地步,所以就略过了。还有Android的UI线程的使用也不多说,因为不懂UI线程使用的,都不是好Android开发,我也就不叨叨叨了----在此感谢你的阅读!再会

你可能感兴趣的