Android之 线程详解

这篇具有很好参考价值的文章主要介绍了Android之 线程详解。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

一 进程

1.1 进程是针对造作系统而言的,打开运行一个应用程序就开始一个进程。对android系统而言,进程就是一个app应用。

1.2 但往往真实中启动一个程序可能并不止开启一个进程,可能还有处于后台的进程,或者其它服务进程。所以我们一般说一个程序至少有一个进程

1.3 进程间的数据是不共享的,如果需要跨进程访问数据,就要通过管道,文件, Socket套接字等技术实现跨进程通信

二 线程

2.1 线程是进程的一部分,是执行程序的最小单位,可以看作进程的一个分流

2.2 线程的作用往往是为了不影响其它线程的任务,一般用来处理独立的任务。

2.3 我们知道计算机处理代码是按照顺序执行的,如果全部在一个线程运行代码,就必须等待前面任务执行完,才能执行后面任务

2.4 这也是android不能在子线程更新UI的原因,UI更直观的展示在我们眼前,如果因为等待一个下载任务长时间更新不了UI,就会造成视觉上的卡顿,体验是非常不好的

2.5 所以android只有一个主线程(UI线程),是程序运行时候系统创建的,我们只能创建多个子线程去完成耗时任务

三 主线程

3.1 主线程是程序运行时系统创建的,我们来看下启动到创建主线程的过程

我们看到main入口函数会创建一个ActivityThread线程

public static void main(String[] args) {
    //....
 
    //创建Looper和MessageQueue对象,用于处理主线程的消息
    Looper.prepareMainLooper();
 
    //创建ActivityThread对象
    ActivityThread thread = new ActivityThread(); 
 
    //建立Binder通道(创建新线程)
    thread.attach(false);
 
    Looper.loop(); //消息循环运行
    throw new RuntimeException("Main thread loop unexpectedly exited");
}

3.2 ActivityThread线程里面有两个集合

Activity信息全部被存储在ActivityThread的成员变量mActivities中

mServices则保存了所有service的信息 

public final class ActivityThread {
    //... 
    final H mH = new H();
    final ArrayMap<IBinder, ActivityClientRecord> mActivities = new ArrayMap<>();
    final ArrayMap<IBinder, Service> mServices = new ArrayMap<>();
 
    final ApplicationThread mAppThread = new ApplicationThread();  
    private class ApplicationThread extends ApplicationThreadNative {    
       //...  
    }
    private class H extends Handler {
            //...
    }
    //...
 
}

3.3 ActivityThread初始化

  • 开启消息循环。调用Looper.prepareLoop() Looper.loop(),开启主线程的消息循环,以便于ApplicationThread调用ActivityThread中的生命周期方法。
  • 通知ActivityManagerService。调用ActivityThread.attach()方法,attach()方法在调用了attachApplication()将ApplicationThread这个Binder交给了ActivityManagerService,意味着ActivityManagerService可以通过ApplicationThread控制我们的应用,建立了服务器端对客户端的通信渠道。
  • 添加GCWatcher。在attach()方法中,添加了监听dialvik内存使用情况得监听者GcWatcher,当内存使用超过总容量的3/4,则打印Log进行记录,并且调用ActivityManagerService的releaseSomeActivities()进行内存释放操作,以防止内存溢出导致应用崩溃。

3.4 调用bindApplication()方法绑定application 

attach()方法在调用了attachApplication()之后,经过一系列操作,最后调用了ApplicationThread的bindApplication()方法,bindApplication中通过消息机制,sendMessage到ActivityThread,handleMessage调用了ActivityThread的handleBindApplication()。通过反射创建了Application对象,然后onCreate创建activity

 private void handleBindApplication(AppBindData data) {
       //创建appContext 
      final ContextImpl appContext = ContextImpl.createAppContext(this, data.info);
      try {
            // If the app is being launched for full backup or restore, bring it up in
            // a restricted environment with the base application class.
            //通过反射创建Application
            app = data.info.makeApplication(data.restrictedBackupMode, null);
            mInitialApplication = app;
 
           try {
                //调用Application的onCreate方法
                mInstrumentation.callApplicationOnCreate(app);
            } catch (Exception e) {
            }
        }
}
 
 public void callApplicationOnCreate(Application app) {
     app.onCreate();
 }

3.5 总结

ActivityThread通过ApplicationThread和AMS进行进程间通讯,AMS接受 ActivityThread的请求后会回调ApplicationThread中的Binder方法,然后ApplicationThread会向H发送消息,H收到消息后会将ApplicationThread中的逻辑切换到ActivityThread中去执行,即切换到主线程中去执行

