java多线程
线程的概述
容易混淆的概念——线程和进程
- 进程
所有运行中的任务通常对应一个进程(Process),通常当一个程序进入内存运行时,就会变成一个进程。进程时处于运行过程中的程序,具有一定的独立功能。
- 进程的特性:
1、独立性:进程时系统中独立存在的实体,拥有自己的独立资源,每一个进程都拥有自己的私有地址空间。一个用户进程不可以直接访问其他进程的地址空间
2、动态性:程序时一个静态的指令集合,进程时一个正在系统中活动的指令集合。
3、多个进程可以再处理器上并发执行,多个进程之间不会互相影响
* expand
> 并发性和并行性:
并行是指再同一时刻有多条指令在处理器上同时执行,并发指再同一时刻只能有一条指令执行,但多个进程指令被快速轮换执行
- 线程
同一个进程可以同时并发处理多个任务。线程也被称为轻量级的进程。线程在操作系统中是独立的、并发的执行流。对于绝大多数应用来说,一个应用程序通常仅要求有一个主线程。可以有多个线程的执行流。
-
线程的特性
1、线程可以与其他线程共享父进程中的共享变量及部分环境。
2、线程时独立运行的,线程的执行时抢占式的 -
线程的优势
- 进程之间不能共享内存,线程之间可以共享内存
- 系统创建线程所需要重新分配的系统资源要小的多
- java语言内置流多线程的支持不是作为底层操作系统的调度方式,简化流编程
线程的创建和启动
- 在java中Thread代表线程,所有的线程对象都必须时Thread类或其子类的实例。
继承Thread类创线程类
- 定义类继承Thread类
- 重写Thread类的run方法,该run()方法为线程需要完成的任务,run()方法为线程的执行体
- 创建Thread的实例,即创建线程对象
- 调用线程对象的start()来创建并启动线程
Thread API
- Thread.getCurrentThread():
Thread类的静态方法,该方法返回当前正在执行的线程对象 - getName()
该方法是Thread类的实例方法。该方法返回调用该方法的线程的名字
problem
- 通过继承Thread类对象来创建线程类的时候,多个线程之间无法共享线程类的实例变量
/**
* Created by D9ing on 2016/11/14.
* 通过继承Thread来创建线程
*/
public class ExtendsThreadWay {
public static void main(String[] args) {
for (int i = 0; i < 200; i++) {
System.out.println("当前线程为" + Thread.currentThread().getName());
if (i == 100) {
ThreadWay threadWay1 = new ThreadWay();
ThreadWay threadWay2 = new ThreadWay();
threadWay1.start();
threadWay2.start();
}
}
}
}
/**
* 线程实体类
*/
class ThreadWay extends Thread {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(getName() + "线程" + i);
}
}
}
实现Runnable接口创建线程类
- 创建Runnable的实现类
- 重写Runnable的run(),该run()方法是线程的执行体
- 创建Runaable实现类的实例,并以此实例作为Thread的target来创建Thread对象
- 调用线程对象的start()方法来启动线程
/**
* Created by D9ing on 2016/11/14.
* 实现Runnable创建线程
*/
public class ImpRunnableWay {
public static void main(String[] args) {
for (int i = 0; i < 20; i++) {
System.out.println(Thread.currentThread().getName() + "isRun()");
if (i == 18) {
new Thread(new RunnableThread(), "name1").start();
new Thread(new RunnableThread(), "name2").start();
}
}
}
}
/**
* 线程的实现类
*/
class RunnableThread implements Runnable {
//定义实例变量
private int i;
@Override
public void run() {
for (; i < 100; i++) {
System.out.println(Thread.currentThread().getName() + "isRun()...");
}
}
}
使用Callable和Future创建线程
从jdk 1.5开始 java提供了Callable接口,该接口提供了一个call()方法可以作为线程的执行体
- call()方法比run()方法强大,call()可以有返回值
- call()方法可以抛出异常
Future
- java5提供了Future接口来代表Callable接口中的call()方法的返回值,并为Future接口提供了一个FutureTask实现类。该类实现了Future接口,并实现了Runnable接口
- Future接口中定义的方法
- boolean cancel(boolean b) 试图取消该Future里关联的Callable任务
- V get() 返回Callable任务里call()方法的返回值,调用该方法将导致程序阻塞。必须等到子线程执行完毕后程序才会得到返回值
- boolean isCancelled() 如果在Callable任务正常返回前被取消,则返回true
- boolean isDone() 如果Callable任务已完成,泽返回true
Callable接口有泛型的限制,Callable接口中的泛型形参类型与call()方法返回值类型相同
步骤
- 创建Callable接口的实现类
- 实现Callable的call()方法,并将该方法作为线程执行体。且该call()方法有返回值
- 使用FutureTask类来包装Callable对象,并且该FutureTask对象封装了该Callable对象的call方法
- 使用FutureTask类来包装Callable对象,该FutureTask对象封装了该Callable对象的call()方法
- 使用FutureTask作为Thread对象的target创建并启动新线程
- 调用Futuretask对象的get()方法来获得子线程执行结束后的返回值
/**
* Created by D9ing on 2016/11/14.
* 使用Callable Future 接口创建线程
*/
public class CallableWay {
public static void main(String[] args) {
int i = 0;
FutureTask<String> futureTask = new FutureTask<>(new CallableThread());
for (; i < 100; i++) {
System.out.println(Thread.currentThread().getName() + "---循环比变量i的值" + i);
if (i == 50) {
//创建FutureTask对象
new Thread(futureTask, "有返回值的线程").start();
}
}
// TODO: 获取线程的返回值
try {
System.out.println(futureTask.get() + "返回值");
} catch (Exception e) {
e.printStackTrace();
}
}
}
/**
* 泛型为Callable call方法的返回值
*/
class CallableThread implements Callable<String> {
private int i = 0;
@Override
public String call() throws Exception {
for (; i < 100; i++) {
System.out.println(Thread.currentThread().getName() + "---循环变量的值" + i);
}
return String.valueOf(i);
}
}
创建三种线程的方式对比
方式 | 优点 | 缺点 |
---|---|---|
继承Thread | 编程简单,获取当前对象方便 | 已经继承了Thread类不能再继承其它父类 |
实现Runnaleb、Callable | 可以实现多个接口,可以继承别的父类,可以共享实例的变量, 实现Callable接口的线程可以具有返回值 | 编程复杂,获得当前线程对象需要使用Thread.currentThread() |
线程的生命周期
当一个线程被创建后,它既不是一启动就处于运行状态,也不是一直处于执行状态。在线程的生命周期中,线程要经过 新建(new)--->就绪(Runnable)--->运行(Running)--->阻塞(Blocked)--->死亡(dead) 一些状态。当线程启动后,线程并不能一直占有着cpu资源。cpu会在多条线程之间切换。线程也就会在多次执行、阻塞之间切换
新建和就绪状态
- 线程使用new关键字创建后,就会进入新建状态。等待jvm为其分配内存。初始化成员变量值
- 当线程调用start()方法后,线程进入就绪状态,虚拟机JVM会为该线程创建方法调用栈和程序计数器。该状态中的线程并没有运行,只是表示该线程可以运行了,是否开始运行取决于jvm中线程调度器的调度
运行和阻塞状态
-
处于就绪状态的线程获得了CPU资源,开始执行run()方法的线程执行体,该线程处于运行状态,在cpu调度中,当线程数大于处理器数时,依然会存在多个线程在同一个cpu上轮换的现象
-
一个线程开始运行后,就不可能一直处于运行状态,线程在运行过程中需要被中断,目的时让其他的线程获得执行的机会。线程的调度取决于底层的策略。
- 对于抢占式策略的系统来说,系统会给每一个可执行的线程一个时间段来处理任务。当时间段用完后,系统就会剥夺该线程所占用的资源。让其它线程获得执行的机会。
- 对于协作式调度策略的cpu中只有当一个线程调用了它的sleep()或yield()方法后才会放弃所占用的资源
-
当正在执行的线程被阻塞之后,其他线程获得执行的机会,被阻塞的线程会在合适的时候重新进入就绪状态。被阻塞的线程阻塞状态解除后,需要重新等待线程调度器再次调度它。
-
线程从阻塞状态只能进入就绪状态,无法直接进入运行状态。而就绪和运行状态的转换不受程序的控制
线程进入阻塞状态的情况
- 线程调用sleep()方法主动放弃所占用的处理器资源
- 线程调用了一个阻塞式IO方法,在方法返回之前,线程被阻塞
- 线程试图获得一个同步监视器。但该监视器被其他线程所有
- 线程等待某个通知(notity)
- 调用线程的suspend()方法将线程挂起
线程解除阻塞状态重新进入就绪状态
- 调用的sleep()方法过了指定时间
- 线程调用的sleep()方法已经返回
- 线程获得同步监视器
- 线程获得某个通知
- 线程被调用resume()状态
线程的状态转换图
线程的死亡
线程以如下的方式结束,结束后就会死亡
- run()或call()方法执行完成,线程正常结束
- 线程抛出一个未捕获的Exception或Error
- 直接调用线程的stop()方法
- 为了测试某个线程是否已经死亡,可以调用线程对象的isAlive()方法。当线程处于就绪、运行、阻塞状态时会返回true,处于新建、死亡状态时会返回false
tips
- 不要对一个已经死亡的线程调用start()方法。将引发IllegalThreadStateException异常
控制线程
Join线程
-
Thread提供了一个线程等待另一个线程完成的方法——join()方法。当在某个程序执行流中调用其他线程的join()方法时,调用线程将被阻塞。直到被join()方法加入的join线程执行完为止
-
join()重载形式
- join() 等待被join的线程执行完成
- join(long millis)等待被join的线程的时间最长为millis毫秒,如果在millis毫秒内被join的线程还没有执行结束,则不在等待
- join(long millis,int nanos)等待被join的线程的时间最长为millis毫秒加nanos毫微秒
/**
* Created by D9ing on 2016/11/14.
* 线程控制join
*/
public class JoinThread {
public static void main(String[] args) {
//创建一个开始线程
new JoinDemo("新的线程").start();
TODO: 主线程执行
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName() + "执行变量" + i);
// TODO: join
if (i == 10) {
JoinDemo joinThread = new JoinDemo("被join的线程");
try {
joinThread.start();
joinThread.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
/**
* 线程
*/
class JoinDemo extends Thread {
public JoinDemo(String name) {
super(name);
}
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(this.getName() + "执行变量" + i);
}
}
}
后台进程(守护线程)
- 有一种线程,是在后台运行的,为其它的线程提供服务。代表线程JVM的垃圾回收线程
- 特征:如果所有的前台线程都死亡,后台线程会自动死亡
使用方式
- 调用Thread对象的setDarmon(true)可将指定线程设置成后台线程。当所有的前台线程死亡时,后台线程也随之死亡。
/**
* Created by D9ing on 2016/11/14.
* 后台线程
*/
public class DaemonThreadDemo {
public static void main(String[] args) {
// TODO: 创建后台线程
DaemonThread daemon = new DaemonThread();
// TODO: 设置后台线程
daemon.setDaemon(true);
daemon.start();
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName() + "***" + i);
}
System.out.println("前台线程结束!!!");
}
}
class DaemonThread extends Thread {
@Override
public void run() {
for (int i = 0; i < 10000; i++) {
System.out.println(this.getName() + "---" + i);
}
System.out.println("后台线程结束!!!");
}
}
-
Thread提供 isDarmon()方法,用于判断指定的线程是否为后台线程
-
关于线程是否是前台线程和后台线程,前台线程创建的子线程为前台线程,后台线程创建的子线程为后台线程
-
由于所有的前台线程死亡后,JVM会通知后台线程死亡,但接收到死亡通知后需要一定的时间,后台线程设置必须在start()之前调用
线程睡眠
-
如果需要让当前正在执行的线程暂停一段时间,并进入阻塞状态。可以通过调用Thread类的静态sleep()来实现
-
sleep()重载
- sleep(long millis):让当前正在执行的线程暂停millis秒,并进入阻塞状态,该方法受到系统计时器和线程调度器的精度与准确度的影响
- sleep(long millis,int nanos)当前执行的线程暂停millis毫秒加nanos毫微秒,并进入阻塞状态
-
当当前线程调用sleep()方法进入阻塞状态后,在其睡眠时间内,该线程不会获得执行的机会。即使系统中没有可执行的线程,也不会执行sleep线程
线程让步 yield
- yield是Thread提供的一个静态方法,它可以让正在执行的线程暂停,但不会阻塞线程。它只是将该线程转入就绪状态,它的根本机制是让当前线程暂停一下,让系统的线程调度器重新调度一次。
- 实际,当某个线程调用了yield()方法暂停之后,只有优先级与当前线程相同,或者优先级比当前线程高的的处于就绪状态的线程才会获得执行的机会。
线程中断
- interrupt 中断线程 interrupt会抛出一个异常,终止线程的状态。
- stop 停止线程 stop会终止线程的执行,无论线程中的执行状态。后面的任何代码都不会被执行。
sleep yield的区别
-
sleep()暂停当前线程后,会给其他线程执行的机会,不会考虑线程的优先级。但yield()只会给优先级相同,或者优先级更高的线程执行的机会
-
sleep()会将线程转入阻塞状态。直到经过阻塞时间才会转入就绪状态。而yield不会将线程转入阻塞状态。它只是强制当前线程进入就绪状态。
-
sleep()方法声明抛出InterruptedException异常,所以调用sleep()要么捕获该异常要么声明抛出异常。
-
sleep()比yield()的可移植性更好
线程的优先级
-
线程具有一定的优先级,优先级高的线程获得较多的执行机会,优先级低的获得较少的执行机会
-
Thread类提供了setPriority(int newPriority)来设置和返回指定线程的优先级。
- MAX_PRIORITY 10 最高优先级
- MIN_PRIORITY 1 最低优先级
- NORM_PRIORITY 5 普通优先级
线程同步 synchronized关键字
- 同步的情况
当多线程并发,有多段代码同时执行时,我们希望在某一段代码执行时CPU不要切换到其他的线程 如果两段代码是同步的,那么同一时间只能执行一段,在一段代码没执行结束之前,不会执行另外一段代码 线程的同步问题就是由于线程调度的不确定性引起的。
同步对象(同步锁)
- 在线程同步中,线程开始执行同步代码块之前,必须先获得同步锁
- 任何时刻只能有一个线程可以获得同步锁
- 当同步代码块执行完后,该线程会释放对同步锁的锁定。
- java程序允许使用任何对象作为同步监视器。
同步代码块
- 使用synchronized关键字加上一个锁对象来定义一段代码
- 锁对象可以是任意对象
- 多个同步代码块如果使用相同的锁对象,那么使用这个相同的锁的代码就是同步的。
/**
* Created by D9ing on 2016/11/15.
* 线程让步demo
*/
public class YieldThreadDemo {
public static void main(String[] args) {
YieldDemo yieldDemo = new YieldDemo();
// TODO: 线程一启动
new Thread() {
@Override
public void run() {
// TODO: 同步控制
synchronized (yieldDemo) {
for (int i = 0; i < 100; i++) {
yieldDemo.print1();
}
}
}
}.start();
// TODO: 线程二启动
new Thread() {
@Override
public void run() {
// TODO: 锁对象
synchronized (yieldDemo) {
for (int i = 0; i < 100; i++) {
yieldDemo.print2();
}
}
}
}.start();
}
}
class YieldDemo {
public void print1() {
System.out.print("一");
System.out.print("二");
System.out.print("三");
System.out.print("四");
System.out.print("\r\n");
}
public void print2() {
System.out.print("1");
System.out.print("2");
System.out.print("3");
System.out.print("4");
System.out.print("\r\n");
}
}
同步方法
在方法的修饰符中添加Synchronized修饰符
- 锁对象
- 非静态的同步方法的锁对象是调用者的对象this
- 静态的同步方法的锁对象是该类的字节码对象
多线程死锁问题
多线程使用锁嵌套,互相等待释放锁对象就会出现死锁问题
关于线程安全的类
- Vector 线程安全 ArrayList 线程不安全的
- StringBuffer 线程安全 StringBuilder 线程不安全
- Hashtable 线程安全 HashMap 线程不安全
- 集合类的不安全线程转化 Collections.synchronized(xxx)
同步锁
java5 java提供了——显示定义同步锁对象来实现同步
- Lock
- Lock支持多个相关的条件对象
- 每次只能有一个线程对Lock对象加锁,线程开始访问共享资源之前应该先获得Lock对象 *Lock实现类ReentrantLock(可重入锁) *java8提供了新的StampedLock
- 使用ReentrantLock代码格式
线程间通信
java提供Object方法来实现线程间通信
- wait() 、notify()、notifyAll()
- wait():导致当前线程等待,直到其他线程调用该同步监视器的notify()方法或notifyAll()方法来唤醒线程。该wait()有三种重载形式——无时间参数的wait()[一直等待,直到其他线程通知]、带毫秒参数的wait()、带毫微秒参数的wait()[等待指定时间后自动苏醒]调用wait()方法的当前线程会释放对该同步监视器的锁定
- notify() 唤醒在此同步监视器上等待的单个线程。如果所有线程都在此同步监视器上等待,则会选择唤醒其中一个线程。选择是任意的,只有当前放弃对该同步监视器的锁定后(wait()),才可以执行被唤醒的线程
- notifyAll() 唤醒在此同步监视器上等待的所有线程。只有当前线程放弃对该同步监视器的锁定后,才可以执行被唤醒的线程
使用Condition控制线程通信
- 如果程序中不使用synchronized关键字来同步,而是直接使用Lock对象来保证同步,系统中不存在隐式的同步监视器,也就不能使用wait()、notify()、notifyAll()进行线程通信
- 当使用Lock来对象来保证同步时,java提供了一个Condition类来保持协调。使用Condition可以让那些已经得到Lock对象却无法继续执行的线程释放Lock对象,Condition对象也可以唤醒其他处于等待的线程
Condition对象通信机制
Condition对象将同步监视器对象的方法(wait()、notity()、notityAll())分解成截然不同的对象,通过将这些对象与Lock对象组合使用,为每个对象提供多个等待集(wait-set) 也就是说,在这种情况下 Lock替代了synchronized同步代码块。Condition替代了同步监视器的功能
- 步骤
- 创建Lock锁对象
- 通过Lock锁对象获取绑定的Condition对象
- 加锁---> 操作数据 --->操作线程--->释放锁(一般在finally中释放)
API: Condition实例被绑定在一个Lock对象上,要获得特定Lock对象的Condition对象,调用Lock对象的new Condition()方法
- await() 类似于隐式同步监视器上的wait()方法,导致当前线程等待,直到其他线程调用该Condition的signal()方法或signalAll()方法来唤醒该线程。
- signal() 唤醒在此Lock对象上等待的单个线程,类似notify(),如果所有的线程都等待,会随机选择一个唤醒只有当前线程放弃对该Lock对象的锁定后,才可以执行被唤醒的线程
- signalAll() 唤醒在此Lock对象上等待的所有线程
使用阻塞队列(BlockingQueue)
- java5 提供了一个线程同步的工具接口
- 当生产者线程向BlockingQueue中放入元素时,如果该队列已满。则线程被阻塞。当消费者师徒从BlockingQueue中取出元素时,如果队列已空,则该线程被阻塞
API:
- put(E e) 尝试把E元素放入BlockingQueue中,如果该队列的元素已满,则阻塞线程
- take() 尝试从BlockingQueue的头部取出元素,该队列的元素已空,则阻塞线程
- 集成Queue()接口的方法
- add(E e)
- offer(E e)
- put(E e)
需要学习一下队列
线程组和未处理的异常
线程组
- Java使用ThreadGroup来表示线程组,线程组的作用是可以对一批线程进行管理。
- 程序对线程组的控制相当于同时控制这批线程。
- 用户创建的所有线程都属于指定线程组
- 如果没有显式指定线程属于哪个线程组,则该线程属于默认线程组
- 默认情况下,子线程和创建它的父线程属于一个线程组
- 一旦某个线程加入了指定线程组之后,该线程将一直属于该线程组,直到线程死亡,线程运行中不能改变它所属的线程组
API:
- Thread(ThreadGroup group,Runnable target):以target的run()方法作为线程执行体创建新线程属于group线程组
- Thread(ThreadGroup group,Runnable target target,String name):以target的run()方法作为线程执行体创建新线程,该线程属于group线程组,且线程名为name
- Thread(ThreadGroup group,String name)创建新线程,新线程名为name,属于group线程组
- getThreadGroup()返回该线程所属的线程组对象。表示一个线程组
- ThreadGroup(String name) 以指定的线程组名字来创建新的线程组
- ThreadGroup(ThreadGroup parent,String name):以指定的名字、指定的父xian'cheng线程组创建一个新线程组
- int activeCount() 返回此线程组中活动线程的数目
- interrupt():中断此线程组中的所有线程
- isDaemon()判断该线程组是否是守护线程组
- setDaemom(boolean deamon)设置线程组为后台线程组,当后台线程组的最后一个线程被销毁后,线程组销毁
- setMaxPriority(int Pri)设置线程组的最高优先级
线程组异常处理
线程组定义了 void uncaughtException(Thread t,Throwable e) 该方法可以处理线程组内的任意线程所抛出的未处理异常
java5开始 线程在执行过程中如果抛出了一个未处理的异常,JVM在结束该线程之前会自动查找是否有对应的Thread.UncaughtExceptionHandler对象,如果找到该处理器对象,则会调用该对象的uncaughtException(Thread t,Throwable e)来处理异常
- Thread.UncaughtExceptionHandler是Thread类的一个静态内部接口。接口内只有一个方法 void uncaughtException(Thread t,Throwable e)
- t 代表出现异常的线程
- e 代表该线程抛出的异常
异常处理
- static setDefaultUncaughtExcptionHandler(Thread.UncaughtExceptionHandler eh):为该线程类的所有线程实例设置默认的异常处理器
- setUncaughtExceptionHandler(Thread.UncaughtExceptionHandler eh):为指定的线程实例设置异常处理器
由于每个ThreadGroup类实现了Thread.UncaughtExceptionHandler接口,所以每个线程所属的线程组将会作为默认的异常处理器。当一个线程抛出未处理的异常时,JVM会首先查找该异常对应的异常处理器也就是(setUncaughtExceotionHandler()中指定的异常处理器) 如果找到该异常处理器,则将调用该异常处理器处理异常,否则,JVM将会调用该线程所属的线程组对象的uncaughtException()来处理异常 线程组处理异常流程
- 如果该线程组有父线程组,则调用父线程组的uncaughtException()来处理异常
- 如果该线程实例所属的线程类有默认的异常处理器则调用该异常处理器来处理异常
- 如果该异常对象是ThreadDeath对象,则不做任何处理,否则将异常跟踪栈的信息打印到System.err错误输出流,并结束线程
tips:与try...catch区别
- 当使用catch捕获异常时,异常不会向上传播给上一级调用者
- 使用异常处理器对异常进行处理后,异常依然会传播给上一级调用者
/**
* Created by D9ing
* 2016/11/17
* Describe:线程的异常处理
* Todo:
* Good Luck
*/
public class ExHandlerThread {
public static void main(String[] args) {
// TODO: 设置主线程异常处理器
Thread.currentThread().setUncaughtExceptionHandler(new mExhandler());
int a = 5 / 0;//除0异常
System.out.println("线程正常结束");
}
}
class mExhandler implements Thread.UncaughtExceptionHandler {
@Override
public void uncaughtException(Thread t, Throwable e) {
System.out.println(t + "线程出现了异常" + e);
}
}
线程池
soource
- 系统启动一个新线程的成本比较高,这时候应当使用线程池来更好的提高性能。
- 线程池在系统启动时即创建大量空闲的线程,程序将一个Runnable对象或Callable对象穿给线程池。线程池会启动一个线程来执行他们的run()或者call(),当执行体方法结束后,线程并不会死亡,而是再次返回线程池中成为空闲状态,等待执行下一个Runnable对象的run()或call()方法
在java5,java内建线程池,java5新增了一个Executors工厂类来产生线程池
- newCachedThreadPool() 创建一个具有缓存功能的线程池,系统根据需要创建线程,这些线程将被缓存在线程池中
- newFixedThreadPool(int nThreads)创建一个可重用的具有固定线程数的线程池
- newSingleThreadExceutor() 创建一个只有单线程的线程池==newFixedThreadPool(1)
- newScheduledThreadPool(int corePoolSize) 创建具有指定线程数的线程池,可以在指定延迟后执行线程任务,corePoolSize指池中锁保存的线程数
- newSingleThreadScheduledExecutor()创建只有一个线程的线程池,可以在指定延迟后执行线程任务
java8新增
-
ExecutorService new WorkStralingPool(int parallelism) 创建持有足够的线程的线程池来支持给定的并行级别,该方法还会使用多个队列来减少竞争
-
ExecutorService new WorkStealingPool() 创建一个依赖CPU核数的并行数量的线程池
-
ExecutorService 代表一个线程池对象
-
ScheduleExecutorService 是ExecutorService是子类 可以在指定延迟后执行线程任务
ExecutorService代表尽快执行线程的线程池,只要线程池中有空闲线程,就会立即执行线程任务
API 方法 Future<?> submit(Runnable task) 将一个Runnable对象提交给指定的线程池,Future代表Runnable任务的返回值 <T>Future<T>submot(Runnable task,T result)将一个Runnable对象提交给指定的线程池,result显式指定线程执行结束后的返回值,所以Future对象将在run()方法执行结束后返回result <T>Future<T>submit(Callable<T> task) 将一个Callable对象提交给指定的线程池,Future代表Callable中的call()方法的返回值
延迟加载线程池 ScheduleFuture<T>schedule(Callable<V> callable,long delay,TimeUnit unit)指定callable任务将在delay延迟后执行 ScheduleFuture<?>schedule(Runnable command,long delay,TimeUnit unit)指定command任务将在delay延迟后执行 ScheduleFuture<?> scheduleAtFixedRate(Runnable command,long delay,long period,TimeUnit unit)指定command任务将在delay延迟后执行,而且以设定的频率重复执行 ScheduledFuture<?> scheduleWithFixedDelay(Runnable command,long initialDelay,long delay,TimeUnit unit) 创建并执行一个在给定初始延迟后首次启用的定期操作,随后在每一次执行终止和下一次执行开始之间都存在给定的延迟,除非遇到异常,否则只能通过程序显式取消或终止任务
- 用完一个线程池后,应该调用该线程池的shutdown()方法,该方法将启动线程池的关闭序列,调用shutdown()方法后的线程池不再接受新任务,以前已经提交的任务会执行完成,当线程池中的所有任务都执行完成后,池中所有的线程都会死亡。
- shutdownNow()该方法试图停止所有正在执行的活动任务,暂停处理正在等待的任务,并返回等待执行的任务列表
使用线程池的步骤
- 调用Executors类的静态工厂方法创建一个ExecutorsService对象
- 创建Runnable实现类或Callable实现类对象,作为线程执行任务
- 调用ExecutorService对象的submit()来提交Runnable对象或Callable对象
- 不想提交任何任务时,调用ExecutorService对象的shutdown()方法来关闭线程池
/**
* Created by D9ing
* 2016/11/18
* Describe:线程池测试
* Todo:
* Good Luck
*/
public class ThreadPoolTest {
public static void main(String[] args) {
ExecutorService executorService = Executors.newFixedThreadPool(6);
Runnable target = () -> {
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName() + "的值为" + i);
}
};
// TODO: 向线程池提交两个线程
executorService.submit(target);
executorService.submit(target);
// TODO: 关闭线程池
executorService.shutdown();
}
}
java8增强的ForkJoinPool
-
java7提供了ForkJoinPool来支持将一个任务拆分成多个“小任务”并行计算,再把多个“小任务”的结果合并成总的计算结果
-
ForkJoinPool是ExecuttorService的实现类,是一种特殊的线程池。
- ForkJoinPool(int p)创建一个包含p个并行线程的ForkJoinPool
- ForkJoinPool()以Runtime.availableProcessors()方法的返回值作为线程数来创建线程池
-
java8拓展了ForkJoinPool功能,ForkJoinPool增加了通用池的功能
- ForkJoinPool commonPool() 该方法返回一个通用池,通用池的运行状态不会受shutdown()或shutdownNow()方法的影响。除非程序杀死了进程
- int getCommonPoolParallelism() 返回通用池的并行级别
-
再创建了ForkJoinPool实例之后,就可调用ForkJoinPool的submit(ForkJoinTask task)或invoke(ForkJoinTask task)来执行指定任务了。ForkJoinTask代表一个可以并行、合并的任务。
-
ForkJoinTask 是一个抽象类, 有两个抽象子类
RecursiveAction 没有返回值
RecursiveTask<T> 有返回值,返回值为泛型T
API:
- fork() 执行方法
- join() 当线程结束后返回线程的返回值
/**
* Created by D9ing
* 2016/11/18
* Describe:
* Todo:
* Good Luck
*/
public class RecursiveTaskSum {
public static void main(String[] args) {
int[] ints = new int[100];
Random random = new Random();
int total = 0;
for (int i = 0, len = ints.length; i < len; i++) {
int tmp = random.nextInt(20);
// TODO: 对数组元素赋值,并将数组元素的值添加到sum总和中
total += (ints[i] = tmp);
}
System.out.println(total);
// TODO: 创建一个通用池
ForkJoinPool pool = ForkJoinPool.commonPool();
// TODO: 提交可分解的任务
Future<Integer> future = pool.submit(new mRecurisveTask(ints, 0, ints.length));
try {
System.out.println(future.get());
pool.shutdown();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
class mRecurisveTask extends RecursiveTask<Integer> {
// TODO: 定义单次执行的个数
private static final int THRESHOLD = 20;
// TODO: 累加的数组
private int arr[];
private int start;
private int end;
// TODO: 构造器
public mRecurisveTask(int[] arr, int start, int end) {
this.arr = arr;
this.start = start;
this.end = end;
}
@Override
protected Integer compute() {
int sum = 0;
if (end - start < THRESHOLD) {
for (int i = start; i < end; i++) {
sum += arr[i];
}
return sum;
} else {
int middle = (start + end) / 2;
mRecurisveTask leftTask = new mRecurisveTask(arr, start, middle);
mRecurisveTask rightTask = new mRecurisveTask(arr, middle, end);
leftTask.fork();
rightTask.fork();
return leftTask.join() + rightTask.join();
}
}
}
[java 在多核cpu的强大]
线程相关类
java还位线程安全提供了一些工具类。
-
ThreadLocal 代表一个线程局部变量,把数据放在ThreadLocal中就可以让每个线程创建一个该变量的副本
- 支持泛型的ThreadLocal<T> 其作用就是每一个线程的局部变量,每个线程都可以保留一个自己的副本,不会和其他线程的副本冲突。
- ThreadLocal API *T get() 返回此线程局部变量中当前副本中的值 *void remove() 删除此线程局部变量中当前线程副本中的值 *void set(T value) 设置此线程局部变量中当前副本的值
-
使用建议:如果多个线程之间需要共享资源,以达到线程之间的通信功能,就要使用同步机制。如果仅仅需要隔离多个线程之间的共享冲突,则使用ThreadLocal
包装线程不安全集合
- Collections 静态方法可以用来包装线程不安全的集合为线程安全的集合
- Collection<T> synchronizedCollection(Collection<T> c) 返回对应线程安全的collection
- List synchronizedList 返回指定List对象对应的线程安全List对象
- Map synchronizedMap 返回指定的Map集合对应的线程安全Map对象
- set...
- tree...
线程安全的集合
- java5 在concurrent 下提供大量支持高效并发访问的集合接口和实现
Concurrent开头的集合类 ConcurrentHashMap、ConcurrentSkipListMap、ConcurrentSkipListSet、ConcurrentLinkedQueue和ConcurrentLinkedDeque
以CopyOnWrite开头 CopyOnWriteArrayList、CopyOnWriteArraySet
- 以Concurrent开头的集合类代表了支持并发访问的集合。可以支持多个线程并发写入访问。
- ConcurrentLinkedQueue 实现了多线程的高效。多个线程访问无须等待
Exercise misson 1: 两个线程一个打印1-27 另一个打印A-Z
package com.probuing.learnjavaagain.threadlearn.threadcommexercise;
/**
* Created by D9ing
* 2016/11/18
* Describe:需求 两个线程一个打印1~52 另一个打印A-z
* Todo:普通线程通信实现
* Good Luck
*/
public class PrintCommThread {
public static void main(String[] args) {
PrintBean bean = new PrintBean("打印");
// PrintBean bean2 = new PrintBean("打印字母");
new PrintNumThread(bean).start();
new PrintLetterThread(bean).start();
}
}
class PrintBean {
// TODO: flag
private boolean flag = true;
private String name;
private char[] letter = new char[]{'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z'};
public PrintBean(String name) {
this.name = name;
}
public synchronized void printNum() {
for (int i = 1; i < 53; i++) {
if (!flag) {
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(name + "线程名:" + Thread.currentThread().getName() + "----" + i);
flag = false;
notifyAll();
}
}
public synchronized void printLetter() {
for (int i = 0; i < letter.length; i++) {
if (flag) {
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(name + "线程名:" + Thread.currentThread().getName() + "----" + letter[i]);
flag = true;
notifyAll();
}
}
}
/**
* @User:D9ing
* @Data:上午11:46
* @describe:打印1~52线程
* @todo:
*/
class PrintNumThread extends Thread {
// TODO: 索引bean
private PrintBean bean;
public PrintNumThread(PrintBean bean) {
this.bean = bean;
}
@Override
public void run() {
bean.printNum();
}
}
/**
* @User:D9ing
* @Data:上午11:56
* @describe:打印字母线程
* @todo:
*/
class PrintLetterThread extends Thread {
private PrintBean bean;
public PrintLetterThread(PrintBean bean) {
this.bean = bean;
}
@Override
public void run() {
bean.printLetter();
}
}
- Lock锁实现
package com.probuing.learnjavaagain.threadlearn.threadcommexercise;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* Created by D9ing
* 2016/11/18
* Describe:两个线程 一个打印1-27 另一个打印A-z
* Todo:java8 Lock实现
* Good Luck
*/
public class PrintCommLock {
public static void main(String[] args) {
LockBean bean = new LockBean("打印实体");
// TODO: 普通线程构造
// new Thread(new PrintNumThreadLock(bean)).start();
// new Thread(new PrintLetterThreadLock(bean)).start();
// TODO: 线程池
ExecutorService executorService = Executors.newFixedThreadPool(4);
executorService.submit(new PrintNumThreadLock(bean));
executorService.submit(new PrintLetterThreadLock(bean));
// executorService.submit(new )
}
}
/**
* @User:D9ing
* @Data:下午12:34
* @describe: Lock实体
* @todo:
*/
class LockBean {
private final Lock lock = new ReentrantLock();
private final Condition cond = lock.newCondition();
private char[] letter = new char[]{'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z'};
private String name;
private boolean flag = true;
public LockBean(String name) {
this.name = name;
}
/*
打印数字方法
*/
public void printNum() {
// TODO: 加锁
lock.lock();
try {
for (int i = 0; i < letter.length; i++) {
if (!flag) {
cond.await();
}
System.out.println(Thread.currentThread().getName() + "---" + i);
flag = false;
// TODO: 唤醒线程
cond.signalAll();
}
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
// TODO: 释放锁
lock.unlock();
}
}
public void printLetter() {
// TODO: 加锁
lock.lock();
try {
for (int i = 0; i < letter.length; i++) {
if (flag) {
// TODO: 线程等待
cond.await();
}
System.out.println(Thread.currentThread().getName() + "---" + letter[i]);
flag = true;
// TODO: 唤醒线程
cond.signalAll();
}
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
class PrintNumThreadLock implements Runnable {
private LockBean bean;
public PrintNumThreadLock(LockBean bean) {
this.bean = bean;
}
@Override
public void run() {
bean.printNum();
}
}
class PrintLetterThreadLock implements Runnable {
private LockBean bean;
public PrintLetterThreadLock(LockBean bean) {
this.bean = bean;
}
@Override
public void run() {
bean.printLetter();
}
}