济南Android培训
达内济南山大路中心

17156168575

热门课程

Android多线程编程详解

  • 时间:2016-10-31
  • 发布:济南达内
  • 来源:互联网

由于涉及非常多的知识,所以这篇只能做到概述,不做细节讲解。

Android所有的UI组件(包括UI的操作交互)都运行在一个线程中,称为:UI线程/主线程。

而Android的这种单线程模型原则,有两个限制:

1.不能阻塞UI线程。

2.非UI线程不能操作Android UI。

保证了UI线程的流畅,也就是保证用户操作的流畅,所以耗时的操作交给其他线程,称为:子线程/工作线程。

做一个秒钟计时器:

new Thread(new Runnable() {

@Override

public void run() {

while (isRunning) {

try {

//休眠1秒

Thread.sleep(1000);

} catch (InterruptedException e) {

e.printStackTrace();

System.out.print("+1s");

}).start();

每隔1秒打印一条日志,但是怎么让UI显示 +1s 呢?

使用Handler隔1秒发送一个消息给UI线程

static class MyTimeHandler extends Handler {

public MyTimeHandler(Callback callback) {

super(callback);

mTimeHandler = new MyTimeHandler(new Handler.Callback() {

@Override

public boolean handleMessage(Message msg) {

if(isRunning) {

//更新UI

mBinding.setSeconds(++mSecond);

return true;

private void secondTimerCount(){

new Thread(new Runnable() {

@Override

public void run() {

while (isRunning) {

try {

//休眠1秒

Thread.sleep(1000);

//发送消息

mTimeHandler.sendEmptyMessage(0);

} catch (InterruptedException e) {

e.printStackTrace();

System.out.print("+1s");

}).start();

由于开始/停止计时,都是在UI线程操作,不存在并发、线程完全问题。

子线程和UI线程的通信方式:

Activity.runOnUiThread(Runnable)

View.post(Runnable)

View.postDelayed(Runnable, long)

AsyncTask

Messenger(支持进程间的通信)

Handler

背后实现的原理都是Handler方式

当有多个子线程之后,就要面临并发问题:

线程安全

指某个函数、函数库在多线程环境中,执行结果都是正确的。

反之,就是非线程安全。

// 非线程安全 代码

public class ExampleNonTheadSafeTest {

ExecutorService mExecutor;

@Before

public void setUp() throws Exception {

mExecutor = Executors.newFixedThreadPool(3);

int count = 0;

public int add(){

for(int i = 0; i < 10000; i++){

count++;

return count;

@Test

public void testAdd() throws IOException, InterruptedException, ExecutionException {

Callable callable = new Callable() {

@Override

public Integer call() throws Exception {

return add();

Future f1 = mExecutor.submit(callable);

Future f2 = mExecutor.submit(callable);

Future f3 = mExecutor.submit(callable);

Future f4 = mExecutor.submit(callable);

Future f5 = mExecutor.submit(callable);

Future f6 = mExecutor.submit(callable);

System.out.println(f1.get());

System.out.println(f2.get());

System.out.println(f3.get());

System.out.println(f4.get());

System.out.println(f5.get());

System.out.println(f6.get());

@After

public void tearDown() throws Exception {

mExecutor.shutdown();

// 执行结果并不是:60000

10366

12013

15049

23700

25479

25414

要写出线程安全的代码,了解一下:Java内存模型。

Java内存模型的抽象

在java中,所有实例域、静态域和数组元素存储在堆内存中,堆内存在线程之间共享(使用“共享变量”这个术语代指实例域,静态域和数组元素)。

局部变量、方法定义参数和异常处理器参数不会在线程之间共享,它们不会有内存可见性问题,也不受内存模型的影响。

Java内存模型

Java线程之间的通信由Java内存模型(本文简称为JMM)控制,JMM决定一个线程对共享变量的写入何时对另一个线程可见。

从抽象的角度来看,JMM定义了线程和主内存之间的抽象关系:

线程之间的共享变量存储在主内存中,每个线程都有一个私有的本地内存,本地内存中存储了该线程以读/写共享变量的副本。

本地内存是JMM的一个抽象概念,并不真实存在。它涵盖了缓存,写缓冲区,寄存器以及其他的硬件和编译器优化。

所以上面非线程安全的代码,count是共享变量,各个线程在使用共享变量副本并发+1的时候,其它线程并不知道,所以会导致+1后的结果丢失。

对于多线程对共享变量的操作,需要线程同步:只能一个线程进入函数,其它线程等待...

同步代码的执行过程:

获取对象监视器的锁

从主内存读取共享变量到线程当前工作内存, 即同步数据

执行代码

将工作内存数据刷回主内存

释放对象监视器的锁

上面count++的代码,最简单的修改方式(并不是最高效的):

public synchronized int add(){

for(int i = 0; i < 10000; i++){

count++;

}

return count;

}

//执行结果

10000

30000

20000

60000

40000

50000

常见的同步方式

synchronized keyword

volatile keyword

atomic variable

Lock

synchronized

synchronized叫做同步锁,是Lock的一个简化版本,性能不如Lock,只需要在一个方法或把需要同步的代码块包装在它内部,那么这段代码就是同步的了。

4种代码块同步方式

实例方法

静态方法

实例方法内部代码块

静态方法内部代码块

// 实例方法,只能单实例中同步,多实例之间是不同步的,实例级别的同步

public synchronized void add(int value){

this.count += value;

}

// 静态方法,所有实例中同步,Class级别的同步

public synchronized void add(int value){

this.count += value;

}

// 实例方法内部代码块,只能单实例中同步,多实例之间是不同步的,实例级别的同步

public void add(int value){

synchronized(this){

this.count += value;

// 静态方法内部代码块,所有实例中同步,Class级别的同步

public static void add(int value){

synchronized(MyClass.class){

count += value;

volatile

volatile 修饰的成员变量在每次被线程访问时,都强迫从共享内存中重读该成员变量的值;当成员变量发生变化时,强迫线程将变化值回写到共享内存。这样在任何时刻,两个不同的线程总是看到某个成员变量的同一个值。

volatile int count = 0;

public int add(){

for(int i = 0; i < 10000; i++){

count += 1;

return count;

// 执行结果

12400

18441

20014

30816

33141

34968

不能达到同步的目的啊?

volatile关键字可以解决多线程环境下的可见性,这也是相对的,因为它不具有操作的原子性,也就是它不适合在对该变量的写操作依赖于变量本身自己。

在进行计数操作时count++,实际是count=count+1;

count最终的值依赖于它本身的值。

所以使用volatile修饰的变量在进行这么一系列的操作的时候,就有并发的问题。

上一篇:Android 编程基础详解
下一篇:必须使用Git的理由

Java程序员需要学什么?

跑分网站泄密 一加5或于近期吃上奥利奥

Java的21个核心技术点

CardView简单实现卡片式布局

选择城市和中心
贵州省

广西省

海南省