四 线程的创建

4.1 继承 Thread 类

class MyThread extends Thread {
    @Override
    public void run() {
      
     }
}
//启动线程
new MyThread().start();

4.2 实现 Runnable 接口

class MyRunnable  implements Runnable {
	@Override
	public void run() {
		//具体耗时逻辑
	}
}

//启动线程
new Thread(new MyRunnable()).start();

4.3 使用Thread匿名内部类

new Thread(new Runnable() {
	@Override
	public void run() {

	}
}).start();

五 AsyncTask异步任务

5.1 创建异步任务

/**
 * 第一个参数String类型,可以传单个类型也可以传类型数组,doInBackground里面获取的参数
 * 第二个参数Float类型,任务进度 onProgressUpdate获取
 * 第三个参数String类型,处理结果 onPostExecute 获取
 */
public class MyAsyncTask extends AsyncTask<String,Float,String>{
	@Override
	protected void onPreExecute() {
		super.onPreExecute();
		//准备执行,可以做一些准备工作,比如弹缓冲框,初始化数据等
	}

	@Override
	protected String doInBackground(String... strings) {
		//String参数数组,传几个就接受几个
		//处理耗时任务,子线程
		//返回处理后的结果
		String param1=strings[1];
		String param2=strings[1];
		String param3=strings[1];
		return null;
	}

	@Override
	protected void onProgressUpdate(Float... values) {
		super.onProgressUpdate(values);
		//任务处理进度
	}

	@Override
	protected void onPostExecute(String s) {
		super.onPostExecute(s);
		//执行结果返回,UI线程
	}


	@Override
	protected void onCancelled() {
		super.onCancelled();
		//处理异步任务
	}
}

5.2 执行异步任务

 new MyAsyncTask().execute("1","2","3");

5.3 也可以线程池执行

//可以用内置线程池AsyncTask.SERIAL_EXECUTOR(单线程顺序执行),AsyncTask.THREAD_POOL_EXECUTOR(多线程并发执行)
//也可以用自定义线程池newCachedThreadPool,newFixedThreadPool,newScheduledThreadPool,newSingleThreadExecutor
new MyAsyncTask().executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR,"1","2","3");

六 HandlerThread

6.1 作用:

频繁创建线程内存开销大,于是HandlerThread 可以自己在子线程中使用looper轮询,避免多次创建线程

6.2 特点

  • HandlerThread是一个线程类,它继承了Thread
  • 它的内部有自己的Looper对象,可以进行loop轮询
  • 使用HandlerThread的looper创建的Handler在handleMessage中可以进行耗时操作,因为它是执行在子线程的
  • 优点是不会阻塞主线程,缺点是不能同时执行多任务,需要有序执行,执行效率低

6.3 场景

HandlerThread其实就是一个线程;HandlerThread比较适用于单线程+异步队列的场景,比如IO读写操作数据库、文件等,耗时不多而且也不会产生较大的阻塞。对于网络IO操作,HandlerThread并不适合,因为它只有一个线程,还得排队一个一个等着 

6.4 使用

创建Handle并绑定HandlerThread的循环器

//开启一个下载任务
private Handler DownloadHandler(){
	if(handler == null){
		// 步骤1:创建HandlerThread实例对象
		// 传入参数 = 线程名字,作用 = 标记该线程
		handlerThread = new HandlerThread("download");
		// 步骤2:启动线程
		handlerThread.start();
		// 步骤3:创建工作线程Handler & 复写handleMessage()
		// 作用:关联HandlerThread的Looper对象、实现消息处理操作 & 与其他线程进行通信
		// 注:消息处理操作(HandlerMessage())的执行线程 = mHandlerThread所创建的工作线程中执行
		handler = new Handler(handlerThread.getLooper());
	}
	return handler;
}

 创建新线程Runnable

//开启下载进度线程
private class DownloadRunnable implements Runnable{
	private int start = 0;
	private int progres;

	public DownloadRunnable(int progres) {
		this.progres = progres;
	}

	@Override
	public void run() {
		while(start <= progres){
			Log.i("下载进度", ": "+start);
			start += 10;
		}
	}
}

开启子线程

//开启下载线程
Handler dowmhandler = DownloadHandler();
dowmhandler.post(new DownloadRunnable(100));

七 线程池

7.1 线程池作用就是用来放线程的,可以有效的管理线程,重用线程,更合理的利用系统资源,从而减小内存的开销的系统资源的不稳定。

7.2 线程池的种类

可配置线程池

ThreadPoolExecutor里面参数根据场景自己配置

指定场景线程池

FixedThreadPool固定线程池

CachedThreadPool缓存线程池

SingleThreadExecutor按顺序执行线程池

ScheduledThreadPool定时周期任务线程池

7.3 ThreadPoolExecutor可配置线程池使用

//构造函数
public ThreadPoolExecutor(int corePoolSize, 
                          int maximumPoolSize, 
                          long keepAliveTime, 
                          TimeUnit unit, 
                          BlockingQueue<Runnable> workQueue, 
                          ThreadFactory threadFactory, 
                          RejectedExecutionHandler handler) 
{

    throw new RuntimeException("Stub!");

}

corePoolSize:核心线程数。默认情况下线程池是空的,只是任务提交时才会创建线程。如果当前运行的线程数少于corePoolSize,则会创建新线程来处理任务;如果等于或者等于corePoolSize,则不再创建。如果调用线程池的prestartAllcoreThread方法,线程池会提前创建并启动所有的核心线程来等待任务。
maximumPoolSize:线程池允许创建的最大线程数。如果任务队列满了并且线程数小于maximumPoolSize时,则线程池仍然会创建新的线程来处理任务。
keepAliveTime:非核心线程闲置的超时事件。超过这个事件则回收。如果任务很多,并且每个任务的执行时间很短,则可以调大keepAliveTime来提高线程的利用率。另外,如果设置allowCoreThreadTimeOut属性来true时,keepAliveTime也会应用到核心线程上。
TimeUnit:keepAliveTime参数的时间单位。可选的单位有天Days、小时HOURS、分钟MINUTES、秒SECONDS、毫秒MILLISECONDS等。
workQueue:任务队列。如果当前线程数大于corePoolSzie,则将任务添加到此任务队列中。该任务队列是BlockingQueue类型的,即阻塞队列(阻塞队列在另一篇博文中会提到)。
ThreadFactory:线程工厂。可以使用线程工厂给每个创建出来的线程设置名字。一般情况下无须设置该参数。
RejectedExecutionHandler:饱和策略,这是当前任务队列和线程池都满了时所采取的应对策略,默认是AbordPolicy,表示无法处理新任务,并抛出RejectedExecutionException异常。

简单创建线程示例:

//创建线程池
public void createExecutorService(){
	//maximumPoolSize设置为2 ,拒绝策略为AbortPolic策略,直接抛出异常
	//线程池能执行的最大任务数为3(最大线程数)+0(队列长度)   SynchronousQueue没有容量
	ExecutorService executorService = new ThreadPoolExecutor(2, 3, 1000, TimeUnit.MILLISECONDS, new SynchronousQueue<>(), Executors.defaultThreadFactory(),new ThreadPoolExecutor.AbortPolicy());
	//添加并执行线程
	executorService.execute(new TaskRunnable());
}

//创建任务线程
class TaskRunnable implements Runnable{
	@Override
	public void run() {
		Log.e("taskRunnable", "run: "+Thread.currentThread().getName()+"---"+ new Date());
	}
}

线程工厂的使用,我们可以在工厂里面自定义线程

//创建线程池工厂
public void createExecutorService(){
	//maximumPoolSize设置为2 ,拒绝策略为AbortPolic策略,直接抛出异常
	//线程池能执行的最大任务数为3(最大线程数)+0(队列长度)   SynchronousQueue没有容量
	ExecutorService executorService = new ThreadPoolExecutor(2, 3, 1000, TimeUnit.MILLISECONDS, new SynchronousQueue<>(),
			//线程池工厂
			new ThreadFactory() {
				public Thread newThread(Runnable runnable) {
					//可以新建线程,进行命名、优先级等设置
					Log.e("taskRunnable", "newThread: "+"线程"+runnable.hashCode()+"创建" );
					//线程命名
					Thread thread = new Thread(runnable,"threadPool"+runnable.hashCode());
					return thread;
				}
			},
			new ThreadPoolExecutor.AbortPolicy());
	//添加并执行线程
	executorService.execute(new TaskRunnable());
}

//创建任务线程
class TaskRunnable implements Runnable{
	@Override
	public void run() {
		Log.e("taskRunnable", "run: "+Thread.currentThread().getName()+"---"+ new Date());
	}
}

饱和策略的使用

