Android多进程与多线程用例简介(线程篇)

进程/线程分别是啥?

  • 通俗地说进程和线程都是一个时间段的描述,是CPU工作时间段的描述。
  • 默认情况下,同一应用程序下的所有组件运行在同一进程中。(每个应用程序都有一个自己的进程)
  • cpu的最小调度单位指的是线程(一个app最少有8个线程其中包括ui线程等)
  • cpu的最小资源分配单位是进程

Android哪里用到了?

  • 要说哪里用到了?用到的地方还是很多的!
  • 举个例子:ui线程这个常听到的词,处理耗时任务需不需要在子线程处理?下载总不能放在主线程等着吧?总之呢在android上一切耗时的操作都是可以放在子线程中去做的毕竟5s 10s 20s 没响应 Activity BroadcastReceiver Service就没响应Anr掉了哦。

实现多线程的N种方式(包含信息交换)

其实实现线程切换的方式非常的多,最方便的还是当属rxjava但是rxjava的使用没那么轻量级,我们总不能为了单纯一个切换线程做一下查询数据库操作就去引入rxjava吧?我们不能为了更新个ui就引入rxjava吧?如果项目中已经使用了rxjava并且熟悉rxjava这看起来还是很惬意的,但是如果没有呢?这就很笨重了。

所以啰嗦了这么多主要想说的还是下面这些种用法还是有必要学习的!

Handler

  • handler原理就不阐述了,有兴趣的老哥可以看一下这篇简述
  • 咱说说用法吧

