Android基础知识-线程相关

一、线程相关
1、线程的构造

private void init(ThreadGroup g, Runnable target, String name,long stackSize) {
	if (name == null) {
		throw new NullPointerException("name cannot be null");
	}
	// 当前线程就是该线程的父线程
	Thread parent = currentThread();
	this.group = g;
	// 将daemon、priority属性设置为父线程的对应属性
	this.daemon = parent.isDaemon();
	this.priority = parent.getPriority();
	this.name = name.toCharArray();
	this.target = target;
	setPriority(priority);
	// 将父线程的 InheritableThreadLocal 复制过来
	if (parent.inheritableThreadLocals != null) {
		this.inheritableThreadLocals=ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
	}
	//分配一个线程id
	tid = nextThreadID();
}

一个新构造的线程对象是由其parent线程来进行空间分配的,
而child线程继承了parent是否为Daemon、优先级和加载资源的contextClassLoader以及可继承的ThreadLocal,
同时还会分配一个唯一的ID来标识这个child线程。在堆内存中等待着运行。

2、线程的启动和终止

1)启动线程
用start()方法就可以启动这个线程。
线程start()方法的含义是:当前线程(即parent线程)同步告知Java虚拟机,只要线程规划器空闲,应立即启动调用start()方法的线程

2)线程中断
	中断可以理解为线程的一个【标识位属性】,它表示一个运行中的线程是否被其他线程进行了中断操作。
中断好比其他线程对该线程打了个招呼,其他线程通过调用该线程的interrupt()方法对其进行中断操作。
	如果该线程已经处于终结状态,即使该线程被中断过,在调用该线程对象的isInterrupted()时依旧会返回false。
	方法在抛出InterruptedException之前,Java虚拟机会先将该线程的中断标识位清除,
然后抛出InterruptedException,此时调用isInterrupted()方法将会返回false

3)过期的suspend()、resume()和stop()
suspend():在调用后,线程不会释放已经占有的资源(比如锁),而是占有着资源进入睡眠状态,这样容易引发死锁问题
stop():在终结一个线程时不会保证线程的资源正常释放,通常是没有给予线程完成资源释放工作的机会,因此会导致程序可能工作在不确定状态下

4)安全地终止线程
	中断状态是线程的一个标识位,而中断操作是一种简便的线程间交互方式,而这种交互方式最适合用来取消或停止任务。
	除了中断以外,还可以利用一个boolean变量来控制是否需要停止任务并终止该线程
	
	通过标识位或者中断操作的方式能够使线程在终止时有机会去清理资源,
而不是武断地将线程停止,因此这种终止线程的做法显得更加安全和优雅。

3、线程间通信
1)volatile和synchronized关键字(与线程通信无关)

volatile修饰的字段在线程间是可见的,一个线程的副本中修改了该字段,另一个引用该字段的线程会感知并使字段失效重新从主内存中加载。

synchronized修饰的方法/同步块,是对一个对象的监视器(monitor)进行获取,而这个获取过程是排他的,
也就是同一时刻只能有一个线程获取到由synchronized所保护对象的监视器。

2)管道输入/输出流

管道输入/输出流主要用于线程之间的数据传输,而传输的媒介为内存。
管道输入/输出流主要包括了如下4种具体实现:PipedOutputStream、PipedInputStream、PipedReader和PipedWriter,前两种面向字节,而后两种面向字符。
public class Piped {
	public static void main(String[] args) throws Exception {
		PipedWriter out = new PipedWriter();
		PipedReader in = new PipedReader();
		// 将输出流和输入流进行连接,否则在使用时会抛出IOException
		out.connect(in);
		Thread printThread = new Thread(new Print(in), "PrintThread");
		printThread.start();
		int receive = 0;
		try {
			while ((receive = System.in.read()) != -1) {
				out.write(receive);//一个线程把内容写到输出流中
			}
		} finally {
			out.close();
		}
	}
	static class Print implements Runnable {
		private PipedReader in;
		public Print(PipedReader in) {
			this.in = in;
		}
		public void run() {
			int receive = 0;
			try {
				while ((receive = in.read()) != -1) {//另一额线程从输入流中读取
					System.out.print((char) receive);
				}
			} catch (IOException ex) {
			}
		}
	}
}
Piped类型的流,必须先要进行绑定,也就是调用connect()方法,将输入/输出流绑定起来

3)Thread.join()的使用

如果一个线程A执行了thread.join()语句,表示:当前线程A等待thread线程终止之后才从thread.join()返回