创建线程池时,为防止资源被耗尽,任务队列都会选择创建有界任务队列,但种模式下如果出现任务队列已满且线程池创建的线程数达到你设置的最大线程数时,这时就需要你指定ThreadPoolExecutor的RejectedExecutionHandler参数即合理的拒绝策略,来处理线程池"超载"的情况。ThreadPoolExecutor自带的拒绝策略如下:

  • AbortPolicy策略:该策略会直接抛出异常,阻止系统正常工作,不建议使用,会造成阻塞,导致线程执行到这卡住;
  • CallerRunsPolicy策略:如果线程池的线程数量达到上限,该策略会把任务队列中的任务放在调用者线程当中运行;
  • DiscardOledestPolicy策略:该策略会丢弃任务队列中最老的一个任务,也就是当前任务队列中最先被添加进去的,马上要被执行的那个任务,并尝试再次提交;
  • DiscardPolicy策略:该策略会默默丢弃无法处理的任务,不予任何处理。当然使用此策略,业务场景中需允许任务的丢失;

拒绝策略示例

//饱和策略的使用
public void createExecutorService() {
	//maximumPoolSize设置为2 ,拒绝策略为AbortPolic策略,直接抛出异常
	//线程池能执行的最大任务数为3(最大线程数)+0(队列长度)   SynchronousQueue没有容量
	ExecutorService executorService = new ThreadPoolExecutor(2, 3, 1000, TimeUnit.MILLISECONDS, new SynchronousQueue<>(), Executors.defaultThreadFactory(),
			//拒绝策略
			new RejectedExecutionHandler() {
				@Override
				public void rejectedExecution(Runnable runnable, ThreadPoolExecutor threadPoolExecutor) {
					Log.e("taskRunnable", runnable.toString()+"执行了拒绝策略");
				}
			}
	);
	//添加并执行线程
	executorService.execute(new TaskRunnable());
}


//创建任务线程
class TaskRunnable implements Runnable{
	@Override
	public void run() {
		Log.e("taskRunnable", "run: "+Thread.currentThread().getName()+"---"+ new Date());
	}
}

 关闭线程池

//关闭线程池
executorService.shutdown();
  • 可以通过调用线程池的shutdown方法或shutdownNow方法来关闭线程池。它们的原理是遍历线程池中的工作线程,然后逐个调用线程的interrupt方法来中断线程,所以无法响应中断的任务可能无法终止。
  • 当所有的任务都已关闭后,才表示线程池关闭成功,这时调用isTerminated方法会返回true。通常调用shutdown方法来关闭线程池如果任务不一定要执行完,则可以调用shutdownNow方法

 7.4 FixedThreadPool固定线程池使用

public static ExecutorService newFixedThreadPool(int nThreads){
	return new ThreadPoolExecutor(nThreads , nThreads, 0L , TimeUnit.MILLISECONDS,new LinkedBlockingQueue<Runnable>()
    );
}
  • FixedThreadPool只有核心线程,并且数量是固定的,没有非核心线程。
  • keepAliveTime设置为0L,意味着多余的线程会被立即终止。因为不会产生多余的线程,所以KeepAliveTime是无效的参数
  • 当执行execute方法时,如果当前运行的线程未达到corePoolSize(核心线程数)时就创建核心线程来处理任务,如果达到了核心线程数则将任务添加到LinkedBlockingQueue中
  • FixedThreadPool就是一个有固定数量核心线程的线程池,并且这些核心线程不会被回收
  • 当线程超过corePoolSize时,就将任务存储在任务队列中。当线程池有空闲线程时,则从任务队列中去取任务执行

创建固定线程池

ExecutorService fixedThreadPool=Executors.newFixedThreadPool(5);
//execute和submit区别
//1. execute只能提交Runnable类型的任务,没有返回值,而submit既能提交Runnable类型任务也能提交Callable类型任务,返回Future类型。
//2. execute方法提交的任务异常是直接抛出的,而submit方法是是捕获了异常的,当调用FutureTask的get方法时,才会抛出异常
fixedThreadPool.submit(new TaskRunnable());
fixedThreadPool.execute(new TaskRunnable());

7.5 CachedThreadPool缓存线程池的使用

public static ExecutorService newCachedThreadPool(){
	return new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS, new SynchronousQueue<Runnable>()
    );
}
  • CachedThreadPool的corePoolSize为0,maximumPoolSize设置为Integer.MAX_VALUE,意味着CachedThreadPool没有核心线程,非核心线程是无界的
  • KeepAliveTime设置为60L,则空闲线程等待新任务的最长时间为60s
  • 在此用了阻塞队列SynchronousQueue,它是一个不存储元素的阻塞队列,每个插入操作必须等待另一个线程的移除操作,同样任何一个移除操作都等待另一个线程的插入操作
  • 因为maximumPoolSize是无界的,所以如果提交的任务大于线程池中线程处理任务的速度就会不断的创建新线程,每次提交任务都会立即有线程去处理
  • 所以,CachedThreadPool比较适于大量的需要立即处理并且耗时较少的任务。

