进程/线程分别是啥?
- 通俗地说进程和线程都是一个时间段的描述,是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开发。