您的位置:首页 > 移动开发 > Android开发

Android多线程与线程池

2016-04-23 23:31 337 查看
一、进程与线程的区别:

进程:是指一个内存中运行的应用程序,每个进程都有自己独立的一块内存空间,一个进程中可以启动多个线程

线程:是程序执行中的一个执行路径即进程的一个执行流程(子任务)多线程:是指程序中包含多条执行路径即在一个进程中可同时执行两个或两个以上的线程大多数程序只有一条执行路线,但现实世界中很多过程都是同时发生的,对于这种情况,可编写有多条执行路径的程序,使程序可以同时执行多个任务(并行)多线程机制使得程序的多个子任务能够同时执行。

线程与进程之间的关系

一个进程中可以有多个线程,但是线程必须有一个父进程线程在进程之中,单线程即进程,但通常一个进程拥有多个线程,其中有一个是主线程。如:Android中的UI线程线程与进程(进程包含线程,线程构成进程)

二、首先,介绍一下线程的生命周期:

线程:是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程的实际运作单位,一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每个线程并执行不同的任务。

生命周期:新建(new)-->就绪(Runnable) -->运行(Running)-->阻塞(Bloack)-->死亡(Dead)

三、控制线程:

再介绍一下线程的常用方法:

void run():创建该类的子类时必须实现的方法

void start():开启线程的方法

static void sleep(long t):释放CPU的执行权,不释放锁

static void sleep(long millis,int nanos):

final void wait():释放CPU的执行权,释放锁

final void notify():