创建缓存线程池

//缓存线程池
ExecutorService cachedThreadPool=Executors.newCachedThreadPool();
cachedThreadPool.submit(new TaskRunnable());

7.6 SingleThreadExecutor单工作线程的线程池的使用

public static ExecutorService newSingleThreadExecutor{
	return new FinalizableDelegatedExecutorService(
               new ThreadPoolExecutor(1,1,0L,TimeUnit.MILLISECONDS,new LinkedBlockingQueue<Runnable>()
               )
     );
}
  • corePoolSize和maximumPoolSize都为1,意味着SingleThreadExecutor只有一个核心线程,其他的参数都和FixedThreadPool一样
  • 当执行execute方法时,如果当前运行的线程数未达到核心线程数,也就是当前没有运行的线程,则创建一个新线程来处理任务。
  • 如果当前有运行的线程,则将任务添加到阻塞队列LinkedBlockingQueue中,因此,SingleThreadExecutor能确保所有的任务在一个线程中按照顺序逐一执行

创建顺序线程池

//顺序线程池
ExecutorService singleThreadExecutor=Executors.newSingleThreadExecutor();
singleThreadExecutor.submit(new TaskRunnable());

 7.7 ScheduledThreadPool定时周期任务线程池

public staic ScheduledExecutorService newScheduledThreadPool(int corePoolSize){
	return new ScheduledThreadPoolExecutor(corePoolSize);
}

public ScheduledThreadPoolExecutor(int corePoolSize){
	super(corePoolSize,Integer.MAX_VALUE,DEFAULT_KEEPALIVE_MILLIS,MILLISECONDS,new DelayedWorkQueue());
}
  • 当执行ScheduledThreadPoolExecutor的scheduleAtFixedRate或者scheduleWithFixDelay方法时,会向DelayedWorkQueue添加一个实现RunnableScheduledFuture接口的ScheduledFutureTask(任务的包装类)
  • 检查运行的线程是否达到corePoolSize。如果没有则新建线程并启动它,但不是立即去执行任务,而是去DelayedWorkQueue中取出ScheduledFutureTask,然后去执行任务
  • 如果运行的线程达到了corePoolSize时,则将任务添加到DelayedWorkQueue中。DelayedWorkQueue会将任务进行排序,先要执行的任务放在队列的前面
  • 这和上面介绍的几个线程池不同的是,当执行完任务后,会将ScheduledFutureTask的time变量改为下次要执行的时间并放回到DelayedWorkQueue中

创建周期性线程池

//周期定时线程池
ExecutorService scheduledThreadPool=Executors.newScheduledThreadPool(5);
scheduledThreadPool.submit(new TaskRunnable());

 7.8 执行线程的方式execute和submit区别

fixedThreadPool.submit(new TaskRunnable());
fixedThreadPool.execute(new TaskRunnable());
  1. execute只能提交Runnable类型的任务,没有返回值,而submit既能提交Runnable类型任务也能提交Callable类型任务,返回Future类型。
  2. execute方法提交的任务异常是直接抛出的,而submit方法是是捕获了异常的,当调用FutureTask的get方法时,才会抛出异常

 八 Rxjava的对线程使用

8.1 RxJava 是一种响应式编程,来创建基于事件的异步操作库。常用于流时间的处理和配合Retrofit进行异步请求和线程切换

8.2 依赖库的集成

//RxAndroid中包含RxJava的内容,只引入RxAndroid还是会报错
dependencies {
    compile 'io.reactivex.rxjava2:rxjava:2.1.3'
    compile 'io.reactivex.rxjava2:rxandroid:2.0.1'
}

8.3 流时间处理的简单示例

观察者和被观察者通过subscribe订阅,订阅完成后被观察者就可以像观察者发送数据

 Observable.create(new ObservableOnSubscribe<Integer>() {

	@Override
	public void subscribe(ObservableEmitter<Integer> emitter) throws Exception {
		emitter.onNext(1);
		emitter.onNext(2);
		emitter.onNext(3);
		emitter.onComplete();
	}
}).subscribe(new Observer<Integer>() {

	@Override
	public void onSubscribe(Disposable d) {
		Log.d(TAG, "开始采用subscribe连接");
	}

	@Override
	public void onNext(Integer value) {
		Log.d(TAG, "对Next事件" + value + "作出响应");
	}

	@Override
	public void onError(Throwable e) {
		Log.d(TAG, "对Error事件作出响应");
	}

	@Override
	public void onComplete() {
		Log.d(TAG, "对Complete事件作出响应");
	}

});        

