课程回顾
文件类: File
创建文件 createNewFile
删除文件 delete
判断文件是否存在: exists
获取名称: getName
获取父路径: getParent
获取绝对路径: getAbsolutePath
获取文件列表: listFile, listRoot,
是否可读: canRead
是否可写: canWrite
获取文件长度: length 字节数
文件读写:
①字节流: FileInputStream FileOutputStream BufferedInputStream BufferedOutputStream( 8192byte )
write( byte[] ) flush 刷出, close , read( byte[] )
②字符流: 操作的是字符
FileWriter 输出流 FileReader 输入流 BufferedReader BufferedWriter
write( char[] ) read( char[] ) readLine ready(就绪)
③ObjectInputStream ObjectOutputStream 对象输入输出流 序列化到磁盘, 反序列化(从磁盘文件转成对象) writeObject readObject Serialzable 标记接口; 读取注意结束标记 ; 写个null
④: 转换流 InputStreamReader 字节流—字符流 OutputStreamWriter 字符流—字节流
⑤: PrintWriter System.out.println out就是PrinterWriter 注意 flush刷出缓冲区
⑥: DataInputStream DataOutputStream 主要提供操作各种数据类型的方法 可以读写各种数据类型
线程的概念
进程: 正在运行中的程序; 一个进程包括多个线程, 线程是程序执行的最小单元;
进程: 高速公路, 线程是单个行车道; 双向8车道
迅雷多线程下载:
再从JVM的内存分布图看一下线程的结构:
胶片电影: 24帧/秒
查看java进程的线程数:
PID : process id 进程ID,进程编号
VisualVM 可视化的java虚拟机
观察自己项目的线程数 :
说明:
①: Finalizer线程: 垃圾回收线程 final finally finalize三个区别?
protected void finalize() throws Throwable { } //Object方法定义的
②: 自己程序的线程
main 线程 主线程
Thread-0 线程 子线程
③: RMI remote method invoke 远程方法调用
接收远程程序的网络连接;
创建线程的方式
java提供一个类封装线程的信息和操作; Thread类, java.lang.Thread ;
public class Thread extends Object implements Runnable
- 创建一个新的执行线程有两种方法。 一个是将一个类声明为
Thread
的子类。 这个子类应该重写run
类的方法Thread
。 然后可以分配并启动子类的实例。 例如,计算大于规定值的素数的线程可以写成如下:- 另一种方法来创建一个线程是声明实现类
Runnable
接口。 那个类然后实现了run
方法。 然后可以分配类的实例,在创建Thread
时作为参数传递,并启动。
继承Thread类
重写Thread类的run方法: run方法是线程执行的核心方法.但是启动线程是start方法; 如果调用run方法就是普通的方法调用,看不到多线程的效果.
package com.hanker;
public class MyThread extends Thread{
@Override
public void run() {
System.out.println("开始执行我的线程:" + getName());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("子线程结束");
}
}
//================================
package com.hanker;
public class MyThreadTest {
public static void main(String[] args) {
System.out.println("main方法的线程: " + Thread.currentThread().getName());
MyThread myThread = new MyThread();
myThread.start();//启动线程
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("主线程结束");
}
}
run方法是线程执行的主体: 写核心业务代码, 下载, 上传, 更新, 数据导出(同步), 耗时的业务开一个子线程去执行. 网络连接, 查杀病毒. 提交订单主业务, 发送一个手机验证码, 发送一个手机短信确认订单可以放在子线程. 物流信息做一个独立的线程.
实现 Runnable 接口
Runnable接口定义:
@FunctionalInterface
public interface Runnable {
/**
没有返回值
不能抛出异常
*/
public abstract void run();
}
案例:
package com.hanker;
public class MyThread2 implements Runnable{
@Override
public void run() {
System.out.println("开始网络下载..............");
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("数据下载完成..........");
}
}
//=======================
package com.hanker;
public class MyThreadTest2 {
public static void main(String[] args) {
System.out.println("主线程:" + Thread.currentThread().getName());
//本质线程还是Thread, 只不过MyThread2提供的是线程运行的主体,
MyThread2 myThread2 = new MyThread2();
Thread th = new Thread(myThread2);
th.start();
System.out.println("主线程结束");
}
}
实现Callable接口
@FunctionalInterface
public interface Callable<V> {
/**
* Computes a result, or throws an exception if unable to do so.
* 计算一个结果,如果不能执行业务,抛出一个异常,
* @return computed result
* @throws Exception if unable to compute a result
*/
V call() throws Exception;
}
Callable接口和Runnable接口:
Callable可以有返回值,可以抛出异常, Runnable没有返回值,不能抛出异常
这个接口的运行都需要借助Thread类执行.
通过线程池创建子线程
见线程池模块
总结:
继承Thread类
①如果继承Thread类: 可以使用Thread类提供的方法
②如果继承Thread类: 不能继承其他类,因为java是单继承
③如果继承Thread类: 不能实现数据共享
实现Runnable接口
①: 还可以继承其他类
②: 不能使用Thread提供的方法
③: 可以实现数据共享
实现Callable接口
①:还可以继承其他类
②:不能是使用Thread类的方法
③: 可以获取线程的返回值
线程安全问题
数据共享问题:
package com.hanker2;
public class MyThread1 extends Thread{
private int ticket = 5;//总票数
public MyThread1(String name) {
super(name);
}
@Override
public void run() {
String name = getName();
for(int i=0; i<5; i++)
System.out.println(name+"成功卖票一张,剩余:"+ --ticket);
}
}
//======================================
package com.hanker2;
public class MyThread1Test {
public static void main(String[] args) {
MyThread1 t1 = new MyThread1("迪丽热巴");
MyThread1 t2 = new MyThread1("古力娜扎");
MyThread1 t3 = new MyThread1("欧阳娜娜");
//线程执行的顺序和start启动顺序不一定一致的;start只说明线程可以运行
//具体运行那个线程看操作系统的调度
t1.start();
t2.start();
t3.start();
}
}
以上代码卖了15张票, 没有实现数据共享, 创建了三个独立的线程,但是创建三个对象,每个对象都有5张票.这是不符合业务的问题.可以通过实现Runnable接口解决问题.
package com.hanker2;
public class MyThread2 implements Runnable{
private int ticket = 5;//总票数
@Override
public void run() {
String name = Thread.currentThread().getName();
for(int i=0; i<5; i++) {
if(ticket >0) {
System.out.println(name+"成功卖票一张,剩余:"+ --ticket);
}
}
}
}
//========================================
package com.hanker2;
public class MyThread2Test {
public static void main(String[] args) {
//只创建一个目标对象
MyThread2 myThread = new MyThread2();
//创建三个线程
Thread t1 = new Thread(myThread,"迪丽热巴");
Thread t2 = new Thread(myThread,"古力娜扎");
Thread t3 = new Thread(myThread,"欧阳娜娜");
//线程执行的顺序和start启动顺序不一定一致的;start只说明线程可以运行
//具体运行那个线程看操作系统的调度
t1.start();
t2.start();
t3.start();
}
}
出现线程安全问题:
如何解决线程安全问题:
synchronized关键字, Lock接口及其实现类
synchronized: 同步
①同步方法
就是给该方法加上同步锁, 在一个时间点只能有一个线程访问.其他线程进不来. 整个方法就是单线程的, 这样就保证了线程的安全问题.
package com.hanker2;
public class MyThread3 implements Runnable{
private int ticket = 5;//总票数
@Override
public synchronized void run() {
String name = Thread.currentThread().getName();
for(int i=0; i<5; i++) {
if(ticket >0) {
System.out.println(name+"成功卖票一张,剩余:"+ --ticket);
}
}
}
}
//==============================
package com.hanker2;
public class MyThread3Test {
public static void main(String[] args) {
//只创建一个目标对象
MyThread3 myThread = new MyThread3();
//创建三个线程
Thread t1 = new Thread(myThread,"迪丽热巴");
Thread t2 = new Thread(myThread,"古力娜扎");
Thread t3 = new Thread(myThread,"欧阳娜娜");
//线程执行的顺序和start启动顺序不一定一致的;start只说明线程可以运行
//具体运行那个线程看操作系统的调度
t3.start();
t2.start();
t1.start();
}
}
同步方法虽然可以解决线程安全问题,但是整个方法都是单线程的; 会有效率问题. 真实业务我们最好只对可能出现线程安全的代码加锁, 而不是整个方法. 整个方法的粒度是相对比较大的. 所以就引入第二种方式,同步代码块. 顾明思议只对容易出现线程安全的局部的代码加锁.这样会相对的提高代码的执行效率.
②同步代码块
package com.hanker2;
//2. 同步代码块 锁是this当前对象, 其实任何对象都可以作为锁对象
public class MyThread4 implements Runnable{
private int ticket = 5;//总票数
@Override
public void run() {
String name = Thread.currentThread().getName();
for(int i=0; i<5; i++) {
synchronized(this) {
if(ticket >0) {
System.out.println(name+"成功卖票一张,剩余:"+ --ticket);
}
}
}
}
}
//================================
package com.hanker2;
public class MyThread4Test {
public static void main(String[] args) {
//只创建一个目标对象
MyThread4 myThread = new MyThread4();
//创建三个线程
Thread t1 = new Thread(myThread,"迪丽热巴");
Thread t2 = new Thread(myThread,"古力娜扎");
Thread t3 = new Thread(myThread,"欧阳娜娜");
//线程执行的顺序和start启动顺序不一定一致的;start只说明线程可以运行
//具体运行那个线程看操作系统的调度
t3.start();
t2.start();
t1.start();
}
}
执行效果:
synchronized 对象锁和class锁
Lock接口及其实现类
lock: 锁
package com.hanker2;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
//3. 使用lock解决线程安全问题
public class MyThread5 implements Runnable{
private int ticket = 5;//总票数
private Lock lock = new ReentrantLock();//可重入锁
@Override
public void run() {
String name = Thread.currentThread().getName();
for(int i=0; i<5; i++) {
try {
lock.lock();//加锁
if(ticket >0) {
System.out.println(name+"成功卖票一张,剩余:"+ --ticket);
}
}finally {
lock.unlock();//解锁
}
}
}
}
//=========================================
package com.hanker2;
public class MyThread5Test {
public static void main(String[] args) {
//只创建一个目标对象
MyThread5 myThread = new MyThread5();
//创建三个线程
Thread t1 = new Thread(myThread,"迪丽热巴");
Thread t2 = new Thread(myThread,"古力娜扎");
Thread t3 = new Thread(myThread,"欧阳娜娜");
//线程执行的顺序和start启动顺序不一定一致的;start只说明线程可以运行
//具体运行那个线程看操作系统的调度
t3.start();
t2.start();
t1.start();
}
}
线程的常用方法
sleep() //休眠
currentThread() //获取当前线程
getId() //获取线程的Id号
getName()//获取线程的名字
getPriority() //返回此线程的优先级。
getState() //返回此线程的状态。
getThreadGroup() //返回此线程所属的线程组。
interrupt() //中断这个线程。
interrupted() //测试当前线程是否中断。
isAlive() //测试这个线程是否活着。
isDaemon() //测试这个线程是否是守护线程。
isInterrupted() //测试这个线程是否被中断。
join() //等待这个线程死亡。
setDaemon(boolean on) //将此线程标记为 daemon线程或用户线程。
setName(String name) //线程的名称更改为等于参数 name 。
setPriority(int newPriority) //此线程的优先级。
sleep(long millis) //休眠指定时间
start() //此线程开始执行; Java虚拟机调用此线程的run方法。
yield() //对调度程序的一个暗示,即当前线程愿意产生当前使用的处理器。 让给其他线程执行
线程的优先级
进程的优先级:
/**
* The minimum priority that a thread can have. 最低
*/
public final static int MIN_PRIORITY = 1;
/**
* The default priority that is assigned to a thread. 正常
*/
public final static int NORM_PRIORITY = 5;
/**
* The maximum priority that a thread can have. 最高
*/
public final static int MAX_PRIORITY = 10;
package com.hanker2;
public class MyThread5Test {
public static void main(String[] args) {
//只创建一个目标对象
MyThread5 myThread = new MyThread5();
//创建三个线程
Thread t1 = new Thread(myThread,"迪丽热巴");
Thread t2 = new Thread(myThread,"古力娜扎");
Thread t3 = new Thread(myThread,"欧阳娜娜");
//16进制 0--9 A(10) B(11) C(12) D(13) E(14) F(15)
t1.setPriority(Thread.MAX_PRIORITY);
t2.setPriority(2);
t3.setPriority(Thread.MIN_PRIORITY);
//线程执行的顺序和start启动顺序不一定一致的;start只说明线程可以运行
//具体运行那个线程看操作系统的调度
t2.start();
t1.start();
t3.start();
}
}
守护线程
只要有前台线程执行,守护线程会一直执行, 如果没有前端线程,守护立即结束.
package com.hanker3;
public class MyThread1 extends Thread {
@Override
public void run() {
System.out.println("子线程开始执行..."+getName());
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("子线程结束执行...id="+getId()+"优先级:"+getPriority());
}
}
//=======================
package com.hanker3;
public class MyThread1Test {
public static void main(String[] args) {
System.out.println("主 线程开始执行:"+Thread.currentThread().getName());
MyThread1 t1 = new MyThread1();
t1.setDaemon(true);//设置为守护线程
t1.start();
System.out.println("子线程是否存活111:"+t1.isAlive());//true
System.out.println("子线程是否守护线程:"+t1.isDaemon());
try {
Thread.sleep(8000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("子线程是否存活222:"+t1.isAlive());//false
System.out.println("主线程结束执行:"+Thread.currentThread().getState());
}
}
join 加入
如果主线程想要获取子线程的返回值, 主线程就不能先结束; 所以要等待子线程先结束,再获取返回值.
package com.hanker3;
public class MyThread2 implements Runnable {
private Integer num1;
private Integer num2;
public MyThread2(Integer num1, Integer num2) {
super();
this.num1 = num1;
this.num2 = num2;
}
public void run() {
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
int result = num1 + num2;
System.out.println("子线程====="+result);
}
}
//==========================
package com.hanker3;
public class MyThread2Test {
//主线程先结束执行, 子线程还在计算, 如何等待子线程结束
public static void main(String[] args) throws Exception {
System.out.println("主线程开始执行===========");
MyThread2 t2 = new MyThread2(100,300);
Thread myThread = new Thread(t2);//匿名对象
myThread.start();//启动线程
myThread.join();
System.out.println("主线程结束执行===========");
}
}
线程的让步yield
package com.hanker3;
public class MyThreadA extends Thread {
public MyThreadA(String name) {
super(name);
}
@Override
public void run() {
for(int i=0; i<10; i++) {
System.out.println("子线程:"+getName()+",i="+i);
if(i == 5) {
yield();//线程让步
}
}
}
}
//==============================
package com.hanker3;
public class MyThreadB extends Thread {
public MyThreadB(String name) {
super(name);
}
@Override
public void run() {
for(int i=0; i<10; i++) {
System.out.println("子线程:"+getName()+",i="+i);
if(i == 8) {
yield();//线程让步
}
}
}
}
//===================================
package com.hanker3;
public class MyThreadABTest {
public static void main(String[] args) {
MyThreadA ta = new MyThreadA("线程A");
MyThreadB tb = new MyThreadB("线程B");
ta.start();
tb.start();
}
}
线程的状态
线程的状态是线程又开始执行到死亡的过程. 主要包括5个状态, 这5个状态都在Thread类中定义:
public enum State {
NEW, //新建
RUNNABLE,//就绪
BLOCKED,//阻塞
WAITING,//等待
TIMED_WAITING,//计时等待
TERMINATED;//结束
}
package com.hanker3;
public class MyThreadState extends Thread {
@Override
public void run() {
System.out.println("状态2: "+ getState());
}
}
//===========================
package com.hanker3;
public class MyThreadStateTest {
public static void main(String[] args) {
MyThreadState myThread = new MyThreadState();
System.out.println("状态1: " + myThread.getState());
myThread.start();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("状态3:" + myThread.getState());
}
}
等待和计时等待
package com.hanker3;
public class MyThreadStateWait extends Thread {
@Override
public void run() {
synchronized (this) {
try {
//wait(); //线程状态:WAITING
wait(5000);//线程状态:TIMED_WAITING
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
//=====================================
package com.hanker3;
public class MyThreadStateWaitTest {
public static void main(String[] args) {
MyThreadStateWait myThread = new MyThreadStateWait();
myThread.start();
try {
Thread.sleep(2000);//等待子线程开始执行
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程状态:" + myThread.getState());
}
}
blocked阻塞状态
package com.hanker3;
/**
* 模拟业务操作
*/
public class EmpService {
//添加员工信息
public synchronized void addEmp() {
System.out.println("开始添加员工信息...."+System.currentTimeMillis());
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("结束添加员工信息...."+System.currentTimeMillis());
}
}
//=======================================
package com.hanker3;
//线程1
class EmpThread1 extends Thread{
private EmpService empService;
public EmpThread1(EmpService empService,String name) {
super(name);
this.empService = empService;
}
@Override
public void run() {
System.out.println(getName());
empService.addEmp();
}
}
//线程2
class EmpThread2 extends Thread{
private EmpService empService;
public EmpThread2(EmpService empService,String name) {
super(name);
this.empService = empService;
}
@Override
public void run() {
System.out.println(getName());
empService.addEmp();
}
}
public class EmpServiceTest {
public static void main(String[] args) throws Exception {
System.out.println("主线程开始执行....");
EmpService empService = new EmpService();
EmpThread1 t1 = new EmpThread1(empService,"线程1");
EmpThread1 t2 = new EmpThread1(empService,"线程2");
t1.start();
Thread.sleep(2000);
t2.start();
Thread.sleep(1000);
System.out.println("线程2的状态:"+t2.getState());
System.out.println("主线程结束执行....");
}
}
volatile关键字
要想理解volatile关键字,得先了解下JAVA的内存模型,Java内存模型的抽象示意图如下:
从图中可以看出:
①每个线程都有一个自己的本地内存空间–线程栈空间???线程执行时,先把变量从主内存读取到线程自己的本地内存空间,然后再对该变量进行操作
②对该变量操作完后,在某个时间再把变量刷新回主内存
因此,就存在内存可见性问题,看一个示例程序:
public class RunThread extends Thread {
private boolean isRunning = true;
public boolean isRunning() {
return isRunning;
}
public void setRunning(boolean isRunning) {
this.isRunning = isRunning;
}
@Override
public void run() {
System.out.println("进入到run方法中了");
while (isRunning == true) {
}
System.out.println("线程执行完成了");
}
}
public class Run {
public static void main(String[] args) {
try {
RunThread thread = new RunThread();
thread.start();
Thread.sleep(1000);
thread.setRunning(false);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
Run.java 第28行,main线程 将启动的线程RunThread中的共享变量设置为false,从而想让RunThread.java 第14行中的while循环结束。如果,我们使用JVM -server参数执行该程序时,RunThread线程并不会终止!从而出现了死循环!!
原因分析:
现在有两个线程,一个是main线程,另一个是RunThread。它们都试图修改 第三行的 isRunning变量。按照JVM内存模型,main线程将isRunning读取到本地线程内存空间,修改后,再刷新回主内存。而在JVM 设置成 -server模式运行程序时,线程会一直在私有堆栈中读取isRunning变量。因此,RunThread线程无法读到main线程改变的isRunning变量从而出现了死循环,导致RunThread无法终止。**这种情形,在《Effective JAVA》中,将之称为“活性失败”**解决方法,在第三行代码处用 volatile 关键字修饰即可。这里,它强制线程从主内存中取 volatile修饰的变量。
volatile private boolean isRunning = true;
扩展一下,当多个线程之间需要根据某个条件确定 哪个线程可以执行时,要确保这个条件在 线程 之间是可见的。因此,可以用volatile修饰。综上,volatile关键字的作用是:使变量在多个线程间可见(可见性)****二,volatile关键字的非原子性所谓原子性,就是某系列的操作步骤要么全部执行,要么都不执行。比如,变量的自增操作 i++,分三个步骤:
①从内存中读取出变量 i 的值
②将 i 的值加1
③将 加1 后的值写回内存
这说明 i++ 并不是一个原子操作。因为,它分成了三步,有可能当某个线程执行到了第②时被中断了,那么就意味着只执行了其中的两个步骤,没有全部执行。关于volatile的非原子性,看个示例:
public class MyThread extends Thread {
public volatile static int count;
private static void addCount() {
for (int i = 0; i < 100; i++) {
count++;
}
System.out.println("count=" + count);
}
@Override
public void run() {
addCount();
}
}
public class Run {
public static void main(String[] args) {
MyThread[] mythreadArray = new MyThread[100];
for (int i = 0; i < 100; i++) {
mythreadArray[i] = new MyThread();
}
for (int i = 0; i < 100; i++) {
mythreadArray[i].start();
}
}
}
MyThread类第2行,count变量使用volatile修饰
Run.java 第20行 for循环中创建了100个线程,第25行将这100个线程启动去执行 addCount(),每个线程执行100次加1
期望的正确的结果应该是 100*100=10000,但是,实际上count并没有达到10000
原因是:volatile修饰的变量并不保证对它的操作(自增)具有原子性。(对于自增操作,可以使用JAVA的原子类AutoicInteger类保证原子自增)
比如,假设 i 自增到 5,线程A从主内存中读取i,值为5,将它存储到自己的线程空间中,执行加1操作,值为6。此时,CPU切换到线程B执行,从主从内存中读取变量i的值。由于线程A还没有来得及将加1后的结果写回到主内存,线程B就已经从主内存中读取了i,因此,线程B读到的变量 i 值还是5
相当于线程B读取的是已经过时的数据了,从而导致线程不安全性。**这种情形在《Effective JAVA》中称之为“安全性失败”**综上,*仅靠volatile不能保证线程的安全性。(原子性)
三,volatile 与 synchronized 的比较
volatile主要用在多个线程感知实例变量被更改了场合,从而使得各个线程获得最新的值。它强制线程每次从主内存中讲到变量,而不是从线程的私有内存中读取变量,从而保证了数据的可见性。关于synchronized,可参考:JAVA多线程之Synchronized关键字–对象锁的特点
比较:
①volatile轻量级,只能修饰变量。synchronized重量级,还可修饰方法
②volatile只能保证数据的可见性,不能用来同步,因为多个线程并发访问volatile修饰的变量不会阻塞。
synchronized不仅保证可见性,而且还保证原子性,因为,只有获得了锁的线程才能进入临界区,从而保证临界区中的所有语句都全部执行。多个线程争抢synchronized锁对象时,会出现阻塞。
四,线程安全性
线程安全性包括两个方面,①可见性。②原子性。
从上面自增的例子中可以看出:仅仅使用volatile并不能保证线程安全性。而synchronized则可实现线程的安全性。
等待唤醒机制
在多线程交互的时候用到等待唤醒机制, Object类, wait , wait( 时间 ) , notify , notifyAll 通知所有的线程.等待唤醒都要在同步代码中, 必须有锁.; 如果没有锁,代码可以正常执行,不需要等待,不用等待就不用唤醒.
只通知一个线程:
package com.hanker4;
public class ThreadWait extends Thread {
private Object lock ;
public ThreadWait(String name,Object lock) {
super(name);
this.lock = lock;
}
@Override
public void run() {
System.out.println(getName()+"开始等待:"+System.currentTimeMillis());
synchronized (lock) {
try {
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(getName()+"结束等待:"+System.currentTimeMillis());
}
}
//=================================================
package com.hanker4;
public class ThreadNotify extends Thread {
private Object lock;
public ThreadNotify(String name,Object lock) {
super(name);
this.lock = lock;
}
@Override
public void run() {
System.out.println(getName()+"开始通知:"+System.currentTimeMillis());
synchronized (lock) {
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
lock.notify();//通知等待的线程
}
System.out.println(getName()+"结束结束:"+System.currentTimeMillis());
}
}
//================================
package com.hanker4;
public class ThreadTest {
public static void main(String[] args) throws InterruptedException {
Object lock = new Object();
ThreadWait wait = new ThreadWait("等待线程",lock);
wait.start();
ThreadNotify notify=new ThreadNotify("唤醒线程",lock);
notify.start();
}
}
通知多个线程:
//等待线程
package com.hanker4;
public class ThreadWait extends Thread {
private Object lock ;
public ThreadWait(String name,Object lock) {
super(name);
this.lock = lock;
}
@Override
public void run() {
System.out.println(getName()+"开始等待:"+System.currentTimeMillis());
synchronized (lock) {
try {
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(getName()+"结束等待:"+System.currentTimeMillis());
}
}
//==============通知线程=========
package com.hanker4;
public class ThreadNotifyAll extends Thread {
private Object lock;
public ThreadNotifyAll(String name,Object lock) {
super(name);
this.lock = lock;
}
@Override
public void run() {
System.out.println(getName()+"开始通知:"+System.currentTimeMillis());
synchronized (lock) {
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
lock.notifyAll();//通知等待的线程
}
System.out.println(getName()+"结束结束:"+System.currentTimeMillis());
}
}
//============测试类=============
package com.hanker4;
public class ThreadTestNotifyAll {
public static void main(String[] args) throws InterruptedException {
Object lock = new Object();
ThreadWait wait = new ThreadWait("等待线程-1",lock);
wait.start();
ThreadWait wait2 = new ThreadWait("等待线程-2",lock);
wait2.start();
ThreadWait wait3 = new ThreadWait("等待线程-3",lock);
wait3.start();
ThreadNotifyAll notifyThread = new ThreadNotifyAll("通知所有线程",lock);
notifyThread.start();
}
private static void test1() {
Object lock = new Object();
ThreadWait wait = new ThreadWait("等待线程-1",lock);
wait.start();
ThreadWait wait2 = new ThreadWait("等待线程-2",lock);
wait2.start();
ThreadWait wait3 = new ThreadWait("等待线程-3",lock);
wait3.start();
//普通notify, 从执行效果可以看出通知是随机
ThreadNotify notify=new ThreadNotify("唤醒线程",lock);
notify.start();
/**
* 等待线程-2开始等待:1577758057211
等待线程-1开始等待:1577758057211
等待线程-3开始等待:1577758057212
唤醒线程开始通知:1577758057212
等待线程-1结束等待:1577758060212
唤醒线程结束结束:1577758060212
*/
}
}
执行结果:
死锁: 背景是两个线程,分别想获取对方的锁, 独木桥, 大家都不让路. 倚天剑和屠龙刀, 屠龙刀里面有武穆遗书 ; 倚天剑有九阴真经; 要想获取这两个秘籍要互相砍, 出现嵌套锁
案例:
package com.hanker4;
//存钱线程
class ThreadSave extends Thread{
private Account account;
public ThreadSave(Account account,String name) {
super(name);
this.account = account;
}
@Override
public void run() {
System.out.println(getName()+"开始执行");
//调用存钱方法
int money = account.save(100);
System.out.println("存钱成功: "+money);
System.out.println(getName()+"结束执行");
}
}
//取钱线程
class ThreadTake extends Thread{
private Account account;
public ThreadTake(Account account,String name) {
super(name);
this.account = account;
}
public void run() {
System.out.println(getName()+"开始执行");
//调用取钱方法
int money = account.take(200);
System.out.println("取钱成功: "+money);
System.out.println(getName()+"结束执行");
}
}
public class Account {
private int balance = 1000;//余额
private Object lock1 = new Object();//第一把锁
private Object lock2 = new Object();//第二把锁
//存钱
public int save(int money) {
//保险柜有两个门
synchronized(lock1) {
System.out.println("存钱进入第一个门");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized(lock2) {
System.out.println("存钱进入第二个门");
balance += money;
}
}
return balance;
}
//取钱
public int take(int money) {
synchronized(lock2) {
System.out.println("取钱进入第二个门");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (lock1) {
System.out.println("取钱进入第一个门");
balance -= money;
}
}
return balance;
}
}
//=========================
package com.hanker4;
public class AccountTest {
public static void main(String[] args) {
Account account = new Account();
ThreadSave ts = new ThreadSave(account,"存钱线程");
ThreadTake tt =new ThreadTake(account,"取钱线程");
ts.start();
tt.start();
}
}
执行效果:
如何解决线程死锁的问题: 保证锁的顺序一致,
![19](images/19.jpg)//取钱线程也是从锁1开始
public int take(int money) {
synchronized(lock1) {
System.out.println("取钱进入第二个门");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (lock2) {
System.out.println("取钱进入第一个门");
balance -= money;
}
}
return balance;
}
生产者和消费者机制
厨师负责做菜, 服务员负责上菜, 厨师做好菜放在窗口上, 服务员从窗口取菜. 如果窗口没有菜服务员要等待. 厨师做好菜要通知服务员.
案例:
package com.hanker5;
class ThreadSet implements Runnable{
private Resource r;
public ThreadSet(Resource r) {
this.r = r;
}
@Override
public void run() {
while(true) {
try {
r.set("酸菜鱼");
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
class ThreadGet implements Runnable{
private Resource r;
public ThreadGet(Resource r) {
this.r = r;
}
@Override
public void run() {
while(true) {
try {
r.get();
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
//表示资源类
public class Resource {
private String [] data = new String[1];//操作的空间(窗口)
private int count;//计数器
private Object lock = new Object();
//生产方法
public void set(String name) throws Exception {
synchronized (lock) {
//窗口是否为空
if(data[0]!=null) {
lock.wait();//等待
}
data[0] = name + count;//生产数据
System.out.println("生产者产生数据:" + data[0]);
count++;
Thread.sleep(1000);
lock.notify();
}
}
//消费方法
public void get() throws Exception {
synchronized (lock) {
if(data[0] == null) {
lock.wait();//没有数据,等待
}
System.out.println("消费者消费数据:" + data[0]);
data[0] = null;//置为空,
count++;
Thread.sleep(1000);
lock.notify();//通知生产者线程
}
}
}
//========================
package com.hanker5;
public class Test1 {
public static void main(String[] args) {
Resource r = new Resource();
ThreadSet ts = new ThreadSet(r);
ThreadGet tg = new ThreadGet(r);
new Thread(ts).start();
new Thread(tg).start();
}
}
执行效果:
多个生产者和多个消费者线程
package com.hanker6;
class ThreadSet implements Runnable{
private Resource r;
public ThreadSet(Resource r) {
this.r = r;
}
@Override
public void run() {
while(true) {
try {
r.set("酸菜鱼");
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
class ThreadGet implements Runnable{
private Resource r;
public ThreadGet(Resource r) {
this.r = r;
}
@Override
public void run() {
while(true) {
try {
r.get();
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
//表示资源类
public class Resource {
private String [] data = new String[1];//操作的空间(窗口)
private int count;//计数器
private Object lock = new Object();
//生产方法
public void set(String name) throws Exception {
synchronized (lock) {
//窗口是否为空
if(data[0]!=null) {
lock.wait();//等待
}
data[0] = name + count;//生产数据
String tname = Thread.currentThread().getName();
System.out.println(tname+"产生数据:" + data[0]);
count++;
Thread.sleep(1000);
lock.notifyAll();//通知所有等待线程
}
}
//消费方法
public void get() throws Exception {
synchronized (lock) {
if(data[0] == null) {
lock.wait();//没有数据,等待
}
String tname = Thread.currentThread().getName();
System.out.println(tname+"消费数据:" + data[0]);
data[0] = null;//置为空,
Thread.sleep(1000);
lock.notifyAll();//通知所有等待线程
}
}
}
//===========================
package com.hanker6;
//多个生成者,多个消费者
public class Test1 {
public static void main(String[] args) {
Resource r = new Resource();
ThreadSet ts = new ThreadSet(r);
ThreadGet tg = new ThreadGet(r);
new Thread(ts,"生产者线程1").start();
new Thread(ts,"生产者线程2").start();
new Thread(tg,"消费者线程1").start();
new Thread(tg,"消费者线程2").start();
}
}
执行效果:
从中可以看出问题: 生产者生成一个数据, 结果通知两个消费者去消费,所以第二个消费者为空.
解决办法一: 追加自旋锁
package com.hanker7;
class ThreadSet implements Runnable{
private Resource r;
public ThreadSet(Resource r) {
this.r = r;
}
@Override
public void run() {
while(true) {
try {
r.set("酸菜鱼");
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
class ThreadGet implements Runnable{
private Resource r;
public ThreadGet(Resource r) {
this.r = r;
}
@Override
public void run() {
while(true) {
try {
r.get();
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
//表示资源类
public class Resource {
private String [] data = new String[1];//操作的空间(窗口)
private int count;//计数器
private Object lock = new Object();
//生产方法
public void set(String name) throws Exception {
synchronized (lock) {
//窗口是否为空
while(data[0]!=null) {
lock.wait();//等待
}
data[0] = name + count;//生产数据
String tname = Thread.currentThread().getName();
System.out.println(tname+"产生数据:" + data[0]);
count++;
Thread.sleep(1000);
lock.notifyAll();//通知所有等待线程
}
}
//消费方法
public void get() throws Exception {
synchronized (lock) {
while(data[0] == null) {
lock.wait();//没有数据,等待
}
String tname = Thread.currentThread().getName();
System.out.println(tname+"消费数据:" + data[0]);
data[0] = null;//置为空,
Thread.sleep(1000);
lock.notifyAll();//通知所有等待线程
}
}
}
//=====================
package com.hanker7;
//多个生成者,多个消费者
//通过自旋锁解决多次消费同一个数据的问题
public class Test1 {
public static void main(String[] args) {
Resource r = new Resource();
ThreadSet ts = new ThreadSet(r);
ThreadGet tg = new ThreadGet(r);
new Thread(ts,"生产者线程1").start();
new Thread(ts,"生产者线程2").start();
new Thread(tg,"消费者线程1").start();
new Thread(tg,"消费者线程2").start();
}
}
执行效果:
解决办法二: 可重入锁
使用Condition条件通知, 理想办法是:生产者唤醒消费者, 消费者线程唤醒生产者.
ReentrantLock(重入锁):效果synchronized一样,都可以同步执行,lock方法获得锁,unlock方法释放锁
Condition类的awiat方法和Object类的wait方法等效
Condition类的signal方法和Object类的notify方法等效
Condition类的signalAll方法和Object类的notifyAll方法等效
synchronized是可以实现加锁功能;但是语义不明确,并没有看到上锁和开锁的代码,jdk1.5新增加Lock接口及其实现类.
读锁: 只是读取数据,不会引发并发异常, 可以多线程执行,
写锁: 写数据, 如果多线程执行有可能会引发异常, 两个写锁是互斥的,同时只能执行一个上锁的代码块.
package com.hanker8;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
class Producer implements Runnable{
private Resource r;
public Producer(Resource r) {
this.r = r;
}
@Override
public void run() {
while(true) {
try {
r.set("酸菜鱼");
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
class Consumer implements Runnable{
private Resource r;
public Consumer(Resource r) {
this.r = r;
}
@Override
public void run() {
while(true) {
try {
r.get();
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
//表示资源类
public class Resource {
private String [] data = new String[1];//操作的空间(窗口)
private int count;//计数器
private Lock lock = new ReentrantLock();//可重入锁
private Condition con_producer = lock.newCondition();//生成者通知
private Condition con_consumer = lock.newCondition();//消费者通知
//生产方法
public void set(String name) throws Exception {
lock.lock();//上锁
try {
//窗口是否为空
while(data[0]!=null) {
con_producer.await();//等待
}
data[0] = name + count;//生产数据
String tname = Thread.currentThread().getName();
System.out.println(tname+"产生数据:" + data[0]);
count++;
Thread.sleep(1000);
con_consumer.signalAll();//通知消费者线程
}finally {
lock.unlock();
}
}
//消费方法
public void get() throws Exception {
lock.lock();//上锁
try{
while(data[0] == null) {
con_consumer.await();//没有数据,等待
}
String tname = Thread.currentThread().getName();
System.out.println(tname+"消费数据:" + data[0]);
data[0] = null;//置为空,
Thread.sleep(1000);
con_producer.signalAll();//通知所有生产者线程
}finally {
lock.unlock();
}
}
}
//=================================
package com.hanker8;
//多个生成者,多个消费者
//通过可重入锁解决多次消费同一个数据的问题
public class Test1 {
public static void main(String[] args) {
Resource r = new Resource();
Producer ts = new Producer(r);
Consumer tg = new Consumer(r);
new Thread(ts,"生产者线程1").start();
new Thread(ts,"生产者线程2").start();
new Thread(tg,"消费者线程1").start();
new Thread(tg,"消费者线程2").start();
}
}
执行效果:
线程池
我们使用线程的时候就去创建一个线程,这样实现起来非常简便,但是就会有一个问题:
如果并发的线程数量很多,并且每个线程都是执行一个时间很短的任务就结束了,这样频繁创建线程就会大大降低系统的效率,因为频繁创建线程和销毁线程需要时间。那么有没有一种办法使得线程可以复用,就是执行完一个任务,并不被销毁,而是可以继续执行其他的任务?在Java中可以通过线程池来达到这样的效果。今天我们就来详细讲解一下Java的线程池,首先我们从最核心的ThreadPoolExecutor类中的方法讲起,然后再讲述它的实现原理,接着给出了它的使用示例,最后讨论了一下如何合理配置线程池的大小。
公交公司一般都会为一路车准备多辆汽车;
线程池的分类
所有实现了ExecutorService接口(Executor的子接口)的实现类都是线程池,可以分为三大类
- ForkJoinPool
- ScheduledThreadPoolExecutor
- ThreadPoolExecutor
具体的线程池,在工具类Executors中预创建了六小类
实现了ThreadPoolExecutor类:
- ExecutorService newCachedThreadPool():无界线程池
- ExecutorService newFixedThreadPool():有界线程池
- ExecutorService newSingleThreadExecutor():单一线程池
实现了ScheduledThreadPoolExecutor类:
- ScheduledExecutorService newSingleThreadScheduledExecutor()
- ScheduledExecutorService newScheduledThreadPool(int corePoolSize)
实现了ForkJoinPool类:
- ExecutorService newWorkStealingPool()
当然我们也可以自定义线程池
常用线程池(ThreadPoolExecutor)示例
现在有一个任务WorkTask
package com.hanker9;
public class WorkTask implements Runnable {
@Override
public void run() {
int random =(int) (Math.random()*10);
try {
Thread.sleep(random * 1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"结束执行 ..");
}
}
//========================
package com.hanker9;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class CachedThreadPoolTest {
public static void main(String[] args) {
//创建一个无界线程池
ExecutorService pool = Executors.newCachedThreadPool();
System.out.println(pool.getClass().getName());
//向线程池添加线程
for(int i=0; i<20; i++) {
pool.execute(new WorkTask());
}
//关闭线程池
pool.shutdown();
}
}
无界线程池,最多可创建Integer.MAX_VALUE个线程,运行结果没有重复的线程号.
线程池的添加过程:
//1.线程池的实现类:
java.util.concurrent.ThreadPoolExecutor
pool.execute(new WorkTask()); //添加线程,调用下面方法
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();
if (! isRunning(recheck) && remove(command))
reject(command);
else if (workerCountOf(recheck) == 0)
addWorker(null, false);
}
else if (!addWorker(command, false))
reject(command);
}
addWork方法
private boolean addWorker(Runnable firstTask, boolean core) {
retry:
for (;;) {
int c = ctl.get();
int rs = runStateOf(c);
// Check if queue empty only if necessary.
if (rs >= SHUTDOWN &&
! (rs == SHUTDOWN &&
firstTask == null &&
! workQueue.isEmpty()))
return false;
for (;;) {
int wc = workerCountOf(c);
if (wc >= CAPACITY ||
wc >= (core ? corePoolSize : maximumPoolSize))
return false;
if (compareAndIncrementWorkerCount(c))
break retry;
c = ctl.get(); // Re-read ctl
if (runStateOf(c) != rs)
continue retry;
// else CAS failed due to workerCount change; retry inner loop
}
}
boolean workerStarted = false;
boolean workerAdded = false;
Worker w = null; //线程被封装成Worker对象
try {
w = new Worker(firstTask);
final Thread t = w.thread;
if (t != null) {
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
// Recheck while holding lock.
// Back out on ThreadFactory failure or if
// shut down before lock acquired.
int rs = runStateOf(ctl.get());
if (rs < SHUTDOWN ||
(rs == SHUTDOWN && firstTask == null)) {
if (t.isAlive()) // precheck that t is startable
throw new IllegalThreadStateException();
workers.add(w); //关键代码: workers是什么? 可以推断是集合
int s = workers.size();
if (s > largestPoolSize)
largestPoolSize = s;
workerAdded = true;
}
} finally {
mainLock.unlock();
}
if (workerAdded) {
t.start();
workerStarted = true;
}
}
} finally {
if (! workerStarted)
addWorkerFailed(w);
}
return workerStarted;
}
workers的定义:
/**
* Set containing all worker threads in pool. Accessed only when
* holding mainLock.
*/
private final HashSet<Worker> workers = new HashSet<Worker>();
创建有界线程池:
public class FixedThreadPoolTest {
public static void main(String[] args) {
//创建一个有界线程池
ExecutorService exec = Executors.newFixedThreadPool(3);//查看源码
for(int i=0;i<20;i++){
exec.execute(new WorkTask());
}
exec.shutdown();
}
}
3个线程执行20个任务
源码分析: Executors.newFixedThreadPool(3)调用如下方法
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, //3
nThreads, //3
0L, //0
TimeUnit.MILLISECONDS,//时间单位: 毫秒
new LinkedBlockingQueue<Runnable>()//阻塞链表队列
);
}
跟踪构造方法:
public ThreadPoolExecutor( int corePoolSize, //核心线程池大小=3
int maximumPoolSize, //最大线程池大小=3
long keepAliveTime, //存活时间: 0秒
TimeUnit unit, //时间单位: 毫秒
BlockingQueue<Runnable> workQueue) { //阻塞队列
this(corePoolSize,
maximumPoolSize,
keepAliveTime,
unit,
workQueue,
Executors.defaultThreadFactory(),
defaultHandler);//又调用另一个构造方法
}
继续跟踪构造方法:
public ThreadPoolExecutor( int corePoolSize, //3
int maximumPoolSize,//3
long keepAliveTime,//0
TimeUnit unit,//毫秒
BlockingQueue<Runnable> workQueue,//队列数据结构
ThreadFactory threadFactory,
RejectedExecutionHandler handler) {
if (corePoolSize < 0 ||
maximumPoolSize <= 0 ||
maximumPoolSize < corePoolSize ||
keepAliveTime < 0)
throw new IllegalArgumentException();
if (workQueue == null || threadFactory == null || handler == null)
throw new NullPointerException();
this.corePoolSize = corePoolSize;
this.maximumPoolSize = maximumPoolSize;
this.workQueue = workQueue;
this.keepAliveTime = unit.toNanos(keepAliveTime);
this.threadFactory = threadFactory;
this.handler = handler;
}
SingleThreadExecutor
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class SingleThreadExecutorTest {
public static void main(String[] args) {
ExecutorService exec = Executors.newSingleThreadExecutor();
for(int i=0;i<20;i++){
exec.execute(new WorkTask());
}
exec.shutdown();
}
}
始终1个线程执行所有任务
ThreadPoolExecutor类分析
三种线程池(返回ThreadPoolExecutor)构造方法
以下3种线程池均实现了ThreadPoolExecutor类
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
ThreadPoolExecutor中的成员变量
以上3中线程池都是使用了ThreadPoolExecutor中的一种构造方法:
ThreadPoolExecutor(int corePoolSize, int maximumPoolSize,
long keepAliveTime, TimeUnit unit,
BlockingQueue<Runnable> workQueue)
①int corePoolSize:核心线程数,即使空闲也仍保留在池中的线程数
②int maximumPoolSize:最大线程数
③BlockingQueue workQueue:阻塞队列,在执行任务之前用于保存任务的队列
④long keepAliveTime:保持激活时间,当线程数大于核心数时,这是多余的空闲线程在终止之前等待新任务的最大时间
⑤TimeUnit unit:keepAliveTime的时间单位
核心线程是指线程池一启动的时候分配的线程,只要任务数量<核心线程数的任务都会使用核心线程去执行,并且执行完是不回收的;如果任务数量>核心线程数量,就会进入阻塞队列,入队之后队列里的任务就由非核心线程来执行;非核心线程如果在空闲后的keepAliveTime内还没活就会被回收。
三种线程池(返回ThreadPoolExecutor类)分析
newCachedThreadPool:核心线程数为0,最大线程数为Integer.MAX_VALUE,所以称之为无界线程池;假设一次执行20个任务,由于corePoolSize为0,所以20个任务全会进入阻塞队列BlockingQueue,启动新线程执行队列中的任务,最多可以启动20个任务,如果20个任务都执行完毕,从线程闲置时开始倒计时60s,超时则关闭线程。
FixedThreadPool:因为核心线程数是传入且固定的,所以称为有界线程池,一般在后台执行一些辅助性的任务,最大线程数与核心线程数相等;假设核心线程数为3,一次执行20个任务:先启动3个线程,剩下17个任务会进入BlockingQueue排队;因为核心线程数数=最大线程数,所以keepAliveTime这个参数是没有意义的。
SingleThreadExecutor:线程池中只有1个线程,超过1个任务进入阻塞队列,与FixedThreadPool类似,只不过nThread=1。比如说服务端需要有一个线程不断监听客户端发送来的socket,一旦出现异常会新建一个线程来替换。
我们可以来模拟一下监听任务ListeningTask并出现异常
package com.hanker9;
import javax.management.RuntimeErrorException;
public class ListeningTask implements Runnable{
private int i;
public ListeningTask(int i){
this.i = i;
}
public void run() {
System.out.println("thread"+Thread.currentThread().getId()+" is started...task" + i);
try {
Thread.sleep(1000);
} catch (Exception e) {
}
if(i==3){
throw new RuntimeErrorException(null,"exception...");
}
System.out.println("thread"+Thread.currentThread().getId()+" is over...task"+i);
}
}
主方法
package com.hanker9;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class test {
public static void main(String[] args) {
ExecutorService exec = Executors.newSingleThreadExecutor();
for(int i=0;i<20;i++){
exec.execute(new ListeningTask(i));
}
exec.shutdown();
}
}
运行结果
可见一开始全由thread8来执行,出现异常后程序并没有终止,由另一个线程thread10接管任务,这就是SingleThreadExecutor在监听上的用武之地
execute与submit方法
Executor接口中有一个方法
而其子接口ExecutorService中有3个重载的submit方法
submit是基方法Executor.execute(Runnable)的延伸,通过创建并返回一个Future类对象可用于取消执行和/或等待完成。
Future类中的方法
用submit方法举个例子,获取多线程运行结果
package com.hanker9;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
public class test {
public static void main(String[] args) {
ExecutorService exec = Executors.newCachedThreadPool();
for(int i=0;i<20;i++){
Future<String> f = exec.submit(new TaskResult(i));
try {
String v=f.get();
System.out.println(v);
} catch (Exception e) {
}
}
}
}
class TaskResult implements Callable<String>{
private int i;
public TaskResult(int i){
this.i=i;
}
public String call(){
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
return Thread.currentThread().getId()+"'s result is "+i;
}
}
运行结果,这是execute方法无法实现的
ForkJoinPool
背景:ForkJoinPool的优势在于,可以充分利用多cpu,多核cpu的优势,把一个任务拆分成多个“小任务”,把多个“小任务”放到多个处理器核心上并行执行;当多个“小任务”执行完成之后,再将这些执行结果合并起来即可。这种思想值得学习。
Fork/Join 框架:就是在必要的情况下,将一个大任务,进行拆分(fork)成若干个小任务(拆到不可再拆时),再将一个个的小任务运算的结果进行 join 汇总。
/* @since 1.7
* @author Doug Lea
*/
public abstract class ForkJoinTask<V> implements Future<V>, Serializable {
...
}
-
采用 “工作窃取”模式(work-stealing):当执行新的任务时它可以将其拆分分成更小的任务执行,并将小任务加到线程队列中,然后再从一个随机线程的队列中偷一个并把它放在自己的队列中。
-
相对于一般的线程池实现,fork/join框架的优势体现在对其中包含的任务的处理方式上.在一般的线程池中,如果一个线程正在执行的任务由于某些原因无法继续运行,那么该线程会处于等待状态。而在fork/join框架实现中,如果某个子问题由于等待另外一个子问题的完成而无法继续运行。那么处理该子问题的线程会主动寻找其他尚未运行的子问题来执行.这种方式减少了线程的等待时间,提高了性能。
Java7 提供了ForkJoinPool来支持将一个任务拆分成多个“小任务”并行计算,再把多个“小任务”的结果合并成总的计算结果。ForkJoinPool是ExecutorService的实现类,因此是一种特殊的线程池。使用方法:创建了ForkJoinPool实例之后,就可以调用ForkJoinPool的submit(ForkJoinTask task) 或invoke(ForkJoinTask task)方法来执行指定任务了。
其中ForkJoinTask代表一个可以并行、合并的任务。ForkJoinTask是一个抽象类,它还有两个抽象子类:RecusiveAction和RecusiveTask。其中RecusiveTask代表有返回值的任务,而RecusiveAction代表没有返回值的任务。
下面的UML类图显示了ForkJoinPool、ForkJoinTask之间的关系:
案列一:通过多线程分多个小任务进行打印数据 无返回值的
package com.hanker9;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.RecursiveAction;
import java.util.concurrent.TimeUnit;
/**
* RecursiveAction 无返回值的
*/
public class PrintTask extends RecursiveAction {
private static final long serialVersionUID = 1L;
private static final int INDEX = 50;
private int start;
private int end;
public PrintTask(int start, int end) {
this.start = start;
this.end = end;
}
@Override
protected void compute() {
if (end - start < INDEX) {
for (int i = start; i < end; i++) {
System.out.println(Thread.currentThread().getName() + "----"+ i);
}
} else {
int middle = (end + start) / 2;
PrintTask taskLeft = new PrintTask(start, middle);
PrintTask taskRight = new PrintTask(middle, end);
//taskLeft.invoke();执行给定的任务,在完成后返回其结果。
//并行执行两个“小任务”
/*
taskLeft.fork();
taskRight.fork();
*/
invokeAll(taskLeft, taskRight);//执行给定的任务
}
}
public static void main(String[] args) throws InterruptedException {
PrintTask task = new PrintTask(0, 300);
ForkJoinPool pool = new ForkJoinPool();
pool.submit(task);
pool.awaitTermination(2, TimeUnit.SECONDS);//阻塞2秒
pool.shutdown();
}
}
运行结果
本机电脑cpu4核,通过上面观察线程名称,可以看出4个cpu都在进行
案列二:通过多线程分多个小任务进行数据累加 返回结果集
package com.hanker9;
import java.util.Random;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.Future;
import java.util.concurrent.RecursiveTask;
import java.util.concurrent.TimeUnit;
public class PrintTask2 {
public static void main(String[] args) throws Exception {
int[] arr = new int[200];
Random r = new Random();
int tempSum = 0;// 普通总数
for (int i = 0; i < arr.length; i++) {
tempSum += (arr[i] = r.nextInt(10));
}
System.out.println("普通总数结果为:" + tempSum);
ForkJoinPool pool = new ForkJoinPool();
MyTask task = new MyTask(0, arr.length, arr);
Future<Integer> sum = pool.submit(task);
// get 如果需要,等待计算完成,然后检索其结果。
System.out.println("多线程的执行结果:" + sum.get());
pool.awaitTermination(2, TimeUnit.SECONDS);
pool.shutdown(); // 关闭线程池
}
}
class MyTask extends RecursiveTask<Integer> {
private static final long serialVersionUID = 1L;
private static final int INDEX = 50;// 每个小任务执行50个
private int start;
private int end;
private int arr[];
public MyTask(int start, int end, int[] arr) {
this.start = start;
this.end = end;
this.arr = arr;
}
@Override
protected Integer compute() {
int sum = 0;
if (end - start < INDEX) {
for (int i = start; i < end; i++) {
sum += arr[i];
}
return sum;
} else {
int middle = (end + start) / 2;
MyTask taskLeft2 = new MyTask(start, middle, arr);
MyTask taskRight2 = new MyTask(middle, end, arr);
/*invokeAll(taskLeft2, taskRight2);*/
taskLeft2.fork();
taskRight2.fork();
int leftValue = taskLeft2.join();// 当计算完成时返回计算结果。
int rightValue = taskRight2.join();
return leftValue + rightValue;
}
}
}
运行结果: