本篇文章主要是针对技术掘金上面《大话Android多线程》进行笔记总结,好记性不如烂笔头。
Thread和Runnable的联系和区别 线程的创建有两种方式,一种是重写runnable的run方法然后放到thread中,一种是重写thread的run方法
两者的联系
thread实现了runnable接口
都需要重写run方法
两者的区别
实现runnable的类更具有健壮性,避免了单继承的局限
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了解的还是比较多的,扫盲一下。
主线程的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,然后进行通信。
子线程和子线程之间进行通信,其实也就是子线程a能否获得子线程b的looper这个问题,如上使用handlerthread或者直接使用继承然后写一个get方法获取looper也可以。
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());//验证主线程的执行情况 } }
Callable在被线程执行后,可以提供一个返回值,我们可以通过Future的get()方法拿到这个值
Future是一个接口,而FutureTask实现了RunnableFuture接口,RunnableFuture继承了Runnable接口和Future接口
与runnable不同的两点:
1。FutureTask用于异步获取执行结果或取消执行任务的场景,它的主要功能有: 可以判断任务是否完成 可以获取任务执行结果 可以中断任务
2。Callable的call()方法可以抛出异常,我们可以在尝试执行get()方法时捕获这个异常
FutureTask可以确保任务只执行一次
我们在某条线程执行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)