join(long millis)和join(long millis,int nanos)两个具备超时特性的方法,如果线程thread在给定的超时时间里没有终止,那么将会从该超时方法中返回。

public class Join {
	public static void main(String[] args) throws Exception {
		Thread previous = Thread.currentThread();
		for (int i = 0; i < 10; i++) {
			// 每个线程拥有前一个线程的引用,需要等待前一个线程终止,才能从等待中返回
			Thread thread = new Thread(new Domino(previous), String.valueOf(i));
			thread.start();
			previous = thread;
		}
		TimeUnit.SECONDS.sleep(5);
		System.out.println(Thread.currentThread().getName() + " terminate.");
	}
	static class Domino implements Runnable {
		private Thread thread;
		public Domino(Thread thread) {
			this.thread = thread;
		}
		public void run() {
			try {
				thread.join();//等待thread线程终止后,本线程才会继续往下走
			} catch (InterruptedException e) {
			}
			System.out.println(Thread.currentThread().getName() + " terminate.");
		}
	}
}

原理:
public class Thread {
	public final void join(long millis) throws InterruptedException {
		synchronized(lock) {
			long base = System.currentTimeMillis();
			long now = 0;

			if (millis < 0) {
				throw new IllegalArgumentException("timeout value is negative");
			}

			if (millis == 0) {
				/* thread.join() 只要thread线程还没终止,那么当前线程(调用thread.join()的线程)就一直等待
				 * 当thread线程终止时,会调用thread线程的lock.notifyAll()方法,会通知所有等待在该线程对象上的线程
				 */
				while (isAlive()) {
					lock.wait(0);
				}
			} else {
				while (isAlive()) {
					long delay = millis - now;
					if (delay <= 0) {
						break;
					}
					lock.wait(delay);
					now = System.currentTimeMillis() - base;
				}
			}
		}
	}
}

4、等待/通知机制

	等待/通知机制,是指一个线程A调用了对象O的wait()方法进入等待状态,而另一个线程B调用了对象O的notify()或者notifyAll()方法,
线程A收到通知后从对象O的wait()方法返回,进而执行后续操作。上述两个线程通过对象O来完成交互,
而对象上的wait()和notify/notifyAll()的关系就如同开关信号一样,用来完成等待方和通知方之间的交互工作。

等待/通知机制【依托于同步机制】,其目的就是确保等待线程从wait()方法返回时能够感知到通知线程对变量做出的修改。

调用wait()、notify()以及notifyAll()时需要注意的细节:
1)使用对象的wait()、notify()和notifyAll()时,需要【先对调用对象加锁】。
2)调用wait()方法后,线程状态由RUNNING变为WAITING,并将当前线程放置到对象的等待队列。
3)notify()或notifyAll()方法调用后,等待线程依旧不会从wait()返回,需要调用notify()或notifAll()的线程释放锁之后,等待线程才有机会从wait()返回。
4)notify()方法将等待队列中的一个等待线程从等待队列中移到同步队列中,而notifyAll()方法则是将等待队列中所有的线程全部移到同步队列,
	被移动的线程状态由WAITING变为BLOCKED。
5)从wait()方法返回的前提是获得了调用对象的锁。

二、线程池相关
https://blog.csdn.net/u010577768/article/details/79883824

1、使用线程池的原因

1)降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消耗。
2)提高响应速度。当任务到达时,任务可以不需要等到线程创建就能立即执行。
3)提高线程的可管理性,使用线程池可以进行统一分配、调优和监控。

2、线程池内部原理

线程池执行任务,未超过核心线程数时,任务添加到HashSet<Worker> workers线程池中,用核心线程直接执行任务;
超过核心线程数时,任务添加到 BlockingQueue<Runnable> workQueue 任务队列中,通过 addWorker(null,false),创建线程从workQueue中取任务来执行。
new Worker()默认会创建一个线程

提交一个新任务到线程池时,线程池的处理流程:
	1)如果当前运行的线程少于 corePoolSize,则创建新线程来执行任务(注意,执行这一步骤需要获取全局锁)。
	2)如果运行的线程等于或多于corePoolSize,则将任务加入 BlockingQueue。
	3)如果无法将任务加入 BlockingQueue(队列已满),则创建新的线程来处理任务(注意,执行这一步骤需要获取全局锁)。
	4)如果创建新线程将使当前运行的线程超出 maximumPoolSize,任务将被拒绝,并调用RejectedExecutionHandler.rejectedExecution()方法	

线程池创建线程时,会【将线程封装成工作线程Worker】,Worker在执行完任务后,会循环从BlockingQueue获取任务来执行。

3、线程池如何维持核心线程数

1)一个任务的执行在runWorker中,执行完一个任务后会从workQueue去取任务继续执行,最终runWorker的finally中调用processWorkerExit

2)线程执行任务完成后触发 processWorkerExit,该方法内部在 RUNNING/SHUTDONW 状态时,如果线程池workCount小于最小闲置线程数,
  会addWorker(null,false),也就是执行完之后线程池还活着,就再丢一个新线程进去,然后旧的线程就彻底结束了
  
3)这个新丢进来的线程进来时,发现workCount <= corePoolSize && 无超时限制 && RUNNING状态
  那么会到任务队列中取,【通过workQueue.take()保活】,实现维持线程池核心线程数。

4)线程池保活的关键在 runWorker(),该方法内部一个while循环,执行完当前任务后,会继续去workQueue里去取出来执行,直到取不到任务时,
  判断是否需要保活,需要保活的调用的是 workQueue.take()。
  【workQueue.take()取元素时有通过ReentrantLock.lock()/unlock()】;通过锁阻塞线程保活。
  
5)每增加一个任务,如果线程数没达到最大线程数限制,会默认添加一个线程

4、线程池中的几种重要的参数及流程说明

1)corePoolSize    //核心线程个数
	提交一个任务到线程池时,如果线程池中线程数少于corePoolSize,线程池会创建一个线程来执行任务。直到线程数达到corePoolSize
   
2)maximumPoolSize //线程池最大线程个数 最大线程个数要大于核心线程个数
	如果队列满了,并且已创建的线程数小于最大线程数,则线程池会再创建新的线程执行任务。
	如果使用了无界的任务队列这个参数就没什么效果
	
  3)workQueue //任务队列,用于保存等待执行的任务的阻塞队列
用于保存等待执行的任务的阻塞队列。
a、ArrayBlockingQueue:是一个基于数组结构的有界阻塞队列,此队列按FIFO(先进先出)原则对元素进行排序。
b、LinkedBlockingQueue:一个基于链表结构的阻塞队列,此队列按FIFO排序元素,吞吐量通常要高于ArrayBlockingQueue。
   静态工厂方法Executors.newFixedThreadPool()使用了这个队列。

  4)keepAliveTime //线程池的工作线程空闲后,保持存活的时间
核心线程之外的线程,如果空闲达到了这个时间,线程自动关闭(可以通过 allowsCoreThreadTimeOut()作用于核心线程)

  5)ThreadFactory //线程工厂
线程工厂用来创建线程的,用于设置线程的优先级,名字

  6)RejectedExecutionHandler // 饱和策略
当队列和线程池都满了,说明线程池处于饱和状态,那么必须采取一种策略处理提交的新任务。

对reject的任务的处理(当workQueue满了并且达到了最大线程的个数时,或者调用了shutdown函数之后,
再加入任务也是会reject的)

这个策略默认情况下是AbortPolicy,表示无法处理新任务时抛出异常。

5、线程池中几种常见的工作队列

1)ArrayBlockingQueue:是一个基于数组结构的有界阻塞队列,此队列按FIFO(先进先出)原则对元素进行排序。
2)LinkedBlockingQueue:一个基于链表结构的阻塞队列,此队列按FIFO排序元素,吞吐量通常要高于ArrayBlockingQueue。
						静态工厂方法Executors.newFixedThreadPool()使用了这个队列。
3)SynchronousQueue:一个不存储元素的阻塞队列。
			每个插入操作必须等到另一个线程调用移除操作,否则插入操作一直处于阻塞状态,
			吞吐量通常要高于Linked-BlockingQueue,静态工厂方法Executors.newCachedThreadPool 使用了这个队列。						
4)PriorityBlockingQueue:一个具有优先级的无限阻塞队列

6、关闭线程池
调用线程池的 shutdownshutdownNow 方法来关闭线程池

原理是遍历线程池中的工作线程,然后逐个调用线程的interrupt方法来中断线程,
所以【无法响应中断的任务可能永远无法终止】。

区别:shutdownNow 正在执行的/暂停任务的线程,也会尝试停止;
      shutdown 只尝试中断没用正在执行任务的线程
	  
	shutdownNow 首先将线程池的状态设置成STOP,然后尝试停止所有的正在执行或暂停任务的线程,并返回等待执行任务的列表
	shutdown 只是将线程池的状态设置成SHUTDOWN状态,然后中断所有没有正在执行任务的线程

7、几种常见的线程池及使用场景

