进程三大特征
独立性:进程是系统中独立存在的实体,它可以拥有自己独立的资源,每一个进程都拥有自己私有的地址空间。在没有经过进程本身允许的条件下,一个用户进程不可以直接访问其他进程的地址空间。
动态性:进程与程序的区别在于,程序只是一个静态的指令集合,而进程是一个正在系统中活动的指令集合。在进程中加入了时间的概念。进程拥有自己的生命周期和各种不同的状态。
并发性:多个进程可以在单个处理器上并发执行,多个进程之间不会互相影响。
并发与并行:并行指在同一时刻,有多条指令在多个处理器上同时执行;并发指在同一时刻只能有一条指令执行,但多个进程指令被快速轮换执行,使得在宏观上具有多个进程同时执行的效果。
线程的创建和启动
- 继承Thread类创建线程类
- 定义Thread类的子类,并重写run()方法,run方法称为线程执行体
- 创建Thread子类的实例,即创建了线程对象。
- 调用线程对象的start方法来启动线程。
- 实现Runnable接口来创建线程类
- 定义Runnable接口的实现类,重写run()方法
- 创建Runnable实现类的实例,并以此实例作为Thread的target来创建Thread对象。
- 调用线程对象的start()方法启动线程。
- 使用Callable和Future创建线程
-
创建Callable接口的实现类并实现call()方法,call()方法将作为线程执行体且call()方法有返回值,再创建Callable实现类的实例。
-
使用FutureTask类来包装Callable对象,该FutureTask对象封装了该Callable对象的call()方法的返回值。
-
使用FutureTask对象作为Thread对象的target创建并启动新线程
-
调用FutureTask对象的get()方法来获得子线程执行的返回值。
优缺点:
- 实现Runnable、Callable接口的方式:
优点:实现Runnable、Callable接口的方式还可以继承其它类。
多个线程共享同一个target对象,适合多个相同线程处理同一份资源。
缺点:编程复杂,需使用Thread.currentThread()方法来访问当前线程。
- 继承Thread类的方式:
缺点:线程不能再继承其它类。
优点:边写简单,使用this便可获得当前线程,不用使用Thread.currentThread()方法。
线程的生命周期
控制线程
- join线程:一个线程等待另一个线程完成的方法-join()方法,当在某个程序执行流中调用其他线程的join()方法,调用线程将被阻塞,知道被join()方法加入的线程执行完成。
- 后台线程:有一种线程,它是在后台执行的,它的任务是为其他的线程提供服务,叫做后台线程。例如JVM垃圾回收线程。特征:如果所有的前台线程都死亡,后台线程会自动死亡。调用Thread对象的setDaemon(true)方法设置。
- 改变线程优先级:每个线程默认的优先级都与创建它的父线程的优先级相同,Thread提供setPriority(int newPriority)、getPriority()方法来设置和返回线程的优先级。
- 线程睡眠: 让当前正在执行的线程暂停一段时间,并进入阻塞状态,调用Thread.sleep()方法。Thread.yield():让当前正在执行的线程暂停,但不会阻塞该线程,只是将线程转入就绪状态。
sleep()方法与yield()区别:
- sleep()方法暂停当前线程后,会给其他线程执行机会,不会理会其他线程的优先级,但yield()只会给优先级相同以及更高的线程执行机会。
- sleep()方法将线程转入阻塞状态,直到阻塞时间到才会转入就绪状态;而yield()不会将线程转入阻塞状态,他只是强制当前线程进入就绪状态。
- sleep()方法声明并抛出了InterruptedException,而yield()方法则没有声明抛出任何异常。
- sleep()方法比yield()方法有更好的可移植性。
线程同步
- 同步代码块:线程开始执行同步代码块前,必须先获得同步监视器的锁定。
synchronized (obj) {
//同步代码块
}
- 同步方法:使用synchronized修饰的方法
- 同步锁:ReadWriteLock,Lock,ReentrantLock, Java 8新增StampedLocked类
class{
Private final ReentrantLock lock = new ReentrantLock();
public void m(){
//加锁
lock.lock();
try{
//线程安全代码
}finally{
lock.unlock();//释放锁
}
}
}
- 死锁:当两个线程互相等待对方释放同步监视器时就会产生死锁。
线程通信
-
Object提供wait(),notify(),notifyAll()方法
wait():导致当前线程等待 notify():唤醒在此同步监视器上等待的单个线程。 notifyAll():唤醒在此同步监视器上等待的所有线程。
-
使用Condition控制线程通信:使用Condition可以让那些已经得到Lock对象却无法继续执行的线程释放Lock对象。Condition类提供了三个方法:
await():导致当前线程等待 signal():选择唤醒其中一个线程。 signalAll():唤醒在此Lock对象上等待的所有线程。
-
使用阻塞队列(BlockingQueue)控制线程通信:程序的两个线程通过交替向BlockingQueue中放入元素、取出元素,即可很好的控制线程的通信。
put(E e):尝试把E元素放入队列中,如果队列满,阻塞该线程。 take():尝试从队列头部取出元素,如果队列元素空,阻塞该线程。
线程组和未处理的异常
ThreadGroup可以对一批线程进行分类管理。Thread类提供了几个构造器来设置该新创建的线程属于哪个线程组。
Thead(THreadGroup group,Runnable target)
Thread(THreadGroup group,Runnable target, String name)
Thread(THreadGroup group, String name)
线程组处理异常的默认流程:
- 如果该线程有父线程组,则调用父线程组的uncaughtException()方法来处理该异常。
- 如果该线程实例所属的线程类有默认的异常处理器(由setDefaultUncaughtExceptionHandler()方法设置的异常处理器),那么就调用该异常处理器来处理异常。
- 如果该异常对象是ThreadDeath的对象,则不做任何处理;否则,将异常跟踪栈的信息打印到System.err错误输出流,并结束该线程。
线程池
Executors工厂类产生线程池
- newCachedThreadPool():创建一个具有缓存功能的线程池
- newFixedThreadPool(int nThreads):创建一个具有固定数量的线程池。
- newSingleThreadExecutor(int corePoolSize):创建一个只有单线程的线程池。
- newScheduledThreadPool(int corePoolSize):创建一个具有固指定线程数的线程池,它可以在指定延迟后执行线程任务。
- newSingleThreadScheduledExecutor():创建一个具有单个线程数的线程池,它可以在指定延迟后执行线程任务。
- ExecutorService new WorkStealingPool(int parallelism):创建持有足够的线程的线程池来支持给定的并行级别。
- ExecutorService newWorkStealingPool():上一个方法的简化版本。
使用线程池来执行线程任务的步骤:
- 调用Executors类的静态方法创建一个ExecutorService对象,该对象代表一个线程池。
- 创建Runnable实现类或Callable实现类的实例,作为线程执行任务。
- 调用ExecutorService对象的submit()方法来提交Runnable和Callable实例。
- 当不想提交任何任务时,调用ExecutorService对象的shutdown()方法来关闭线程池。
ForkJoinPool:支持将一个任务拆分成多个“小任务”并行计算,再把多个“小任务”的结果合并成总的计算结果。
- ForkJoinPool(int parallelism):创建一个包含parallelism个并行线程的ForkJoinPool。
- ForkJoinPool():以Runtime.availableProcessors()方法的返回值作为parallelism参数来创建ForkJoinPool。
- Java 8:ForkJoinPool commonPool():返回一个通用池。
- int getCommonPoolParallelism():该方法返回通用池的并行级别。
创建了ForkJoinPool实例后,调用submit(ForkJoinTask task)或invoke(ForkJoinTask task)方法来执行指定任务了,ForkJoinTask代表一个可以并行、合并的任务。有两个抽象子类:RecursiveAction代表有返回值的任务和RecursiveTask代表没有返回值的任务。
线程相关类
ThreadLocal的功用就是为每一个使用该变量的线程都提供一个变量值的副本,使每一个线程都可以独立地改变自己的副本,而不会和其他线程的副本冲突。
- T get():返回此线程局部变量中当前线程副本中的值。
- void remove():删除此线程局部变量中当前线程的值。
- voId set(T value):设置此线程局部变量中当前线程副本中的值。