进程:可以被看做是程序的实体,同样,它也是线程的容器。是受操作系统管理的最小单元。

线程:操作系统调度的最小单元。

多线程的优点:

​ 使用多线程可以减少程序的相应时间

​ 与进程比,线程创建和切换开销很小,同时多线程在数据共享方面效率非常高

​ 多CPU或者多核计算机本身就具备执行多线程的能力

​ 使用多线程能简化成 程序的结构,使程序便于维护和理解

线程的几种状态:

New:新创建状态。

Runnable:可运行状态。一旦调用start,线程就处于Runnable状态。

Blocked:阻塞状态。表示线程被锁阻塞,暂时不活动。(请求锁)

Waiting:等待状态。线程暂时不活动,并且不运行任何代码,这消耗最少的资源,直到线程调度器重新激活它。

Time Waiting:超时等待状态。和等待状态不同的是,它是可以在指定的时间自由返回的。

Terminated:终止状态。表示当前线程已经执行完毕,可能是run方法执行完毕正常退出,也可能是因为未捕获的异常导致了run方法的终止。

线程的创建:

1.继承Thread类,重写run方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
public class TestThread extends Thread {

@Override
public void run() {
Log.e("thread", "create");
}
}


public void run() {
Thread thread = new TestThread();
thread.run();
}

2.实现Runnable接口,并实现run方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class TestRunnable implements Runnable {

@Override
public void run() {
Log.e("thread", "create");
}
}


public void run() {
TestRunnable runnable = new TestRunnable();
Thread mThread = new Thread(runnable);
mThread.start();
}

3.实现Callable接口,重写call方法:

Callable接口实际是属于Executor框架中的功能类,Callable和Runnable接口的功能类似,但提供了比Runable更强大的功能:

Callable可以在任务接受后提供一个返回值,而Runnable不可以。

Callable接口中的call方法可以抛出异常,但是Runnable 中的run方法不可以。

运行Callable可以拿到一个Future对象,Future对象表示异步计算的结果,它提供了检查计算是否完成的办法。但是当调用Future的get 方法获取结时,当前线程会阻塞,直到call方法返回结果。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class TestCallable implements Callable {

@Override
public Object call() throws Exception {
return "hello";
}
}


public void run() {
TestCallable callable = new TestCallable();
ExecutorService executorService = Executors.newSingleThreadExecutor();
Future future = executorService.submit(callable);
try {
Log.e("thread", "create");
} catch (Exception e) {
e.printStackTrace();
}
}

安全地终止线程:

1.用中断来终止线程:

1
2
3
4
5
6
7
public void stopThread() throws InterruptedException {
TestRunnable runnable = new TestRunnable();
Thread thread = new Thread(runnable);
thread.start();
TimeUnit.MILLISECONDS.sleep(10);
thread.interrupt();
}

2.通过boolean来控制是否需要停止线程:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class MoonRunner implements Runnable {
private long i;
private volatile boolean on = true;

@Override
public void run() {
while (on) {
i++;
Log.e("test", "i=" + i);
}
}

public void cancel(){
on = false;
}
}

public void stop() throws InterruptedException {
MoonRunner runner = new MoonRunner();
Thread mThread = new Thread(runner,"moonThread");
mThread.start();
TimeUnit.MILLISECONDS.sleep(10);
runner.cancel();
}

同步:

Lock

Condition

Volatile

阻塞队列:就是生产者存放元素的容器,而消费者也只从容器里拿元素。使用阻塞队列无须单独考虑同步和线程间通信的问题。

常见的两种阻塞场景:

(1)当队列中没有数据的情况下,消费者端的所有线程会被自动阻塞,直到有数据放进队列

(2)当队列中填满数据的情况下,生产者端的所有线程会被自动阻塞,直到队列中有空的位置,线程被自动唤醒

核心的几个方法:

放入数据:

offer(anObject):表示如果可能的话,将anObject放到BlockingQueue里。可以容纳返回true,否则返回false,且这个方法不阻塞当前执行方法的线程

offer(E o,long timeout,TimeUnit unit):可以设定等待的时间。如果在指定的时间内还不能往队列中加入BlockingQueue,则返回false.

