基本概念
并发
单个处理器(CPU),轮换执行多个任务,因为轮换的速度比较快,看起来好像多个任务在同时执行一样
并行
多个处理器(CPU),同时执行多个任务,每个任务分配在一个处理器上执行
(并发就像是一个大人给两个婴儿喂饭,喂完这个喂那个;并行,就是两个大人分别同时给两个婴儿喂饭)
同步,异步
同步,在发出一个"调用"时,在没有得到结果之前,该“调用”就不返回。但是一旦调用返回,就得到返回值了。
换句话说,就是由“调用者”主动等待这个“调用”的结果。
异步则是相反,"调用"在发出之后,这个调用就直接返回了,所以没有返回结果。
当一个异步过程调用发出后,调用者不会立刻得到结果。而是在"调用"发出后,"被调用者"通过状态、通知来通知调用者,或通过回调函数处理这个调用
原子性
和数据库事务中的原子性一样,满足原子性特性的操作是不可中断的,要么全部执行成功要么全部执行失败
有序性
编译器和处理器为了优化程序性能而对指令序列进行重排序,也就是你编写的代码顺序和最终执行的指令顺序是不一致的,
重排序可能会导致多线程程序出现内存可见性问题
可见性
多个线程访问同一个共享变量时,其中一个线程对这个共享变量值的修改,其他线程能够立刻获得修改以后的值
一、进程与线程
1、进程
进程是操作系统结构的基础;是一次程序的执行;是一个程序及其数据在处理机上顺序执行时所发生的活动。
操作系统中,几乎所有运行中的任务对应一条进程(Process)。一个程序进入内存运行,即变成一个进程。进程是处于运行过程中的程序,并且具有一定
独立功能。(进程是系统进行资源分配和调度的一个独立单位)。 进程是系统中独立存在的实体,拥有自己独立的资源,拥有自己私有的地址空间。
进程的实质,就是程序在多道程序系统中的一次执行过程,它是动态产生,动态消亡的,具有自己的生命周期和各种不同的状态。进程具有并发性,它可以
同其他进程一起并发执行,按各自独立的、不可预知的速度向前推进。
2、线程
线程,有时被称为轻量级进程(Lightweight Process,LWP),是程序执行流的最小单元。一个标准的线程由线程ID,当前指令指针(PC),
寄存器集合和堆栈组成。另外,线程是进程中的一个实体,是被系统独立调度和分派的基本单位,线程自己不拥有系统资源,只拥有一点儿在运行中必不可少
的资源,但它可与同属一个进程的其它线程共享进程所拥有的全部资源。一个线程可以创建和撤消另一个线程,同一进程中的多个线程之间可以并发执行。
由于线程之间的相互制约,致使线程在运行中呈现出间断性。每一个程序都至少有一个线程,若程序只有一个线程,那就是程序本身。
线程的生命周期及五种基本状态
新建状态(New):当线程对象对创建后,即进入了新建状态,如:Thread t = new MyThread()。
就绪状态(Runnable):当调用线程对象的start()方法(t.start();),线程即进入就绪状态。处于就绪状态的线程,只是说明此线程已经做好了准备,
随时等待CPU调度执行,并不是说执行了t.start()此线程立即就会执行。
运行状态(Running):当CPU开始调度处于就绪状态的线程时,此时线程才得以真正执行,即进入到运行状态。注:就绪状态是进入到运行状态的唯一入
口,也就是说,线程要想进入运行状态执行,首先必须处于就绪状态中。
阻塞状态(Blocked):处于运行状态中的线程由于某种原因,暂时放弃对CPU的使用权,停止执行,此时进入阻塞状态,直到其进入到就绪状态,才有机会
再次被CPU调用以进入到运行状态。根据阻塞产生的原因不同,阻塞状态又可以分为三种:
1.等待阻塞 -- 运行状态中的线程执行wait()方法,使本线程进入到等待阻塞状态;
2.同步阻塞 -- 线程在获取synchronized同步锁失败(因为锁被其它线程所占用),它会进入同步阻塞状态;
3.其他阻塞 -- 通过调用线程的sleep()或join()或发出了I/O请求时,线程会进入到阻塞状态。当sleep()状态超时、
join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态。
死亡状态(Dead):线程执行完了或者因异常退出了run()方法,该线程结束生命周期。
JMM定义了线程和主内存之间的抽象关系:
线程之间的共享变量存储在主内存(Main Memory)中每个线程都有一个私有的本地内存(Local Memory),本地内存是JMM的一个抽象概念,
并不真实存在,它涵盖了缓存、写缓冲区、寄存器以及其他的硬件和编译器优化。本地内存中存储了该线程以读/写共享变量的拷贝副本。
从更低的层次来说,主内存就是硬件的内存,而为了获取更好的运行速度,虚拟机及硬件系统可能会让工作内存优先存储于寄存器和高速缓存中。
给变量加上了volatile后,线程将直接对主内存中的变量进行操作,也就保证了变量无论是谁来读,谁来写,都是一个值,只是保证了变量在内存的可见
性,并不保证原子性,但是对于 i++ 这种复合操作是无法保证原子性的。volatile 作为一个轻量级的锁可以实现内存可见性以及禁止重排序,常用于修
饰标记变量以及双重加锁的场景等。
volatile应用场景
(1) 状态标志
public class TestThread extends Thread {
private volatile boolean on;
public TestThread() {
this.on = true;
}
@Override
public void run() {
while (on && !this.isInterrupted()) {
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
this.interrupt();
}
}
}
public void shutdown() {
this.on = false;
}
}
(2)双重检查模式(DCL)
public class Singleton {
private volatile static Singleton instance;
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
instance = new Singleton();
}
}
return instance;
}
}
线程池
Executors 工厂方法
Executors.newCachedThreadPool()(无界线程池,可以进行自动线程回收)
Executors.newFixedThreadPool(int)(固定大小线程池)
Executors.newSingleThreadExecutor()(单个后台线程)
Executors.newScheduledThreadPool(int);(创建一个周期线程池,支持定时及周期性任务执行)
(自定义线程池)
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)
corePoolSize:线程池核心线程数(平时保留的线程数)
maximumPoolSize:线程池最大线程数(当workQueue都放不下时,启动新线程,最大线程数)
keepAliveTime:超出corePoolSize数量的线程的保留时间。
unit:keepAliveTime单位
workQueue:阻塞队列,存放来不及执行的线程
ArrayBlockingQueue:构造函数一定要传大小
LinkedBlockingQueue:构造函数不传大小会默认为(Integer.MAX_VALUE ),当大量请求任务时,容易造成 内存耗尽。
SynchronousQueue:同步队列,一个没有存储空间的阻塞队列 ,将任务同步交付给工作线程。
PriorityBlockingQueue : 优先队列
threadFactory:线程工厂
handler:饱和策略
AbortPolicy(默认):直接抛弃
CallerRunsPolicy:用调用者的线程执行任务
DiscardOldestPolicy:抛弃队列中最久的任务
DiscardPolicy:抛弃当前任务
测试代码
public class ThreadPool {
public void newCachedThreadPool() {
ExecutorService executorService = Executors.newCachedThreadPool();
executorService.execute(new Runnable() {
@Override
public void run() {
System.out.println("" + Thread.currentThread().getId());
}
});
}
public void newScheduledThreadPool() {
Executors.newScheduledThreadPool(1);
}
public void newFixedThreadPool() {
Executors.newFixedThreadPool(2);
}
public void newSingleThreadExecutor() {
Executors.newSingleThreadExecutor();
}
public void ThreadPoolExecutor() {
int corePoolSize = 2;
int maximumPoolSize = 5;
long keepAliveTime = 60;
TimeUnit unit = TimeUnit.SECONDS;
BlockingQueue<Runnable> workQueue = new ArrayBlockingQueue<>(10);
ExecutorService executorService = new ThreadPoolExecutor(corePoolSize,
maximumPoolSize, keepAliveTime, unit, workQueue);
}
}
AsyncTask是一个抽象类,它有三个泛型参数分别为Params,Progress和Result,其中Params为参数类型,Progress为后台任务执行进度的类型,Result为返回结果的类型。AsyncTask内部也是使用的线程池,使用时需 实现子类
public class MyTask extends AsyncTask<String, String, String> {
@Override
protected void onPreExecute() {
super.onPreExecute();
//运行在MainThread,运行在doInBackground前
}
@Override
protected String doInBackground(String... strings) {
//运行在运行在WorkerThread
return null;
}
@Override
protected void onPostExecute(String s) {
super.onPostExecute(s);
// 异步任务完成,运行在MainThread
}
@Override
protected void onCancelled() {
super.onCancelled();
// 将异步任务设置为取消状态
}
}
一个异步任务的执行一般包括以下几个步骤:
1.execute(Params... params),执行一个异步任务,需要我们在代码中调用此方法,触发异步任务的执行。
2.onPreExecute(),在execute(Params... params)被调用后立即执行,一般用来在执行后台任务前对UI的一些操作
3.doInBackground(Params... params),在onPreExecute()完成后立即执行,用于执行较为费时的操作,此方法将接收输入参数和返回计算结果。
在执行过程中可以调用publishProgress(Progress... values)来更新进度信息。doInBackground运行在WorkerThread
4.onProgressUpdate(Progress... values),在调用publishProgress(Progress... values)时,此方法被执行,运行在MainThread。
5.onPostExecute(Result result),当后台操作结束时,此方法将会被调用,运行在MainThread。
AsyncTask在使用的时候,有几点需要格外注意:
1.异步任务的实例必须在UI线程中创建。
2.execute(Params... params)方法必须在UI线程中调用。
3.不能在doInBackground(Params... params)中更改UI组件的信息。
4.一个任务实例只能执行一次,如果执行第二次将会抛出异常。
线程池好文1
线程池好文2
AsyncTask好文1
AsyncTask好文2