1)Executors.newFixedThreadPool(FIX_THREAD_COUNT):
	public static ExecutorService newFixedThreadPool(int nThreads) {
		/* 核心线程是不会回收的
		 * 核心线程数等于最大线程数:线程数固定、且不会被回收,核心线程全部繁忙时,新任务放入任务队列
		 * 任务队列 LinkedBlockingQueue:基于链表的阻塞队列
		 */
		return new ThreadPoolExecutor(nThreads, nThreads,
									  0L, TimeUnit.MILLISECONDS,
									  new LinkedBlockingQueue<Runnable>());
	}
	
	建可容纳固定数量线程的池子,每个线程的存活时间是无限的,当池子满了就不在添加线程了;
	如果池中的所有线程均在繁忙状态,对于新任务会进入阻塞队列中

	场景:执行长期的任务,性能好很多

2)Executors.newSingleThreadExecutor():
	public static ExecutorService newSingleThreadExecutor() {
		/* 核心线程是不会回收的
		 * 核心线程数 = 最大线程数 = 1:固定1个线程、且不会被回收,核心线程全部繁忙时,新任务放入任务队列
		 * 任务队列 LinkedBlockingQueue:基于链表的阻塞队列
		 */
		return new FinalizableDelegatedExecutorService(new ThreadPoolExecutor(1, 1,
									0L, TimeUnit.MILLISECONDS,
									new LinkedBlockingQueue<Runnable>()));
	}
	一个单线程的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。
	
	场景:一个任务一个任务执行的场景

3)Executors.newCachedThreadPool():
	public static ExecutorService newCachedThreadPool() {
		/* 核心线程数0:核心线程是不会回收的,这里设置为0,所以池中所有线程都是可回收的
		 * 空闲保活时间60s:线程空闲时间达到60s时,会被回收
		 * 任务队列SynchronousQueue:没有容量,是无缓冲等待队列,是一个不存储元素的阻塞队列,
		 *                            会直接将任务交给消费者,必须等队列中的添加元素被消费后才能继续添加新的元素。
		 */
		return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
									  60L, TimeUnit.SECONDS,
									  new SynchronousQueue<Runnable>());
	}

	创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。
	线程池为无限大,当执行第二个任务时第一个任务已经完成,会复用执行第一个任务的线程,而不用每次新建线程。

	场景:执行很多短期异步的小程序或者负载较轻的服务器
	
4)Executors.newScheduledThreadPool()

	public ScheduledThreadPoolExecutor(int corePoolSize, ThreadFactory threadFactory) {
		super(corePoolSize, Integer.MAX_VALUE,
			  DEFAULT_KEEPALIVE_MILLIS, MILLISECONDS,
			  new DelayedWorkQueue(), threadFactory);
	}

	创建一个线程池,可以支持定时及周期性任务执行。如果所有线程均处于繁忙状态,对于新任务会进入 DelayedWorkQueue 队列中
	
	DelayedWorkQueue:是基于堆结构(完全二叉树)的队列,会根据延迟执行的时间进行排序,延迟时间短的先执行。

	场景:周期性执行任务的场景
8、ThreadPoolExecutor 执行任务时规则:
	1)提交一个任务到线程池时,如果线程池中线程数少于corePoolSize,
	  线程池会创建一个核心线程来执行任务。直到线程数达到corePoolSize
		
	2)线程池达到/超过核心线程数,任务塞入队列,线程池会轮询队列执行任务。
	
	3)线程池达到/超过核心线程数,插入任务到队列失败,(队列满),
	  如果线程池没达到最大值线程数,会创建一个非核心线程来执行任务。
	  如果线程池达到最大线程数,会启用拒绝策略
	
	public class ThreadPoolExecutor extends AbstractExecutorService {
		public void execute(Runnable command) {
			if (command == null)
				throw new NullPointerException();
			
			int c = ctl.get();
			//小于核心线程数
			if (workerCountOf(c) < corePoolSize) {
				//创建核心线程来执行任务
				if (addWorker(command, true))
					return;
				c = ctl.get();
			}
			//工作线程大于核心线程数
			//任务成功塞入的队列里,线程池会轮询队列执行任务
			if (isRunning(c) && workQueue.offer(command)) {
				int recheck = ctl.get();
				//线程池状态非running,移除任务,启动拒绝策略
				if (! isRunning(recheck) && remove(command))
					reject(command);
				else if (workerCountOf(recheck) == 0)//如果线程池的工作线程为零
					//如果线程池中线程池未达到最大线程数,创建一个非核心线程来执行任务
					addWorker(null, false);
			}
			//添加非核心线程失败,启用拒绝策略
			else if (!addWorker(command, false))
				reject(command);
		}
	}
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值