put(anObjec t):将anObject加到BlockingQueue里。如果BlockingQueue没有空间,则调用此方法的线程被阻断,直到BlockingQueue里面有了空间再继续。

获取数据:

poll(time):取走BlockingQueue里排在首位的对象,若不能立即取出,则可以等time的参数规定的时间。取不到时返回null

poll(long time,TimeUnit unit):从BlockingQueue中取出一个队首的对象。如果在指定的时间内,队列一旦有数据可取,则立即返回队列中的数据;否则知道时间超时还没有数据可取,返回失败。

take():取走BlockingQueue里排在首位的对象。若BlockingQueue为空,则阻断进入等待状态,直到BlockingQueue有新的数据被加入。

drainTo():一次从BlockingQueue获取所有的可用的数据对象(还可以指定获取的个数)。通过该方法,可以提升获取数据的效率;无须多次分批加锁和释放锁。

线程池:

构造方法如下所示:

1
2
3
4
5
6
7
8
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue) {
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
Executors.defaultThreadFactory(), defaultHandler);
}

corePoolSize:核心线程数。默认情况下线程池是空的,只有任务提交时才会创建线程。如果当前运行的线程数小于corePoolSize,则创建新线程来处理任务;如果等于或者多余corePoolSize,则不再创建。如果调用线程池的prestartAllcoreThread方法,线程池也会 提前创建并启动所有核心线程来等待任务。

maximumPoolSize:线程池允许创建的最大线程数。如果任务队列满了并且线程数小于maximunPoolSize时,则线程池仍旧会创建新的线程来处理任务。

keepAliveTime:非核心线程闲置的超时时间。超过这个时间则回收。如果任务很多,并且每个任务的执行时间很短,则可以调大keepAliveTime来提高线程的利用率。另外,如果设置allowCoreThreadTimeOut属性为true时,keepAliveTime也会应用到核心线程上。

TimeUnit:keepAliveTime参数的时间单位。

workQueue:任务队列。如果当前线程数大于corePoolSize,则将任务添加到次任务队列中。该任务队列是BlockingQueue类型的,也就是阻塞队列。

ThreadFactory:线程工厂。可以用线程工厂给每个创建出来的线程设置名字。

RejectedExecutionHandler:饱和策略。这是当任务队列和线程池都满了时所采取的应对策略,默认是AbordPolicy,表示无法处理新任务,并抛出RejectedExecutionException异常。此外还有三种:

(1)CallerRunsPolicy:用调用者所在的线程来处理任务。此策略提供简单的反馈控制机制,能够减缓新任务提交的速度

(2)DiscardPolicy:不能执行任务,并将该任务删除

(3)DiscardOldestPolicy:丢弃队列最近的任务,并执行当前任务 

以下是线程池的处理流程

线程池的种类:

(1)FixedThreadPool:只有核心线程,并且数量固定,没有非核心线程。keepAliveTime为0L表示多余的线程会被立即终止。因为不会产生多余的线程,所以keepAliveTime是无效参数。此外,任务队列采用的无界的阻塞队列LinkedBlockingQueue。

(2)CachedThreadPool:根据需要创建线程的线程池,corePoolSize为0,maximumPoolSize设置为Integer.MAX_VALUE ,这意味着CachedThreadPool没有0核心线程,非核心线程是无界的。keepAliveTime设置为60L,则空闲线程等待新任务的最长时间为60s。用了阻塞队列SynchronousQueue,它是一个不存储元素的阻塞队列,每个插入操作必须等待另一个线程的移除操作,同样任何一个移除操作都等待另一个线程的插入操作。 

(3)SingleThreadExecutor:使用单个工作线程的线程池。corePoolSize和maximumPoolSize都为1,这意味着只有一个核心线程,其他的参数都和FixedThreadPool一样。

(4)ScheduledThreadPool:是一个能实现定时和周期性任务的线程池。构造时传入corePoolSize,maximumPoolSize的值是Integer.MAX_VALUE。采用了DelayedWorkQueue,是无界的,所以maximumPoolSize这个参数是无效的。

本文地址: http://www.yppcat.top/2019/04/10/多线程/