8.4 异步处理,线程切换示例

Observable.create(new Observable.OnSubscribe<String>() {
				@Override
				public void call(Subscriber<? super String> subscriber) {
					Log.e(TAG, "===create: " + Thread.currentThread().getName());
					subscriber.onNext("1");
				}
			})
			.map(new Func1<String, Integer>() {
				@Override
				public Integer call(String s) {
					Log.e(TAG, "===String -> Integer: " + Thread.currentThread().getName());
					return Integer.valueOf(s);
				}
			})
			.flatMap(new Func1<Integer, Observable<String>>() {
				@Override
				public Observable<String> call(final Integer integer) {
					Log.e(TAG, "===Integer->Observable: " + Thread.currentThread().getName());
					return forEach(integer);
				}
			})
			.map(new Func1<String, Long>() {
				@Override
				public Long call(String s) {
					Log.e(TAG, "===String->Long: " + Thread.currentThread().getName());
					return Long.parseLong(s);
				}
			})
			.subscribeOn(Schedulers.io())
			.observeOn(AndroidSchedulers.mainThread())
			.subscribe(new Subscriber<Long>() {
				@Override
				public void onCompleted() {
				}

				@Override
				public void onError(Throwable e) {
				}

				@Override
				public void onNext(Long aLong) {
					Log.e(TAG, "===onNext: " + Thread.currentThread().getName());
				}
			});

可以看到线程切换主要是下面这两行

.subscribeOn(Schedulers.io())//切换到子线程
.observeOn(AndroidSchedulers.mainThread())//切换到UI线程

再配合map,flatMap等这些操作符可以实现复杂的数据变化,并且能保持流程逻辑的清晰。

九 Kotlin携程对线程的处理

 9.1 kotlin的携程本质上是依赖线程的,但跟线程是有区别的,是一个线程调度的框架。

线程会调用系统资源来处理任务,但携程是在编辑语言层面实现的,即不需要多核CPU的支持就能实现并发。

携程的主要任务就是协助线程,是线程的助手,分担线程的任务。

9.2 携程远程库的依赖

implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.1.1"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.1.1"

9.3 创建携程runBlocking方法

private fun main() {
    // 不指定调度器,在方法调用的线程执行
    runBlocking {
        // 这里是协程的作用域
        Log.d("liduo", "123")
    }
}

private fun main() {
    // 指定调度器,在IO线程中执行
    runBlocking(Dispatchers.IO) {
        // 这里是协程的作用域
        Log.d("liduo", "123")
    }
}

9.4 launch方法

private fun main() {
    // 作用域为GlobalScope
    // 懒启动,主线程执行
    val job = GlobalScope.launch(
            context = Dispatchers.Main, 
            start = CoroutineStart.LAZY) {
        Log.d("liduo", "123")
    }
    // 启动协程
    job.start()
}

9.5 async方法

//suspend标记
private suspend fun test(): Int {
    //作用域为GlobalScope,返回值为Int类型,,泛型可省略,自动推断
    val deffer = GlobalScope.async<Int> {
        Log.d("aa", "123")
        // 延时1s
        delay(1000)
    }
    // 获取返回值
    return deffer.await()
}

9.6 线程切换withContext方法

private suspend fun test() {
    // IO线程启动并执行,启动模式DEFAULT
    GlobalScope.launch(Dispatchers.IO) {
        Log.d("aa", "start")
        // 线程主切换并挂起,泛型可省略,自动推断
        val result = withContext<String>(Dispatchers.Main) {
            // 网络请求
            "json data"
        }
        // 切换回IO线程
        Log.d("aa", result)
    }
}

9.7 携程间通信Channel

Channel用于协程间的通信。Channel本质上是一个并发安全的队列,类似BlockingQueue。在使用时,通过调用同一个Channel对象的send和receive方法实现通信

suspend fun main() {
    // 创建
    val channel = Channel<Int>()

    val producer = GlobalScope.launch {
        var i = 0
        while (true){
            // 发送
            channel.send(i++)
            delay(1000)
            // channel不需要时要及时关闭
            if(i == 10)
                channel.close()
        }
    }

    // 写法1:常规
    val consumer = GlobalScope.launch {
        while(true){
            // 接收
            val element = channel.receive()
            Log.d("liduo", "$element")
        }
    }
    
    // 写法2:迭代器
    val consumer = GlobalScope.launch {
        val iterator = channel.iterator()
        while(iterator.hasNext()){
            // 接收
            val element = iterator.next()
            Log.d("liduo", "$element")
        }
    }
    
    // 写法3:增强for循环
    val consumer = GlobalScope.launch {
        for(element in channel){
            Log.d("liduo", "$element")
        }
    }
    
    // 上面的协程由于不是懒启动,因此创建完成直接就会start去执行
    // 也就是说,代码走到这里,上面的两个协程已经开始工作
    // join方法会挂起当前协程,而不是上面已经启动的两个协程
    // 在Android环境中,下面两行代码可以不用添加
    // producer.join()
    // consumer.join()
}

