Java到Android 并发编程引气入门篇

2,118 阅读7分钟

Android的并发编程,即多线程开发,而Android的多线程开发模型也是源于Java中的多线程模型
所以本篇也会先讲一些Java中的多线程理念,再讲解具体涉及的类,最后深入Android中的并发场景和实践

并发

什么是并发

举个很简单的栗子,当你一边在撸撸撸,一边在看小视频,同时在做两件事,这就是并发。

咳,年轻人节制啊。

并发的好处

提高资源利用率

当一个任务并没有完全占用系统资源,就可以利用并发来提高资源利用率,同时也能更快地完成任务。

当你的右手在干些什么的时候,左手是不是在没事做呢?那就也用起来呗。
(某次聚会,一名骑马的汉子说自己左右互搏(lu)特厉害)。

在程序任务上更加精简

就拿上一个栗子来说,左手做什么,右手做什么,任务明确分配好,又能同时进行,既提高了效率,逻辑又清晰。

更好的响应程序

这个拿Android客户端举个栗子,上传图片时,当前界面还是正常运转没有卡死,图片也正常上传,既保证了界面被响应,又保证图片可以上传。

并发的风险

并发的代价

  • 需要占用更多的资源。
  • 设计好一个并发程序并不容易。
  • 并发的资源交互问题复杂。

并发的隐患

  • 滥用资源导致系统不稳定
  • 结果与预期不符
  • 出现BUG难以排查

线程篇

进程是什么

就拿Android中的App来说,一般来说一个app就是一个进程,(除了特殊的手段开启了多个进程,这里不深入这个话题,就是一个一对多的关系)。

线程是什么

进程只是一个程序、任务的统称,但是却不能执行任务,真正执行任务的是线程,所以线程是由进程创建的,一个进程可以创建多个线程。

线程可以调度资源等等,在这里只需要了解大致的概念就好,如果要深入可以学习一下操作系统

进程与线程的关系

人脑就相当于是CPU,想做一件事的时候,这个任务就是一个进程了,需要运用手脚等器官去完成这个任务,而手脚器官就可以理解成一个个线程,去做了不同的事,从而完成任务。

单线程

还是用Android举栗子,当你在手机上操作的时候,这个被称之为UI线程(之后会详解)。而一个最基本的app,不需要复杂的功能时,就只有一个UI线程和我们交互,那么这个app就是个单线程的。一般的程序面向用户的线程就是UI线程,也称之为主线程,单线程程序,其实就是只有一个主线程的程序。

多线程

多个进程可以算是并发,但是我们所说的并发场景,大部分是在一个进程中的,而并发就是由线程完成的,多个线程同时执行任务,就称之为并发。

以下为多线程工作示意图:

多线程

多线程并发过程中会遇到的问题

1. 资源共享

A线程要写文件C,B线程也要写文件C,这个时候就好像你拿着两只笔同时往纸上写东西,写出来的是什么自己也不知道。

这个时候我们需要一个类似于的东西,当C被A在写的时候,B不能写,B要等A写完了才能继续写。

至于这个到底是什么会在后面继续讲到。

3. 死锁问题

死锁的四个条件是:

  • 禁止抢占:no preemption

  • 持有和等待:hold and wait

  • 互斥:mutual exclusion

  • 循环等待:circular waiting

预防死锁就是至少破坏这四个条件其中一项,即破坏“禁止抢占”、破坏“持有等待”、破坏“资源互斥”和破坏“循环等待”。

举个例子:

A在B那边割包皮,B把A割坏了,A占着B的床位,要B赔钱,B要A让出床位才给钱。双方僵持不下。

线程如何去用

在Java中,线程通常就是指Thread这个类,或者实现了Runnable的类,其实Thread这个类也是实现了Runnable接口的,可以看一下Runnable接口的代码:

Runnable

里面就是一个run方法需要被实现。

再看一下Thread类的声明:

Thread

确实是一个实现了Runnable的类。

那么Thread类中拥有start()方法,和run()方法,下面用run()方法直接调用
得到信息:

ThreadRun

发现其实和外面的线程是在同一个线程上。

而调用start()方法得到的信息是:

ThreadStart

发现线程名不一样了,用start会开启一个新的线程,而run还是在当前线程执行。

另外在Java1.5之后,还有Callable、Future和FutureTask,在这里就不详细介绍,还有线程的wait、
yield、sleep等在下一章会一起详细介绍。

线程的优先级

在Java中,线程的优先级有1~10,而默认的是5。1最低,10最高。在Thread类中有三个常量:

  • MIN_PRIORITY = 1

  • NORM_PRIORITY = 5

  • MAX_PRIORITY = 10

在同一个线程池中的线程优先级是相同的。

JVM会根据线程的优先级去抢先调度,然而线程的优先级只能保证抢占资源的概率较大,并不能保障线程的执行顺序,所以不能过于依赖设置线程的优先级。

线程池

频繁地创建和销毁线程会导致性能大幅度降低,这肯定不是你希望的。

线程池的出现,就是为了解决这个问题,根据java中提供不同的线程池机制,有效地提高资源利用率。

直接在代码中创建Thread、Runnable去start或者run容易出现不可预测的问题,在java1.5开始,引入了java.util.concurrent包,其中有个并发的框架:Executor,使用ExecutorService替代直接操作线程类,而Executors是用来创建线程池的,内部提供了很多静态方法去创建你想要的线程池,不需要你再手动去创建实现。
看一下关于Executor中的类和接口的大致的成员与关系:

concurrent

关于这些类如何使用,以及有什么特性,下一章会作介绍。

队列

在java中提到队列肯定会想起Queue,而线程队列用的是BlockingQueue,这是个接口,在concurrent包中有好几个类实现了这个接口。

BlockingQueue

介绍一下BlockingQueue常用的方法

会异常 返回是否成功 会阻塞 设定等待时间
入队列 add(e) offer(e) put(e) offer(e, timeout, unit)
出队列 remove() poll() take() poll(time, unit)
查看值 element() peek() none none

线程安全

在前面讲过死锁,死锁是由于使用不当引起的一种现象,而这里的锁是人工干预的,让并发按照你的意思走。

在java中的锁有synchonrized、Lock。锁的出现主要是为了解决线程安全问题。

关于线程的状态会在下一章讲锁的机制时候再讲,因为线程的状态会影响到锁。

线程安全的集合

因为多线程访问资源可能会造成数据不一致或者数据污染,而某些集合会用一些锁或者同步机制做了处理。

线程安全的集合有:HashTable、SynchronizedCollection、ConcurrentHashMap、Vector等。

线程安不安全的首要前提是在多线程访问同一个对象的情况下。

预告

本节中挖的,一些没有展开讲的东西,会在下一节中填上。

感谢

《Java并发编程实践》
《Thinking in Java》

baoyongzhang(鲍老师)