主线程和子线程通信(下面代码使用了两种方式一种是直接post(runnable)--看thread0,另一种是sendMessage(message)--看thread1

public class MainActivity extends ReactActivity {
Handler handler0 = new Handler();//主线程初始化Handler自动绑定主线程
    Handler handler1 = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
                case 1:
                    textView.setText((String) msg.obj);
                    break;
            }
        }
    };
    TextView textView;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        new TestThread0().start();
        new TestThread1().start();
    }
    class TestThread0 extends Thread {
        @Override
        public void run() {
            super.run();
            try {
                Thread.sleep(5500);
                handler0.post(new Runnable() {
                    @Override
                    public void run() {
                        textView.setText("OK");
                    }
                });
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
    class TestThread1 extends Thread {
        @Override
        public void run() {
            try {
                Thread.sleep(5500);
                Message message = new Message();
                message.what = 001;
                message.obj = "OK";
                handler1.sendMessage(message);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

子线程和子线程通信
ps1:双Thread形式

        new Thread() {
            @Override
            public void run() {
                //注意接受消息的线程一定要手动开启Looper的循环,并且Looper要先准备准备完成后处理接收逻辑然后再开启循环。
                Looper.prepare();
                handler0 = new Handler() {
                    @Override
                    public void handleMessage(Message msg) {
                        switch (msg.what) {
                            case 1:
                                System.out.print((String) msg.obj);
                                break;
                        }
                    }
                };
                Looper.loop();
            }
        }.start();
        new Thread() {
            @Override
            public void run() {
                Message message = new Message();
                message.what = 1;
                message.obj = "from thread1";
                handler0.sendMessage(message);

            }
        }.start();

ps2:使用ThreadHandler

HandlerThread handlerThread = new HandlerThread("handlerThread");
        handlerThread.start();
        final Handler handler = new Handler(handlerThread.getLooper()) {
            @Override
            public void handleMessage(Message msg) {
                //再handlerThread线程进行处理
                switch (msg.what) {
                    case 1:
                        Log.d("test", "来自主线程的消息");
                        break;
                    case 2:
                        Log.d("test", "来自子线程的消息");
                        break;
                }
            }
        };
        handler.sendEmptyMessage(1);
        new Thread(new Runnable() {
            @Override
            public void run() {
                handler.sendEmptyMessage(2);
            }
        }).start();

AsyncTask

  • 其实AsyncTask的原理也不过就是handler+内部维护的线程池进行了一系列的封装,它被设计出来的目的就是为了满足Android的特殊需求:非主线程不能操作(UI)组件,所以AsyncTask扩展Thread增强了与主线程的交互的能力。如果你的应用没有与主线程交互,那么就直接使用Thread就好了。
  • 注意下面AsyncTask的泛形参数,他们有自己的意义
public abstract class AsyncTask<Params, Progress, Result>
//   第一个params代表了初始参数由主线程传入
//   第二个Progress代表了执行进度的返回参数
//   第三个Result代表了执行结果的返回参数
//   如果不需要某一项直接填void就可以咯,或者完全不填就代表了全部void
  • 使用的话看下面的代码段吧
AsyncTask asyncTask = new AsyncTask() {
            @Override
            protected void onPreExecute() {
                //顾名思义PreExecute再execute之前意味着这个方法中可以更新UI,在耗时操作执行之前的操作。
                super.onPreExecute();
            }
            @Override
            protected Object doInBackground(Object[] objects) {
                //这个是主要的方法,所有的耗时的操作需要在这个方法中处理,
                publishProgress("");
                //调用publishProgress()方法来更新操作的进度
                return null;
            }
            @Override
            protected void onProgressUpdate(Object[] values) {
                super.onProgressUpdate(values);
            }
            @Override
            protected void onPostExecute(Object o) {
                //在耗时操作完成之后,触发这个方法,在UI线程中执行,可以通知用户操作已经完成
                super.onPostExecute(o);
            }
        };
        asyncTask.execute();

IntentService

  • 要说这个IntentService为啥放在了这个位置呢?因为这个东西别看它名字里面又有intent又有service它其实本质上还是一个service,它继承自service但是内部维护了一个自己的线程这个线程是通过HandlerThread开启的(handlerThread本质还是thread只是其内部完成了Looper的初始化他还是要结合handler使用的)
  • 我们使用了IntentService最起码有两个好处,一方面不需要自己去newThread了;另一方面不需要考虑在什么时候关闭该Service了(源码调用stopSelf(msg.arg1))。
  • 下面我们看看这货的基本使用吧
    首先注意IntentService使用的是启动方式开启service
    再次注意IntentService可以多次启动,一但便会回掉再IntentService的onHandleIntent方法,直到没有需要处理的intent,该service会自己关闭。
    最后手动关掉IntentService可以使用ondestory
 @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Intent intent = new Intent(MainActivity.this, MyIntentService.class);
        Bundle bundle = new Bundle();
        bundle.putString("1","one");
        startService(intent);
    }

    class MyIntentService extends IntentService {
        /**
         * Creates an IntentService.  Invoked by your subclass's constructor.
         *
         * @param name Used to name the worker thread, important only for debugging.
         */
        public MyIntentService(String name) {
            //此处可以做点准备工作什么的
            super(name);
        }
        @Override
        protected void onHandleIntent(@Nullable Intent intent) {
            //此处已经在IntentService开启的内部线程处理了
            Log.d("IntentServiceTest", (String) intent.getExtras().get("1"));
            //此处怎么把处理好的消息返回给Ui线程呢?两种方法1:本地广播2:handler(此处不述)
        }
    }

Thread(runnable/callable) + 线程池 + future/futuretask等

  • 开启线程的最简单方式Thread
protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        //方法一
        new Thread(new Runnable() {
            @Override
            public void run() {
                Log.d("ThreadTest", "方法一:我在子线程");
            }
        }).start();
        //方法二
        new myThread().start();
    }

    //方法二
    class myThread extends Thread {
        @Override
        public void run() {
            Log.d("ThreadTest", "方法二:我在子线程");
        }
    }

直接显示创建线程是一种很不明智的做法,由于在创建线程和销毁线程上花的事件以及系统资源的开销很大,有可能造成大量同类线程从而导致消耗完内存或者“过度切换”的问题,总之不要显示创建线程,为了避险这些问题java给我们提供了线程池

  • 处理并发的最好方式线程池

最好不最好其实我也说不准,限于自己的姿势水平目前处理多线程问题都是适用线程池进行管理的,而且Aaync内部也是使用线程池管理了多个线程,既然源码都在用了,我想我们来使用它也不过分吧!
常用线程池

        ExecutorService executorService0 = Executors.newSingleThreadExecutor();
        //一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。(常用)
        ExecutorService executorService1 = Executors.newCachedThreadPool();
        //一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。
        ExecutorService executorService2 = Executors.newFixedThreadPool(10);
        //一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。
        ExecutorService executorService3 = Executors.newWorkStealingPool();
        //创建持有足够线程的线程池来支持给定的并行级别,并通过使用多个队列,减少竞争,它需要穿一个并行级别的参数,如果不传,则被设定为默认的CPU数量。
        ScheduledExecutorService executorService4 = Executors.newScheduledThreadPool(10);
        //一个定长线程池,支持定时及周期性任务执行。(延迟执行线程池)
        ScheduledExecutorService executorService5 = Executors.newSingleThreadScheduledExecutor();
        //创建一个单任务线程池,支持定时及周期性任务执行。(延迟执行线程池)其实等于Executors.newScheduledThreadPool(1);
        

常规用法-延迟任务和循环任务

ScheduledExecutorService excutorService5 = Executors.newSingleThreadScheduledExecutor();
        excutorService5.schedule(new Runnable() {
            //延迟1s执行
            @Override
            public void run() {
                Log.d("ScheduledTest", "test");
            }
        }, 1, TimeUnit.SECONDS);
        excutorService5.scheduleAtFixedRate(new Runnable() {
            //延迟3s执行,每隔1s进行一次循环
            @Override
            public void run() {
                Log.d("ScheduledTest", "test1");
            }
        }, 3, 1, TimeUnit.SECONDS);
        excutorService5.shutdown();

常规用法

//不需要返回值的常规用法
ExecutorService executorService0 = Executors.newSingleThreadExecutor();
for (int i = 0; i < 10; i++) {
            final int finalI = i;
            executorService0.execute(new Runnable() {
                @Override
                public void run() {
                    Log.d("executorTest", String.valueOf(finalI));
                }
            });
        }
executorService0.shutdown();

//需要返回值的常规用法
ExecutorService executorService0 = Executors.newSingleThreadExecutor();
ExecutorService executorService1 = Executors.newSingleThreadExecutor();
for (int i = 0; i < 10; i++) {
            final int finalI = i;

            Future future = executorService1.submit(new Callable<Object>() {
                @Override
                public Object call() throws Exception {
                    System.out.print(String.valueOf(finalI));
                    return finalI;
                }
            });
            try {
                System.out.print(future.get());
            } catch (InterruptedException e) {
                e.printStackTrace();
            } catch (ExecutionException e) {
                e.printStackTrace();
            }
        }
executorService0.shutdown();
for (int i = 0; i < 10; i++) {
            Future future1 = executorService1.submit(new Runnable() {
                @Override
                public void run() {

                }
            }, "always one");
            try {
                System.out.print(future1.get());
                //永远为“always one”这是因为submit方法指定了这个值。如若不指定返回null
            } catch (InterruptedException e) {
                e.printStackTrace();
            } catch (ExecutionException e) {
                e.printStackTrace();
            }

        }
executorService1.shutdown();

补充

提交任务的方式一般两种submit()(有返回值)和execute()(没返回值)在不需要一个结果的时候直接用execute()会提升很多性能。因为submit()方法内部会封装RunnableFuture(实际返回FutureTask的实例化)然后在交给execute()执行,注意runnable调用submit()也会有返回值但是为值null。

关闭任务一般分为两个阶段第一阶段shutdown调用后,不可以再submit新的task,已经submit的将继续执行。第二阶段shutdownNow试图停止当前正执行的task,并返回尚未执行的task的list。

线程池理论上来讲其实有无数种我们一般都是直接在Executors里面拿,但是如果看过源码我们发现其实它最终也是根据不同的需要实例化了ThreadPoolExecutor而已。

future.get()是阻塞方法,如果调用这个方法来获得返回值,那么在获得返回值之前当前线程池都会阻塞在这个单线程上(部分并发线程池会出现问题)。

FutureTask是啥?它就是Future这个接口的唯一实现类(java的多态嘛!面向接口编程嘛!所以submit()方法返回的其实是FutureTask的实例化对象),内部封装了很多方法,可以帮助判断线程的执行状态,并且还可以取消一个线程的执行。

FutureTask的一个实用方法

ExecutorService executorService1 = Executors.newSingleThreadExecutor();
        FutureTask futureTask = new FutureTask(new Callable<Integer>() {
            @Override
            public Integer call() throws Exception {
                System.out.println("任务执行中" + new SimpleDateFormat("yyyy/MM/dd HH:mm:ss").format(new Date()));
                Thread.sleep(3000);
                Random random = new Random();
                return random.nextInt();
            }
        }) {
            @Override
            protected void done() {
                //此处是自定义FutureTask带来的一个小福利,我们可以重写他的done方法在Callable或者runnable的call和run方法执行完后会执行这个call方法在此处我们可以做一些事件处理
                try {
                    int num = (int) this.get();

                    System.out.println("任务结束 结果是~~~~~~" + num + "~~~~~~" + new SimpleDateFormat("yyyy/MM/dd HH:mm:ss").format(new Date()));
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } catch (ExecutionException e) {
                    e.printStackTrace();
                }

                super.done();
            }
        };
        System.out.println("任务开始" + new SimpleDateFormat("yyyy/MM/dd HH:mm:ss").format(new Date()));
        executorService1.execute(futureTask);

线程同步问题简略分析

  • 首先说下synchronized关键字
/**
 *      一个单例模式的工具类
 */

public class XXXTest {

    private static XXXTest instance;

    private static Object obj = new Object();

    private XXXTest(){}

    /**
     * synchronized 修饰的同步方法
     * */
    public static synchronized XXXTest getInstance(){
        if (instance == null){
            instance = new XXXTest();
        }
        return instance;
    }

    /**
     * 含有synchronized 同步快的方法
     * */
    public static XXXTest getInstance(){
        if (instance == null){
            synchronized (obj){
                instance = new XXXTest();
            }
        }
        return instance;
    }

}

以上是synchronized的两种用法:
1、synchronized修饰方法,表示不同线程访问相同对象的相同方法,必须要排队,相当于synchronized对这个对象上了锁,只能获取这个对象的锁的线程才能使用这个方法,使用完毕自动释放锁。
2、synchronized修改某一段代码,指定这段代码块要同步的对象进行上锁解锁。例如例子中的代码,先去判断intance是否初始化,没有就对obj进行上锁,防止创建多次,破坏了单例模式。
两种用法优点缺点分析:
1、synchronized修饰方法,使用简单,但是效率低下,不需要同步的操作也被迫同步。
2、synchronized代码块,使用相对复杂,需要对功能逻辑有完整的了解,但是仅仅是同步了某一块代码,效率也大幅提升。
注意:synchronized代码块指定同步对象不能为空对象

  • 再说一下Thread自带的wait()、notify()、notifyAll()方法

首先这几个方法是Object类提供的并不是Thread类特有的,三个方法配套使用,wait() 使得线程进入阻塞状态,它有两种形式,一种允许 指定以毫秒为单位的一段时间作为参数,另一种没有参数,前者当对应的 notify() 被调用或者超出指定时间时线程重新进入可执行状态,后者则必须对应的 notify() 被调用,notifyAll()则比较特殊它可以唤醒在此对象监视器上等待的所有线程。

作者简介:
就职于甜橙金融技术部,负责android开发。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 201,552评论 5 474
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,666评论 2 377
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 148,519评论 0 334
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,180评论 1 272
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,205评论 5 363
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,344评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,781评论 3 393
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,449评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,635评论 1 295
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,467评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,515评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,217评论 3 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,775评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,851评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,084评论 1 258
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,637评论 2 348
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,204评论 2 341

推荐阅读更多精彩内容

  • Java 多线程 线程和进程的区别 线程和进程的本质:由CPU进行调度的并发式执行任务,多个任务被快速轮换执行,使...
    安安zoe阅读 2,188评论 1 18
  • Android中的线程 线程,在Android中是非常重要的,主线程处理UI界面,子线程处理耗时操作。如果在主线程...
    shenhuniurou阅读 747评论 0 3
  • 第5章 多线程编程 5.1 线程基础 5.1.1 如何创建线程 在java要创建线程,一般有==两种方式==:1)...
    AndroidMaster阅读 1,784评论 0 11
  • 1.一鼓作气,再而衰,三而竭。爬山一定要一鼓作气爬上去,就算很累很累,也只能休息尽可能次数少时间短。不停地休息只会...
    酸酸酸酸酸奶阅读 684评论 0 1
  • 话说我的工位史,真是从繁到简,曾经我的工位都是凌乱不堪,堆满了各种文件书本,而现在的工位则是比较简单,这也见证了我...
    瀚文ILoveU阅读 1,905评论 9 14