9.8  多个接收者BroadcastChannel

当遇到一个发送者对应多个接收者的场景时,可以使用BroadcastChannel。创建BroadcastChannel对象时,必须指定容量大小。接收者通过调用BroadcastChannel对象的openSubscription方法,获取ReceiveChannel对象来接收消息

// 创建BroadcastChannel,容量为5
val broadcastChannel = BroadcastChannel<Int>(5)

// 创建发送者协程
GlobalScope.launch {
    // 发送 1
    broadcastChannel.send(1)
    delay(100)
    // 发送 2
    broadcastChannel.send(2)
    // 关闭
    broadcastChannel.close()
}.join()

// 创建接收者1协程
GlobalScope.launch {
    // 获取ReceiveChannel
    val receiveChannel = broadcastChannel.openSubscription()
    // 接收
    for (element in receiveChannel) {
        Log.d("receiver_1: ", "$element")
    }
}.join()

// 创建接收者2协程
GlobalScope.launch {
    // 获取ReceiveChannel
    val receiveChannel = broadcastChannel.openSubscription()
    // 接收
    for (element in receiveChannel) {
        Log.d("receiver_2: ", "$element")
    }
}.join()

9.9 多路复用select,类似Java中Nio的select方法

private suspend fun test() {
    // 创建一个Channel列表
    val channelList = mutableListOf<Channel<Int>>()
    // 假设其中有5个Channel
    channelList.add(Channel())
    channelList.add(Channel())
    channelList.add(Channel())
    channelList.add(Channel())
    channelList.add(Channel())
    
    // 调用select方法,协程挂起
    val result = select<Int> {
        // 对5个Channel进行注册监听,等待接收
        channelList.forEach {
            it.onReceive
        }
    }
    // 当5个Channel中任意一个接收到消息时,select挂起恢复
    // 并将返回值赋给result
    Log.d("liduo", "$result")
}

9.10 携程异步流,类似于RxJava的响应式编程

// 在主线程上调用
GlobalScope.launch(Dispatchers.Main) {
    // 创建流
    flow<Int> {
        // 挂起,输出返回值
        emit(1)
      // 设置流执行的线程,并消费流
    }.flowOn(Dispatchers.IO).collect {
            Log.d("liduo", "$it")
        }
}.join()

9.11 携程的调度器

  1. Dispatchers.Default:默认调度器。它使用JVM的共享线程池,该调度器的最大并发度是CPU的核心数,默认为2。
  2. Dispatchers.Unconfined:非受限调度器。该调度器不会限制代码在指定的线程上执行。即挂起函数后面的代码不会主动恢复到挂起之前的线程去执行,而是在执行挂起函数的线程上执行。
  3. Dispatchers.IO:IO调度器。它将阻塞的IO任务分流到一个共享的线程池中。该调度器和Dispatchers.Default共享线程。
  4. Dispatchers.Main:主线程调度器。一般用于操作与更新UI。

注意:

Dispatchers.Default调度器和Dispatchers.IO 调度器分配的线程为守护线程 

9.12 协程的启动模式

  1. CoroutineStart.DEFAULT:立即执行协程,可以随时取消。
  2. CoroutineStart.LAZY:创建一个协程,但不执行,在用户需要时手动触发执行。
  3. CoroutineStart.ATOMIC:立即执行协程,但在协程执行前无法取消。目前处于试验阶段。
  4. CoroutineStart.UNDISPATCHED:立即在当前线程执行协程,直到遇到第一个挂起。目前处于试验阶段。

十 总结

10.1 java环境可以根据场景选择不同的线程方式

  • 简单的异步处理可以直接new Thread
  • 需要进度的或者下载的可以用MyAsyncTask 异步任务
  • 需要频繁切换线程或者和网络请求配合的可以用rxjava

10.2 kotlin 可以直接用携程了

因为携程包含了线程的功能,也包含了rxjava事件流的能力,可以处理任何异步操作的场景

 文章来源地址https://www.toymoban.com/news/detail-428192.html