static void yied():可以对当前线程进行临时暂停((让线程将资源释放出来)

四、线程同步:

首先介绍一下为什么需要使用同步:java允许多线程并发控制,当多个线程同时操作一个可共享的资源变量时(如数据的增删改),将会导致数据不准确,相互之间产生冲突,因此加入同步锁以避免在该线程没有完成操作之前,被其他线程的调用,从而保证了该变量的唯一性和准确性。

例如:一共有1000张票,当A来买票的时候,先去查询剩余的票数为1000张,这是B也来买票,也开始查剩余的票数,因为A买的票还没有打印出来,所有B查票也为1000张,B打印出来后,是第1000的,而A这时打印出来的票也是第1000张的,所以就会出现问题,因此需要使用线程同步,保证买票的时候,只能有一个线程进去,其他的线程不能对其进行操作,

同步的方法:有synchronized关键字修饰的方法

同步的代码块:有synchronized关键字修饰的语句块

  表示线程在执行的时候会将object对象上锁。(注意这个对象可以是任意类的对象,通常使用类名.class)。这样就可以自行规定上锁对象。

使用特殊域变量(volatile)实现线程同步

1、volatile关键字为域变量的访问提供了一种免锁机制

2、使用volatile修饰域相当于告诉虚拟机该域可能会被其他线程更新

3、因此每次使用该域就要重新计算,而不是使用寄存器中的值

4、volatile不会提供任何原子操作,它也不能用来修饰final类型的变量,解释原子变量:Linux内核中比较常见的一个名词,是原子操作的基本单位,也就是cup操作的(不能继续向下分的),原子变量有基本数据类型,但是Long不属于,因为Long类型需要分为高位和低位。

五、线程池:

(1)线程池的组成部分:

1、线程池管理器(ThreadPool):用于创建并管理线程池,包括创建线程池,销毁线程池,添加新任务

2、工作线程(PoolWorker):线程池中线程,在没有任务时处于等待状态,可以循环的执行任务

3、任务接口(Task):每个任务必须实现的接口,以供工作线程调度任务的执行,它主要规定了任务的入口,任务执行完后的收尾工作,任务的执行状态等

4、任务队列(taskQueue):用于存放没有处理的任务,提供一种缓冲机制

(2)关键类:

1、Executor接口表示线程池,它的execute(Runnable task)方法用来执行Runnable类型的任务

2、ExecutorService中声明了管理线程池的一些方法,比如用语关闭线程池的shutdown()方法等,同时它是Executor的子类,实现了Executor接口

3、Executors类中包含一些静态方法,它们负责生成各种类型的线程池ExecutorService实例

六、Android为我们封住好了5中线程池可以供我们使用,当然如果不满足我们的需求,我们也可以自己进行自定义线程池。下面我们开始介绍这五中封住好的线程池。

1.第一种newFixedThreadPool,它会返回一个固定线程数量的线程池,该线程池中的线程数量始终不变,即不会再创建新的线程,也不会销毁以创建好的线程,自始至终都是那几个固定的线程在工作,例如,下一行代码,就是一共有3个线程,它的任务队列是先进先出的。

ExecutorService fixedThreadPool = Executors.newFixedThreadPool(3);


可以进行一下小测试:

for (int i = 0; i < 10; i++) {
final int index = i;
//execute 向线程池中添加任务,接收一个Runnable
//对象的参数
fixedThreadPool.execute(new Runnable() {
@Override
public void run() {

//Thread.currentThread().getName()获取线程的名字
String threadName = Thread.currentThread().getName();
Log.d("Sysout",index + ":" + threadName);
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
}

2.第二种线程池newCachedThreadPool
它会返回一个可以根据实际情况调整,线程池中线程数量的线程池,即该线程池中的线程数量是不确定的,是根据实际情况,动态调整的,它的最大线程数是int类型的上限,为了保证工作线程不会越来越多,该线程池设定了工作线程的存活时间-60s,即如果工作线程没有工作达到60s的时间,就会把这个线程销毁掉。
ExecutorService cachedThreadPool = Executors.newCachedThreadPool();

3.第三种线程池newSingleThreadExecutor该方法会返回一个只有一个线程的线程池,每次只能执行一个线程任务,队列策略FIFO,实际上和FixedThreadPool的参数给1是一样的(即,最大线程数,和核心线程数都是1)
ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor();


4.第四种线程池newScheduledThreadPool
这种线程池可以控制线程池内的线程,定时或者周期性的执行某个任务,创建的时候,需要指定核心线程数,最大线程数,也是int类型的最大值
ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(3);
scheduledThreadPool,调用schedule方法来向线程池中添加一个Runnable任务

第一个参数是Runnable对象,第二个参数是延迟的时间,第三个参数是时间单位,时间单位封装在TimeUnit类中
scheduledThreadPool.schedule(new Runnable() {
@Override
public void run() {
Log.d("MainActivity", "执行");
}
}, 20, TimeUnit.SECONDS);


5.第五种线程池newSingleThreadScheduledExecutor
功能上与上一种是一样的,唯一区别,核心线程数是1,和上一种参数给1 产生的效果是一样的
ScheduledExecutorService singleScheduledThreadPool = Executors.newSingleThreadScheduledExecutor();


向线程池中添加新的任务,调用threadPool的execute方法,参数是,Runnable,如下
threadPool.execute(new Runnable() {
@Override
public void run() {

}
});
//关闭线程池,线程池会完成现有的工作,然后关闭
threadPool.shutdown();
//线程池会放弃现有的工作,立刻关闭
threadPool.shutdownNow();


我们使用自定义的线程池:

(1)首先自定义创建一个MyThreadPool类,通过这个类来获取线程池对象

public class MyThreadPool {
private static MyThreadPool ourInstance;
//线程池对象
private ThreadPoolExecutor threadPoolExecutor;
//写一个静态方法,来暴露自己
public static MyThreadPool getInstance() {
//最外面if是为了提高效率
if (ourInstance == null) {
//加上同步代码块,来保证new的语句只能有同一个线程执行
synchronized (MyThreadPool.class) {
if (ourInstance == null) {
ourInstance = new MyThreadPool();
}
}
}
return ourInstance;
}
private MyThreadPool() {
//在构造方法里对线程池对象初始化,这里我们使用5个参数的构造方法.
//第一个参数(int corePoolSize):核心线程数量,核心线程会随着线程池的创建,同时创建出来,如果没有任务,核心线程就会等待,并不会销毁,只有核心线程,才能执行任务,通常,核心线程数 是CPU核心数 + 1.
//第二个参数(int maximumPoolSize): 最大线程数,线程池中所能容纳的线程数量的上线,它一定大于等于核心线程数,超过核心线程数的线程叫做工作线程,工作线程并不能执行任务,它们会等待核心线程执行完毕,再开始执行,所有的工作线程,满足先进先出的策略,工作线程是有可能被销毁的,当工作线程没有任务可做的时候,同时时间超过了keepAliveTime所规定的时间,就会把该工作线程销毁掉.    //第三个参数(long keepAliveTime): 保持活动时间,确定工作线程 没有工作时还能存活的最大时间.
//第四个参数(TimeUnit unit): 用来确定keepAliveTime的单位,最大的是天,最小的是纳秒.
//第五个参数(BlockingQueue<Runnable> workQueue):任务队列,当线程池内的线程数达到线程池规定的最大线程数时,还继续向线程池中提交任务,这些任务就会放进任务队列,BlockingQueue是一个接口对象,我们在使用的时候,需要使用实现该接口的实现类,Android为我们封装了几个常用的任务队列.
//常用的任务队列有:
//1.LinkedBlockingQueue:无界任务队列.
//2.SynchronousQueue:直接提交的任务队列,该任务队列不能储存任务,所以一般配合线程池的最大线程数无上限(int的最大值)的这种情况使用.
//3.DelayQueue:等待队列,它会让工作队列里的任务等待一会(自己定义的时间)再进入线程池.
//4.ArrayBlockingQueue:有界任务队列.
//5.PriorityBlockingQueue:优先级任务队列,可以指定每个任务的优先级,优先级高的,先进入线程池 .
//CPU 核心数:
int CPUCores = Runtime.getRuntime().availableProcessors();
threadPoolExecutor = new ThreadPoolExecutor(CPUCores + 1,
CPUCores * 2 + 1,
60l,
TimeUnit.SECONDS,
new LinkedBlockingQueue<Runnable>());
}
//获得线程池对象
public ThreadPoolExecutor getThreadPoolExecutor() {
return threadPoolExecutor;
}
}
//当线程池中的核心线程数满了的时候,若还有线程需要进来,仅需要在任务队列里等待,如果定义的任务队里的数是无上限的,那么非核心线程中不会有线程进入,当核心线程中有空缺的时候,才有让任务队列中的线程进入,并执行。


//当返回一个线程池的对象的时候,我们就可以使用了,下面就是使用我们自己自定义的线程池

ThreadPoolExecutor threadPool = MyThreadPool.getInstance().getThreadPoolExecutor();

//需要关闭线程池

threadPool.shutdown();


本人菜鸟一个,有什么不对的地方,希望大家能指正,大神勿喷!
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: