读书笔记-大话多线程

本篇文章主要是针对技术掘金上面《大话Android多线程》进行笔记总结,好记性不如烂笔头。

Thread和Runnable的联系和区别

线程的创建有两种方式,一种是重写runnable的run方法然后放到thread中,一种是重写thread的run方法

两者的联系

  1. thread实现了runnable接口
  2. 都需要重写run方法

两者的区别

  1. 实现runnable的类更具有健壮性,避免了单继承的局限
  2. runnable更容易实现资源共享,能多个线程同时处理一个资源

两者的比较

使用runnable创建的多线程,若是同一个runnable,那么运行的时候实际上执行的是同一个任务,共享的是相同的资源。
相反,使用thread创建的多线程,则是不同的任务,也不会共享相同的资源。

Synchronized使用解析

多线程的环境下面,很容易出现不同步的情况下,条件a为当b大于0的时候,b自减,结果1号线程2号线程同时进行操作,1号线程快2号线程几毫秒,对1号线程来讲,a成立,在1号线程准备对b进行操作的时候,2号线程进来,2号线程在判断的时候发现a也成立,而此时1号线程在2号线程判断之后顺利的进行的b的自减操作,此时2号线程虽然条件不成立,但是仍然需要进行b操作,此时就会产生一系列的问题。

锁的作用,便是对方法块进行同步操作。在一个对象操作该方法的时候,另一个对象无法操作,等第一个对象操作完毕,该方法块才可以让别的对象操作。

synchronized (obj){}同步代码块和用synchronized声明方法的作用基本一致,都是对synchronized作用范围内的代码进行加锁保护,其区别在于synchronized同步代码块使用更加灵活、轻巧,synchronized (obj){}括号内的对象参数即为该代码块持有锁的对象

同步方法和静态同步方法

  • synchronized声明非静态方法、同步代码块的synchronized (this){}和synchronized (非this对象){}这三者持有锁的对象为实例对象(类的实例对象可以有很多个),线程想要执行该synchronized作用范围内的同步代码,需获得对象锁。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class SynchronizedTest {
public synchronized void test1(){
//持有锁的对象为SynchronizedTest的实例对象
}

public void test2(){
synchronized (this){
//持有锁的对象为SynchronizedTest的实例对象
}
}

private String obj = "obj";
public void test3(){
synchronized (obj){
//持有锁的对象为obj
}
}
}
  • synchronized声明静态方法以及同步代码块的synchronized (类.class){}这两者持有锁的对象为Class对象(每个类只有一个Class对象,而Class对象是Java类编译后生成的.class文件,它包含了与类有关的信息),线程想要执行该synchronized作用范围内的同步代码,需获得类锁
1
2
3
4
5
6
7
8
9
10
11
public class SynchronizedTest {
public static synchronized void test4(){
//持有锁的对象为SynchronizedTest的Class对象(SynchronizedTest.class)
}

public void test5(){
synchronized (SynchronizedTest.class){
//持有锁的对象为SynchronizedTest的Class对象(SynchronizedTest.class)
}
}
}

若synchronized同步方法(代码块)持有锁的对象不同,则多线程执行相应的同步代码时互不干扰;若相同,则获得该对象锁的线程先执行同步代码,其他访问同步代码的线程会被阻塞并等待锁的释放

线程间的通信机制之Handler

handler了解的还是比较多的,扫盲一下。

  1. 主线程的looper在应用开始前系统就已经创建好了,需要在主线程往子线程发射消息,需要在子线程重写looper并开启循环

重写方式如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
public class HandlerTestActivity extends AppCompatActivity {
private Handler handler2;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_handler_test);
TestThread testThread = new TestThread();
testThread.start();

while (true){//保证testThread.looper已经初始化
if(testThread.looper!=null){
handler2 = new Handler(testThread.looper){
@Override
public void handleMessage(Message msg) {//子线程收到消息后执行
switch (msg.what){
case CODE_TEST_FOUR:
Log.e(TAG,"收到主线程发送的消息");
break;
}
}
};

handler2.sendEmptyMessage(CODE_TEST_FOUR);//在主线程中发送消息
break;
}
}

private class TestThread extends Thread{
private Looper looper;

@Override
public void run() {
super.run();
Looper.prepare();//创建该子线程的Looper实例
looper = Looper.myLooper();//取出该子线程的Looper实例
Looper.loop();//开始循环
}
}
}

虽然可以使用继承一个线程然后开looper的方式进行子线程设置looper,不过仍然是比较麻烦,直接使用handlerthread便可直接解决该问题。

1
2
3
4
5
6
7
8
9
10
handler2 = new Handler(handlerThread.getLooper()){
@Override
public void handleMessage(Message msg) {//子线程收到消息后执行
switch (msg.what){
case CODE_TEST_FOUR:
Log.e(TAG,"收到主线程发送的消息");
break;
}
}
};

这样子在主线程中使用handler便可直接获取子线程的looper,然后进行通信。

  1. 子线程和子线程之间进行通信,其实也就是子线程a能否获得子线程b的looper这个问题,如上使用handlerthread或者直接使用继承然后写一个get方法获取looper也可以。

  2. messagequeue其实是looper中包含的,并不能将他们区分开来看。

callable、future、futuretask

使用callable方式创建线程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
public static class TestCallable implements Callable{
private int ticket = 10;

@Override
public String call() throws Exception {
System.out.println(Thread.currentThread().getName() + ":我买几个橘子去。你就在此地,不要走动" + " 时间:" + getTime());
Thread.sleep(2000);//模拟买橘子的时间
System.out.println(Thread.currentThread().getName() + ":橘子卖完了" + " 时间:" + getTime());

throw new NullPointerException("橘子卖完了");
}
}

public static void main(String args[]){
TestCallable callable = new TestCallable();
FutureTask<String> futureTask = new FutureTask<String>(callable);

Thread thread1 = new Thread(futureTask, "爸爸");
thread1.start();

//也可以用threadpool
//ExecutorService executor = Executors.newCachedThreadPool();
//Future<String> future = executor.submit(callable);

System.out.println("儿子站在原地" + " 时间:" + getTime());//验证主线程的执行情况
try{
System.out.println(futureTask.get());
System.out.println("儿子收到橘子" + " 时间:" + getTime());//验证主线程的执行情况
}catch (InterruptedException | ExecutionException e){
System.out.println("儿子没收到橘子" + " 时间:" + getTime());//验证主线程的执行情况
}
}
  1. Callable在被线程执行后,可以提供一个返回值,我们可以通过Future的get()方法拿到这个值

  2. Future是一个接口,而FutureTask实现了RunnableFuture接口,RunnableFuture继承了Runnable接口和Future接口

  3. 与runnable不同的两点:

1。FutureTask用于异步获取执行结果或取消执行任务的场景,它的主要功能有:
可以判断任务是否完成
可以获取任务执行结果
可以中断任务

2。Callable的call()方法可以抛出异常,我们可以在尝试执行get()方法时捕获这个异常

  1. FutureTask可以确保任务只执行一次

  2. 我们在某条线程执行get()方法时,该线程会被阻塞,直到Future拿到Callable.call()方法的返回值。

在UI线程中使用时(尤其是后续还有更新UI的操作)要特别注意这点,以免造成界面卡顿。那么要如何处理这种多线程执行耗时任务,等待结果,然后再更新UI的情况呢?handler

ThreadPoolExecutor

new Thread()的缺点

• 每次new Thread()耗费性能
• 调用new Thread()创建的线程缺乏管理,被称为野线程,而且可以无限制创建,之间相互竞争,会导致过多占用系统资源导致系统瘫痪
• 不利于扩展,比如如定时执行、定期执行、线程中断

线程池的优点

• 重用存在的线程,减少对象创建、消亡的开销,性能佳
• 可有效控制最大并发线程数,提高系统资源的使用率,同时避免过多资源竞争,避免堵塞
• 提供定时执行、定期执行、单线程、并发数控制等功能

参数解析

1
2
3
4
5
6
7
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)