到了这里,关于Android之 线程详解的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • Linux和windows进程同步与线程同步那些事儿(三): Linux线程同步详解示例

    Linux和windows进程同步与线程同步那些事儿(一) Linux和windows进程同步与线程同步那些事儿(二): windows线程同步详解示例 Linux和windows进程同步与线程同步那些事儿(三): Linux线程同步详解示例 Linux和windows进程同步与线程同步那些事儿(四):windows 下进程同步 Linux和wi

    2024年02月01日
    浏览(26)
  • 操作系统进程线程(三)—进程状态、同步互斥、锁、死锁

    原子操作的概念 原子操作就是不可中断的一个或者一系列操作。 原子操作如何实现 总线锁定 使用处理器提供的一个LOCK#信号,当一个处理器在总线上输出此信号的时候,其他处理器的请求将被阻塞住,那么该处理器可以独占内存。 缓存锁 总线锁开销比较大,因为把CPU和内

    2024年02月04日
    浏览(31)
  • 【操作系统——进程与线程(一)】

    2.1.1 进程的概念和特征 进程是指正在执行中的程序的实例。它是计算机系统进行资源分配和调度的基本单位。每个进程都有自己的地址空间、堆栈和数据区域,以及与其他进程通信和同步所需要的操作系统资源。 进程具有以下特点: 独立性:进程是独立的执行实体,拥有自

    2024年02月11日
    浏览(35)
  • 操作系统-进程和线程-同步、互斥、死锁

    目录 一、同步互斥  二、互斥的实现方法 2.1软件实现 2.1.1单标志法 2.1.2双标志先检查 2.1.3双标志后检查 2.1.4Petersons算法 2.2硬件实现 2.2.1 TestAndSet指令 2.2.2 Swap指令   三、信号量机制 3.1整形变量  3.2 记录型变量  3.3用信号量实现进程互斥、同步、前驱关系 3.3.1互斥  3.3.2同步

    2024年02月08日
    浏览(29)
  • Android 进程与进程之间的通讯 详解及实现步骤 -- 两个app实现

    分两个app -- 客户端为:jinc1application、服务端为:jinc2application 这是 Android 中最常用的通讯方式,主要用于启动 Activity、Service 等 jinc1application 中的代码 1、 jinc1application代码如下: 注意:要启动的目标包名和包名+类名如下: jinc2application 中的代码 2、jinc2application 这是基于 B

    2024年02月13日
    浏览(34)
  • 深入理解操作系统中进程与线程的区别及切换机制(上)

    所谓进程,大家可以理解为我们打开的应用程序,如微信、QQ、游戏等,但也有系统应用是我们看不见的,可以打开任务管理器一探究竟,我们写的代码程序在服务器上在不运行的情况下,它就是一个二进制文件,并不是进程! 一个进程可以包含一个或者多个线程,但对于

    2024年02月11日
    浏览(30)
  • 深入理解操作系统中进程与线程的区别及切换机制(下)

    上一篇文章中我们了解了进程的执行方式,包括早期单核处理器上的顺序执行以及引入多任务概念实现的伪并行。我们还探讨了进程的状态模型。进程可以处于就绪、运行、阻塞和结束等不同的状态。 在本篇文章中,我将探讨研究进程的状态模型、控制结构和切换机制。希望

    2024年02月11日
    浏览(35)
  • Android之 线程详解

    一 进程 1.1 进程是针对造作系统而言的,打开运行一个应用程序就开始一个进程。对android系统而言,进程就是一个app应用。 1.2 但往往真实中启动一个程序可能并不止开启一个进程,可能还有处于后台的进程,或者其它服务进程。所以我们一般说一个程序至少有一个进程 1.

    2024年02月01日
    浏览(13)
  • [嵌入式系统-32]:RT-Thread -17- 任务、进程、线程的区别

    目录 一、基本概念澄清 1.1 任务 1.2 进程 1.3 线程 1.4 比较 1.5 任务VS进程 1.6 进程 VS 线程 1.7 任务 进程 线程 发展历史 任务(Task): 进程(Process): 线程(Thread): 发展趋势: 二、不同操作系统中任务、进程、线程 2.1 Linux:没人任务,只有进程与线程 进程相关函数: 线程

    2024年02月21日
    浏览(33)
  • android--RxJava线程调度源码详解

     从今天起关闭烦恼,开启赚钱模式,别去想那些乱七八糟的破事了,满脑子都是钱不好吗,肤浅又快乐! 目录 前言  一,IO线程调度 二,安卓主线程调度   学习线程调度的源码之前,我们需要先分析RxJava的源码,关于RxJava的源码,请移步文章android--RxJava源码详解-CSDN博客

    2024年02月20日
    浏览(28)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包