12.1 多线程的基本概念
12.1.1线程与进程
进程:执行中的程序
线程:线程是轻量级的进程,不能单独运行,只能放在一个进程中才能执行
12.1.2 java的线程模型
1.使线程暂停
①调用sleep( )方法
②调用wait( )方法
③等待输入/输出完成
2.使线程进入就绪状态
①线程调用notify( )方法
②线程调用notifyAll( )方法 //通知全部线程就绪
③线程调用interrupt( )方法 //中断线程
④线程的休眠时间结束
⑤输入输出结束
12.2 线程创建的两种方法
12.2.1 继承Thread类创建线程
// 创建:
public class 类名 extends Thread{
public void run( ){
//相关代码
}
}
// 调用:
类名 命名=new 类名( );
命名.start( );
12.2.2 实现Runnable接口创建线程
// 创建:
public class 类名 implements Runnable{
public void run( ){
//相关代码
}
}
// 调用:
类名 命名1=new 类名( );
Thread 命名2=new Thread( 命名1 );
命名2.start( );
12.3 多线程的应用
12.3.1多个线程并发执行
Java对启动后唯一能保证的是每个线程都被启动并结束,什么时候开始执行和执行的先后顺序不保证
12.3.2 线程的优先级
获取优先级:命名.getPriority( );
设置优先级:命名.setPriority(Thread.选择要设置的具体选项);
①继承性:线程A启动线程B,则B和A优先级一样
②规则性:CPU尽量倾向于把资源分配给优先级高的线程
③随机性:优先级不等同于执行顺序,二者关系不确定
12.4 线程调度的四个方法
12.4.1 休眠方法sleep( );
使用:Thread.sleep( );
1.sleep(0<=毫秒<=999999)
2.sleep(0<=毫秒<=999999,0<=纳秒<=999999)
3.sleep会让该线程睡眠,不影响其他线程的运行
4.休眠完后,线程再次进入就绪状态等待分配CPU时间片
sleep()和wait()区别:
12.4.2 暂停方法yield( );
使用:Thread.yield( );
- Thread.Yield使线程资源释放,然后让其他线程和自身线程一起竞争资源
- 也就说Thread.Yield用跟不用其实一样
12.4.3 挂起方法join( );
使用:线程名.join( );
1.在一个线程里加入另外一个线程,就用 线程名.join( );
2.加入线程的位置不确定,必定是在设置好的插入位置或者之前
3.因为有可能在插入位置之前,该线程已经获得了CPU而执行了
4.一旦join后,就会执行完该join的线程,再执行其他线程
12.4.4 等待方法wait( ); 会释放锁
使用:线程名.wait( );
- 使线程进入等待状态
- notify( )方法唤醒对应等待状态的线程
- notifyAll( )方法唤醒所有等待状态的线程
12.5 线程同步问题的由来
三个老师抢占一台打印机:
Print类:1.Print类被用于打印学生成绩
2.有打印分数的方法printScore( )
3.printScore方法需要传入一下参数
@param name 学生姓名
@param cScore 语文成绩
@param mScore 数学成绩
@param eScore 英语成绩
Teacher类:
* 1.Teacher类继承了线程Thread类
* 2.实例化的时候传入数据信息到Teacher类
* 3.在线程run方法里调用Print类的printScore方法
12.6 同步问题java的解决方案(加锁)
12.6.1同步方法 (锁住整个对象的加锁方法)
当线程进入同步方法的时候,会获得同步方法所属对象的锁,一旦获得对象锁,则其他 线程不能再执行被锁对象的任何同步方法,只有在同步方法执行完毕后释放了锁,其他 线程才能执行
synchronized 声明方法{
}
12.6.2 同步块 (锁住整个对象的所有方法,包括不加锁方法)
当线程调用被锁对象时,会获得该对象的锁,一旦获得对象锁,则其他线程不能执行该 对象的任何方法(不加Synchronized 的方法也被锁住),只有在同步方法执行完后释放 了锁,其他线程才能执行。资源必须是多个线程都共有的,否则不能实现同步功能
synchronized (公共资源对象){
//需要执行的同步方法
}
12.7 死锁问题
12.7.1 死锁思想
线程A占有资源2,需要申请资源1
线程B占有资源1,需要申请资源2
12.7.2 死锁案例
一、先定义一个资源类
public class Source {
/**
* 资源类:
* sourceName 资源名字
* 传递资源名字的构造方法
*/
String sourceName;
public Source(String sourceName){
this.sourceName=sourceName;
}
}
二、定义线程类,用于不同线程的占用与申请资源定义
public class MyThread extends Thread{
/**
* 线程类:
* 1.构造方法传递参数
* 2.在run方法里实现synchronized的嵌套
* 3.在占有某个资源的同时,该线程也在申请另一个被占用的资源
*/
Source r1,r2;
String name;
public MyThread(Source r1,Source r2,String name){
this.name=name;
this.r1=r1;
this.r2=r2;
}
public void run(){
//设置synchronized的嵌套,先申请第一个资源
//锁住资源对象r1,在里面完成程序执行
//synchronized(公共资源对象) ps:资源对象必须是公用的,各线程存在竞争
synchronized(r1){
System.out.println(name+"获得资源"+r1.sourceName);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(name+"\t"+r1.sourceName+"执行完毕,在等待 "+r2.sourceName);
//设置要申请的第二个资源
//在r1的执行程序中,锁住资源对象r2,在里面完成程序执行
synchronized(r2){
System.out.println(name+"获得资源"+r2.sourceName);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(name+"\t"+r2.sourceName+"执行完毕");
}
}
}
三、测试方法类,用于测试死锁
public class Test {
/**
* 测试类:
* 三个资源,三个线程,出现死锁现象
* 线程一:占有资源一,申请资源二
* 线程二:占有资源二,申请资源三
* 线程三:占有资源三,申请资源一
*/
public static void main(String[] args) {
Source r1=new Source("资源一");
Source r2=new Source("资源二");
Source r3=new Source("资源三");
MyThread mt1=new MyThread(r1,r2,"线程一");
MyThread mt2=new MyThread(r2,r3,"线程二");
MyThread mt3=new MyThread(r3,r1,"线程三");
mt1.start();
mt2.start();
mt3.start();
}
}
12.8 生产消费模式
/**
* 生产者:
* 1、List作为仓库
* 2、定义最大容量
* 3、构造函数 传入线程名字、最大容量、List仓库
* 4、锁定仓库对象list,在里面不断循环生产,当生产满了就wait()挂停
* 5、仓库没满继续生产,每生产一件商品就notifyAll()通知所有线程可以消费了
*/
public class Producer extends Thread{
//仓库
List<Integer> list;
//仓库存放的最大值
int max;
/**
* 生产者构造函数
* @param name 线程名字(直接传到父类name)
* @param max 仓库最大容量值
* @param list 共用的List仓库
*/
public Producer( String name, int max, List<Integer> list ){
//线程名字,传到Thread类
super(name);
this.max = max;
this.list = list;
}
/**
* 线程方法run()
*/
public void run(){
//锁住list进行处理
while( true ){
//锁住仓库list
synchronized(list){
//判断仓库是否已满
while( list.size()==max ){
System.out.println("仓库已满");
try {
//如果仓库满了,就将线程挂起。同时释放了锁
list.wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
//仓库没满继续生产
int pudect = (int)(Math.random()*100);
list.add(pudect);
System.out.println(getName()+" 生产了 "+pudect);
// 每生产一个就通知全部list可以被消费了。同时释放了锁
/*调用 notifyAll() 方法将把因调用该对象的 wait() 方法而阻塞的所有线程一次性全部解除阻塞。当然,只有获得锁的那一个线程才能进入可执行状态。*/
list.notifyAll();
}
//synchronized(list)的外面可能会被抢占资源而挂起,而去执行别的线程
}
}
}
/**
* 消费者:
* 1、2、3同生产者
* 4、锁定仓库对象list,在里面不断循环消费,当消费完了就wait()挂停
* 5、仓库消费完继续消费,每消费一件商品就notifyAll()通知所有线程可以生产了
*/
public class Consumer extends Thread{
//.........前面代码与生产者相同
public void run(){
//while()在里面循环消费
while( true ){
//锁住仓库list
synchronized(list){
//判断仓库为空就调用wait(),挂起线程
while( list.size()== 0 ){
System.out.println("仓库已空");
try {
list.wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
//当仓库还有产品就继续消费
System.out.println(getName()+"消费了"+list.get(list.size()-1));
list.remove(list.size()-1);//从仓库的最后一个开始消费
//每消费一个产品就通知list可以继续被生产
list.notifyAll();
}
//synchronized(list)的外面可能会被抢占资源而挂起,而去执行别的线程
}
}
}
/**
* 测试类:
* 1、创建共同的List作为仓库
* 2、创建生产者、消费者线程
* 3、启动线程
*/
public class Test {
public static void main(String[] args) {
List<Integer> list = new ArrayList<>();//创建一个List仓库
//创建线程
Producer producer = new Producer( "生产者", 100, list );
Consumer consumer = new Consumer( "消费者", 100, list );
//启动线程
producer.start();
consumer.start();
}
}
12.9 线程池
12.9.1 什么是线程池?
Android的线程池来源于Java的Executor,Executor是一个接口,真正的线程池实现为ThreadPoolExecutor,ThreadPoolExecutor提供了一系列的参数来配置,通过不同的参数来创建不同线程池。
12.9.2 构造方法
public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler)
下面详细讲解各个参数的含义:
-
corePoolSize: 核心线程数,默认情况,线程一直存活;如果设置allowCoreThreadTimeOut = true那么核心线程就会存在超时策略,这时间间隔由keepAliveTime决定,等待时间超过keepAliveTime指定的时间后,核心线程就会被停止。
-
maximumPoolSize: 线程池所能容纳的最大线程数,超过这数,后面的任务线程就会被阻塞;
-
keepAliveTime: 非核心线程超时时的时长,当系统中非核心线程闲置时间超过这个时长后,则会被系统回收,如果设置allowCoreThreadTimeOut = true,那么这个时长同样对核心线程奏效;
-
unit: keepAliveTime这个参数的时间单位,有纳秒、微秒、毫秒、秒、分、时、天等,例如TimeUnit.SECONDS表示单位为秒;
-
workQueue: 线程池中的任务队列,主要用来存储已经提交但是未执行的任务,通过ThreadPoolExecutor的execute方法来提交任务;这个队列就是BlockQueue类型,属于阻塞队列,如果队列为空时,取出消息的操作就会被阻塞,直到任务加入队列中不为空的时候,才能进行取出操作,而在满队列的时候,添加操作同样被阻塞;
-
threadFactory: 线程工厂,为线程池提供创建新线程的功能,它是一个接口,只有一个方法Thread newThread(Runnable r),一般情况使用默认即可;
-
handler: 拒绝策略,当线程无法执行新任务时(一般由于线程池中线程数量达到最大值任务队列已经饱和或者线程池被关闭导致),默认情况,当线程池无法处理新线程时,抛出RejectedExecutionException异常。
线程池执行流程原则:
1.如果线程池中的线程数未达到核心线程数,就会开启一个线程去执行任务;
2.如果线程池中的线程数已经达到核心线程数,而任务队列workQueue未满,则会将任务添加到workQueue任务队列中;
3.如果线程池中的线程数已经达到核心线程数但未超过最大线程数,而且任务队列workQueue已经满,则会开启非核心线程来执行任务;
4.如果线程池中的线程数已经达到最大线程数,那么拒绝该任务,执行饱和策略,抛出RejectedExecutionException异常。
12.9.3 相关方法
ThreadPoolExecutor有两个方法供我们执行,execute()和submit()都可以执行任务。通常我们不需要返回值时使用execute()执行任务,需要返回值时使用submit(),submit()方法返回一个Future,Future可以获得结果,并且可以cancel()取消请求。
execute(): 执行任务,无返回值;
submit(): 执行任务,返回Future,可以获得结果,并且cancel()取消请求。内部也执行execute()的逻辑;
shutdown(): 关闭线程池,不影响已经提交的任务;
shutdownNow(): 关闭线程池,尝试终止正在执行的任务。
12.9.4 线程池分类
12.9.5 线程池的封装
/**
* 线程池管理工具类
*/
public class ThreadPoolManager {
//核心线程池的数量,同时能够执行的线程数量
private int corePoolSize;
//最大线程池数量,表示当缓冲队列满的时候能继续容纳的等待任务的数量
private int maximumPoolSize = 100;
//空闲线程存活时间30分钟
private long keepAliveTime = 30 * 60;
//单位:秒
private TimeUnit unit = TimeUnit.SECONDS;
//线程池实例
private ThreadPoolExecutor executor;
private static ThreadPoolManager mInstance;
//线程池管理类
public static ThreadPoolManager getInstance() {
//单例
if (mInstance == null) {
//加锁
synchronized (ThreadPoolManager.class) {
if (mInstance == null) {
mInstance = new ThreadPoolManager();
}
}
}
return mInstance;
}
/** * 私有化构造方法 */
private ThreadPoolManager() {
/** * 给corePoolSize赋值:当前设备可用处理器核心数*2 + 1,能够让cpu的效率得到最大程度执行(有研究论证的) */
corePoolSize = Runtime.getRuntime().availableProcessors() * 2 + 1;
executor = new ThreadPoolExecutor(
//当某个核心任务执行完毕,会依次从缓冲队列中取出等待任务
corePoolSize,
//线程池线程最大数量
maximumPoolSize,
// 表示的是maximumPoolSize当中等待任务的存活时间
keepAliveTime,
// keepAliveTime的时间单位
unit,
//缓冲队列,用于存放等待任务,Linked的先进先出 Executors.defaultThreadFactory(),
new LinkedBlockingQueue(),
//创建线程的工厂,用来对超出maximumPoolSize的任务的处理策略
new ThreadPoolExecutor.AbortPolicy() );
}
/** * 执行任务 */
public void execute(Runnable runnable) {
if (runnable == null) return;
executor.execute(runnable);
}
/** * 从线程池中移除任务 */
public void remove(Runnable runnable) {
if (runnable == null) return;
executor.remove(runnable);
}
}
使用:
//任务请求
Runnable runnable = new Runnable() {
@Override
public void run() {
try {
Log.e(TAG, "线程池管理类:" + Thread.currentThread().getName() + " 正在执行任务"); Thread.currentThread().sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
//执行请求
ThreadPoolManager.getInstance().execute(runnable);
//移除请求
ThreadPoolManager.getInstance().remove(runnable);
12.9.6深入理解线程池
1拒绝策略defaultHandler
由上面构造方法可知,可以传入自定义的拒绝策略defaultHandler,否则就是使用默认的;
表示无法执行任务的通知类, 当线程池无法执行任务,可能因为无法成功执行任务或者任务队列已满,这个时候就会调用ThreadPoolExecution的rejectedExecution(拒绝执行异常)方法通知调用者;
(1)AbortPolicy:默认值,拒绝任务时直接抛出异常和原因;
(2)CallerRunsPolicy :拒绝任务时,判断线程池的状态是否为SHUTDOWN,如果为true任务则会被丢弃,如果为false任务会继续执行;
(3)DiscardPolicy:拒绝任务,什么也不会发生
(4)DiscardOldestPolicy:拒绝任务时,判断线程池的状态是否为SHUTDOWN,如果为true则任务丢弃,如果为false,将当前任务队列中等待时间最长的任务弹出,将其加入任务队列中并重试。
2.执行任务execute()
线程池中有两个执行的方法execute()和submit(),其实两个方法的本质含义是一样的,只是有无返回值的区别
-
execute():
-
addWorker():
addWorker()才是创建线程(核心、非核心)的主要方法,而reject()则是一个创建线程失败的回调;
addWorkerFailed()方法:回滚线程的创建操作,如果线程的包装类Worker存在就移除掉,刷新工作线程数量,尝试终止线程操作; tryTerminate():尝试停止线程池操作,1.如果在SHUTDOWN状态并且队列为空时;2.stop状态,两种情况会将线程池进行停止操作, interruptIdleWorkers():设置线程中断的操作,onlyOne如果为true:只中断工作线程中的其中一个线程;如果为false:中断所有的工作线程。t.interrupt():表示中断线程。
-
shutdown():中断所有空闲线程的方法,它的核心方法还是调用了上面的interruptIdleWorkers()方法。
public void shutdown() {
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
//校验线程的状态
checkShutdownAccess();
//设置线程池的状态为SHUTDOWN
advanceRunState(SHUTDOWN);
//中断所有空闲进程,内部调用interruptIdleWorkers(false)
interruptIdleWorkers();
//中断所有线程可指定的操作,需要自己实现 onShutdown();
// hook for ScheduledThreadPoolExecutor
} finally {
mainLock.unlock();
}
tryTerminate();
}
}
private void interruptIdleWorkers() {
interruptIdleWorkers(false);
}
注意:1.在shutdown()被执行时,可以让现有的任务被执行关闭,但是新的任务不会再被处理;
2.如果任务线程时SHUTDOWN状态,继续调用shutdown()不会产生任何效果。
-
submit()
submit()其实还需要调用execute()去执行任务,而execute()和submit()本质不同的是submit()将包装好的任务进行返回。
public Future submit(Runnable task) {
if (task == null) throw new NullPointerException();
//最终还是调用execute(ftask)
RunnableFuture ftask = newTaskFor(task, null);
execute(ftask);
return ftask;
}
//创建RunnableFuture
protected RunnableFuture newTaskFor(Runnable runnable, T value) {
//FutureTask其实就是包装了callable 和其他一些信息的类
return new FutureTask(runnable, value);
}
public FutureTask(Runnable runnable, V result) {
this.callable = Executors.callable(runnable, result);
// ensure visibility of callable
this.state = NEW;
}
Future则是返回的结果,里面封装了请求等信息,我们可以通过cancel(boolean),true表示中断退出正在执行的任务,false则是任务可以被完成。