Java多线程
1.线程概述
1.线程和进程
-
在一个系统中,每个独立运行的程序是一个进程;每个程序中有多个顺序流,称为线程;
-
进程的特征:
-
独立性:每个进程都有着自己独立的地址空间,互不影响;
-
动态性;
-
并发性:多个进程可以在单个处理器上并发执行,且互不影响;
- 并发性:同一时刻只能有一条指令执行,但是多个进程指令被快速轮换执行,在宏观上看起来是一起执行的效果;
- 并行性:同一时刻,多条指令在多个处理器上同时执行;
- Windows和Linux都是抢占式的多任务操作策略;
- 线程又被称为是轻量级进程,每个线程是独立运行的;一个进程至少包括一个线程;
2.多线程的优势
- 线程之间共享内存、文件句柄和状态信息;这也是线程执行速度高于进程的原因;
- 多线程的应用:
- javaWeb服务器响应多个用户需求;
- JVM虚拟机的垃圾回收机制;
2.线程的创建和启动
1.继承Thread类创建线程类
- 所有的线程对象都必须是Thread类或其子类的实例;每个线程的任务就是执行一段程序流;
- 创建并启动线程的步骤如下:
/*
1.定义Thread类的子类,重写Thread的run方法,run就是线程的执行体;
2.创建Thread子类的实例,即创建了线程对象;
3.调用线程对象的start()方法来启动线程;
*/
//继承Thread
public class FirstThread extends Thread
{
private int i;
// 重写run
@Override
public void run()
{
for ( ; i < 100; i++)
{
// 直接调用getName方法可以返回当前线程的名字
System.out.println(getName() + " " + i);
}
}
public static void main(String[] args)
{
//这是主线程
for (var i = 0; i < 100; i++)
{
// 调用Thread的currentThread方法获得当前线程;
System.out.println(Thread.currentThread().getName()
+ " " + i);
if (i == 20)
{
// 创建并启用第一个线程
new FirstThread().start();
// 创建并启用第二个线程
new FirstThread().start();
}
}
}
}
- main方法体就是主线程的方法体;
2.实现Runable接口创建线程类
-
步骤如下
/*
使用Runable接口创建线程
1.定义Runable接口的实现类,并重写run方法,run是线程的实现体;
2.创建实现类的实例,并以此实例作为Thread的target来创建Thread对象,该对象为线程对象;
3.调用线程对象的start方法启动线程;
*/
//实现Runable接口
public class SecondThread implements Runnable
{
private int i;
//重写run方法
public void run()
{
for ( ; i < 100; i++)
{
//如果想获得线程名,只能通过Thread.currentThread()方法
System.out.println(Thread.currentThread().getName()
+ " " + i);
}
}
public static void main(String[] args)
{
for (var i = 0; i < 100; i++)
{
System.out.println(Thread.currentThread().getName() //主线程
+ " " + i);
if (i == 20)
{
var st = new SecondThread(); //创建线程实例;
//用实例初始化Thread实例,并启动线程;
new Thread(st, "新线程1").start();
new Thread(st, "新线程2").start();
}
}
}
}
3.使用Callable和Future创建线程(739)(这个方法有点复杂)
-
Thread将run方法作为线程执行体,但是不能将其他方法作为线程执行体;(C#可以)
-
Callable接口是Runable的增强版,提供了一个较强的call方法代替了run方法;
4.创建线程的三种方式对比
- 由于扩展性原因,一般推荐采用Runable接口实现线程的编写;
3.线程的生命周期
1.新建和就绪状态
- 线程的生命周期中,包括新建New,就绪Ready,运行Running,阻塞Blocked、死亡Dead共5个状态;
- new一个线程对象,就进入了线程的新建状态;
- 线程对象调用start方法,就进入了就绪状态;处于这个状态的线程并没有开始运行,只是表示该线程可以运行了,何时开始运行取决于JVM线程调度器的调度;
- run方法不能直接执行,启动线程只能是start方法;
- 如果希望子线程点击start方法后立即执行,程序可以使用Thread.sleep(1)来让当前运行的线程睡眠1ms,此时CPU就会去执行另一个已经就绪的线程;
2.运行和阻塞状态
- 线程调度平台会定期的让一些运行的线程进入阻塞状态,让其他线程有机会运行;
- Windows和Linux都是抢占式调度方法;而手机系统采用协作式调度方法,即由当前运行线程自动调用sleep或者yield方法进入阻塞状态;
- 当发生如下状态时,系统进入阻塞状态:
- 线程调用sleep方法主动放弃占用线程资源;
- 线程调用了一个阻塞式IO方法,在该方法返回之前,该线程被进程阻塞;
- 线程试图获得一个同步监视器;
- 线程在等待某个通知notify;
- 程序调用了线程的suspend方法将该线程挂起(此方法容易导致死锁)
- 被阻塞的线程会在解除阻塞后重新进入就绪状态;
3.线程死亡
- 线程会以如下三种方式结束,并进入死亡状态:
- run或者call方法执行完成,线程正常结束;
- 线程抛出一个未捕获的Exception或Error;
- 直接调用stop方法停止线程;
- 主线程和子线程是平级关系,主线程死亡不会影响子线程;
- 为了验证线程是否死亡,可以调用isAlive()方法来验证;
- 已经死亡的线程不能被再次start;
4.控制线程
1.join线程
-
join是让一个线程等待另一个线程的方法,当某个程序执行流过程中调用其他线程的join方法时,该线程将被阻塞,直到被join方法加入的join线程执行完为止;
/*
join方法有如下三种重载形式
1.join():等待被join的线程执行完成;
2.join(long millis):等待被join的线程的执行时间最长为millis毫秒;如果在给定时间内被join的线程还没有执行结束,则不再等待;
3.join(long millis, int nanos):等待被join的线程的时间最长为millis+nanos毫微秒;
*/
public class JoinThread extends Thread
{
// 该构造器用于设置线程名;Thread的有参构造器用于给线程命名
public JoinThread(String name)
{
super(name);
}
//重写run方法
public void run()
{
for (var i = 0; i < 100; i++)
{
System.out.println(getName() + " " + i);
}
}
public static void main(String[] args) throws Exception
{
// 启动线程
new JoinThread("新线程").start();
for (var i = 0; i < 100; i++)
{
if (i == 20)
{
var jt = new JoinThread("被join的线程");
jt.start();
// main线程调用了jt线程的join方法,必须等到jt结束才会向下执行;
jt.join();
}
System.out.println(Thread.currentThread().getName()
+ " " + i);
}
}
}
2.后台线程
- 后台线程Daemon Thread:为其他线程提供服务,又被称为是守护线程、精灵线程;典型的就是JVM垃圾回收;
- 如果所有的前台线程都死亡,后台线程会自动死亡;
- 调用Thread对象的setDaemon(true)方法可以将指定的线程设置为后台线程:
public class DaemonThread extends Thread
{
public void run()
{
for (var i = 0; i < 1000; i++)
{
System.out.println(getName() + " " + i);
}
}
public static void main(String[] args)
{
var t = new DaemonThread();
// 设置为守护进程
t.setDaemon(true);
t.start();
for (var i = 0; i < 10; i++)
{
System.out.println(Thread.currentThread().getName()
+ " " + i);
}
}
}
- Thread类提供了isDaemon方法,用于判定线程是否为后台线程;
3.睡眠线程sleep
- Thread类的sleep方法可以让当前正在执行的线程暂停一段时间,并进入阻塞状态;
- sleep的两种重载形式
- static void sleep(long millis):让当前正在执行的线程暂停millis毫秒;
- static void sleep(long millis, long nanos)
- sleep常用于暂停线程的执行,即使内存中没有其他线程执行,sleep的线程也不会执行;
import java.util.*;
public class SleepTest
{
public static void main(String[] args)
throws Exception
{
for (var i = 0; i < 10; i++)
{
System.out.println("时间为 " + new Date());
// 暂停主线程
Thread.sleep(1000);
}
}
}
- yield方法可以将当前正在执行的线程暂停,但是不会阻塞该线程,它只是将线程转入就绪状态;
4.改变线程优先级
- Thread类提供了setPriority(int newPriority)、getPriority()方法来设置和返回指定线程的优先级;
- Thread类优先级有关的三个静态常量:
- MAX_PRIORITY:10
- MIN_PRIORITY:1
- NORM_PRIORITY:5
public class PriorityTest extends Thread
{
// 给定线程名称
public PriorityTest(String name)
{
super(name);
}
public void run()
{
for (var i = 0; i < 50; i++)
{
System.out.println(getName() + ",其优先级为:"
+ getPriority() + ", 循环变量的值为:" + i);
}
}
public static void main(String[] args)
{
// 设置主线程优先级为6;
Thread.currentThread().setPriority(6);
for (var i = 0; i < 30; i++)
{
if (i == 10)
{
var low = new PriorityTest("低级");
low.start();
System.out.println("创建之初的优先级:"
+ low.getPriority());
// 设置low的优先级为最低;
low.setPriority(Thread.MIN_PRIORITY);
}
if (i == 20)
{
var high = new PriorityTest("高级");
high.start();
System.out.println("创建之初的优先级为:"
+ high.getPriority());
// 设置high的优先级
high.setPriority(Thread.MAX_PRIORITY);
}
}
}
}
5.线程同步
1.线程安全问题
- 定义一个账户取钱程序
public class Account
{
//封装账户编号、账户余额的两个成员变量
private String accountNo;
private double balance;
public Account(){}
// 给定构造体,初始化账户名和余额;
public Account(String accountNo, double balance)
{
this.accountNo = accountNo;
this.balance = balance;
}
public void setAccountNo(String accountNo)
{
this.accountNo = accountNo;
}
public String getAccountNo()
{
return this.accountNo;
}
public void setBalance(double balance)
{
this.balance = balance;
}
public double getBalance()
{
return this.balance;
}
// 重写hashcode和equals方法
public int hashCode()
{
return accountNo.hashCode();
}
public boolean equals(Object obj)
{
if (this == obj)
return true;
if (obj != null
&& obj.getClass() == Account.class)
{
var target = (Account) obj;
return target.getAccountNo().equals(accountNo);
}
return false;
}
}
- 提供一个取钱的线程类
public class DrawThread extends Thread
{
//账户
private Account account;
// 取钱数量
private double drawAmount;
//用户姓名,账户,取钱数
public DrawThread(String name, Account account,
double drawAmount)
{
super(name);
this.account = account;
this.drawAmount = drawAmount;
}
// 线程体
public void run()
{
// 余额大于取钱数
if (account.getBalance() >= drawAmount)
{
// 吐出钞票
System.out.println(getName()
+ "取钱成功" + drawAmount);
try
{
Thread.sleep(1);
}
catch (InterruptedException ex)
{
ex.printStackTrace();
}
account.setBalance(account.getBalance() - drawAmount);
System.out.println("剩余余额为: " + account.getBalance());
}
else
{
System.out.println(getName() + "取钱失败余额不足");
}
}
}
- 启动两个取钱线程
public class DrawTest
{
public static void main(String[] args)
{
// 创建一个Account实例
var acct = new Account("1234567", 1000);
//设置两个取钱线程
new DrawThread("甲", acct, 800).start();
new DrawThread("乙", acct, 800).start();
}
}
2.同步代码块
- 当两个线程访问和修改同一文件时,就有可能导致线程执行错误;
- 为了解决这个问题,Java引入了同步监视器来解决这个问题,使用同步监视器的通用代码方法就是同步代码块,语法格式为:
//线程开始执行同步代码块之前,必须先获得同步监视器的锁定
synchronized (obj)
{
...
}
- 同步监视器的目的就是阻止两个线程对同一共享资源进行并发访问;
- 对于上面的取钱程序,可以改为:
public class DrawThread extends Thread
{
private Account account;
private double drawAmount;
public DrawThread(String name, Account account, double drawAmount)
{
super(name);
this.account = account;
this.drawAmount = drawAmount;
}
// 重写线程体
public void run()
{
synchronized (account) //使用account作为同步监视器(因为它是共享资源),任何线程进入下面的同步代码之前,必须先获得对account账户的锁定——其他线程无法获得锁,也就无法修复它;
//这种做法符合“加锁——修改——释放锁”的逻辑;
{
if (account.getBalance() >= drawAmount)
{
System.out.println(getName()
+ "取钱额度为" + drawAmount);
try //这步是为了测试有没有抢占发生
{
Thread.sleep(1);
}
catch (InterruptedException ex)
{
ex.printStackTrace();
}
//设定余额
account.setBalance(account.getBalance() - drawAmount);
System.out.println("余额为: " + account.getBalance());
}
else
{
System.out.println(getName() + "额度不足");
}
}
}
}
3.同步方法
-
与同步代码块对应,Java的多线程安全支持还提供了同步方法,同步方法的同步监视器是this,也就是调用该方法的对象;
-
通过使用同步方法可以非常方便的实现线程安全的类,线程安全的类具有如下特征:
-
该类的对象可以被多个线程安全访问;
-
每个线程调用该对象的任意方法都能获得正确结果;
-
每个线程调用该对象的任意方法后,该对象的状态依然保持合理状态;
public class Account
{
private String accountNo;
private double balance;
public Account(){}
public Account(String accountNo, double balance)
{
this.accountNo = accountNo;
this.balance = balance;
}
public void setAccountNo(String accountNo)
{
this.accountNo = accountNo;
}
public String getAccountNo()
{
return this.accountNo;
}
public double getBalance()
{
return this.balance;
}
// 使用synchronized关键字修饰方法
public synchronized void draw(double drawAmount)
{
if (balance >= drawAmount)
{
System.out.println(Thread.currentThread().getName()
+ "取钱额度为:" + drawAmount);
try
{
Thread.sleep(1);
}
catch (InterruptedException ex)
{
ex.printStackTrace();
}
balance -= drawAmount;
System.out.println("余额为: " + balance);
}
else
{
System.out.println(Thread.currentThread().getName()
+ "额度不足");
}
}
public int hashCode()
{
return accountNo.hashCode();
}
public boolean equals(Object obj)
{
if (this == obj)
return true;
if (obj != null
&& obj.getClass() == Account.class)
{
var target = (Account) obj;
return target.getAccountNo().equals(accountNo);
}
return false;
}
}
- synchronized关键字可以修饰方法、代码块,但是不能修饰变量、构造器;
- 线程安全是会降低程序性能的,在编程时要遵循以下几点:
- 不要对线程安全类的所有方法都进行同步,只对那些会引起共享资源改变的方法进行同步;
- 如果可变类有两种运行环境:单线程和多线程环境,则应该提供两个版本,在单线程中提供线程不安全版本提高性能,在多线程中提供线程安全版本保证正确性;
- StringBuilder线程不安全;StringBuffer线程安全;
4.释放同步监视器的锁定(753)
5.同步锁
-
Java5定义了lock对象作为同步锁来实现线程安全;
import java.util.concurrent.locks.*;
public class Account
{
// ReetrantLock可以显式加锁和释放锁;
private final ReentrantLock lock = new ReentrantLock();
private String accountNo;
private double balance;
public Account(){}
public Account(String accountNo, double balance)
{
this.accountNo = accountNo;
this.balance = balance;
}
public void setAccountNo(String accountNo)
{
this.accountNo = accountNo;
}
public String getAccountNo()
{
return this.accountNo;
}
public double getBalance()
{
return this.balance;
}
public void draw(double drawAmount)
{
// 上锁
lock.lock();
try
{
if (balance >= drawAmount)
{
System.out.println(Thread.currentThread().getName()
+ "取钱数量为" + drawAmount);
try
{
Thread.sleep(1);
}
catch (InterruptedException ex)
{
ex.printStackTrace();
}
balance -= drawAmount;
System.out.println("余额为: " + balance);
}
else
{
System.out.println(Thread.currentThread().getName()
+ "余额不足");
}
}
finally
{
// 在finally块中解锁
lock.unlock();
}
}
public int hashCode()
{
return accountNo.hashCode();
}
public boolean equals(Object obj)
{
if (this == obj)
return true;
if (obj != null
&& obj.getClass() == Account.class)
{
var target = (Account) obj;
return target.getAccountNo().equals(accountNo);
}
return false;
}
6.死锁及常用处理策略
- 死锁:当两个线程相互等待对方释放同步监视器时就会发生死锁;例如,A和B分别锁了两个资源,但是,A必须访问B的锁定资源,才能完成线程;而B也必须访问A的锁定资源,才能完成线程,由此互相等待;
- 避免死锁的方法:
- 尽量避免同一个线程对多个同步监视器进行锁定;
- 让线程具有相同的加锁顺序;
- 使用定时锁;
6.线程通信
1.传统的线程通信
-
为什么线程需要通信:举个例子,银行如果要求存钱者在存好钱之后,取钱者立刻取钱,不允许多次连续存钱和连续取钱,这时候就需要线程通信;
-
Object类提供三种方法:wait(), notify(), notifyAll()方法,这三个方法必须由同步监视器对象调用;
-
同步方法中(synchronized)可以直接调用这三个方法;同步代码块中需要使用对象调用;
-
方法解释:
-
wait():导致当前线程等待,直到其他线程调用该同步监视器的notify方法或notifyAll方法来唤醒该线程;
-
notify():唤醒在此同步监视器上等待的单个线程。如果所有的线程都在这个同步监视器上等待,则会随机唤醒其中一个线程
-
notifyAll():唤醒在此同步监视器上等待的所有线程;
public class Account
{
private String accountNo;
private double balance;
// 状态判定变量
private boolean flag = false;
public Account(){}
public Account(String accountNo, double balance)
{
this.accountNo = accountNo;
this.balance = balance;
}
public void setAccountNo(String accountNo)
{
this.accountNo = accountNo;
}
public String getAccountNo()
{
return this.accountNo;
}
public double getBalance()
{
return this.balance;
}
//取钱方法
public synchronized void draw(double drawAmount)
{
try
{
// 如果flag为true,表示还没有存钱进账户,方法阻塞;
if (!flag)
{
wait();
}
else
{
// ִ执行取钱
System.out.println(Thread.currentThread().getName()
+ "取钱" + drawAmount);
balance -= drawAmount;
System.out.println("账户余额为" + balance);
// 将flag设为false,表示标识账户已有存款;
flag = false;
//唤醒其他线程;
notifyAll();
}
}
catch (InterruptedException ex)
{
ex.printStackTrace();
}
}
//存钱方法
public synchronized void deposit(double depositAmount)
{
try
{
if (flag)
{
wait();
}
else
{
//存钱
System.out.println(Thread.currentThread().getName()
+ "存钱" + depositAmount);
balance += depositAmount;
System.out.println("余额" + balance);
// 存入钱,唤醒其他线程
flag = true;
notifyAll();
}
}
catch (InterruptedException ex)
{
ex.printStackTrace();
}
}
public int hashCode()
{
return accountNo.hashCode();
}
public boolean equals(Object obj)
{
if (this == obj)
return true;
if (obj != null
&& obj.getClass() == Account.class)
{
var target = (Account) obj;
return target.getAccountNo().equals(accountNo);
}
return false;
}
}
- 存款者线程:
public class DrawThread extends Thread
{
// 用户账户
private Account account;
// 要取得钱数
private double drawAmount;
public DrawThread(String name, Account account,
double drawAmount)
{
super(name);
this.account = account;
this.drawAmount = drawAmount;
}
//线程体
public void run()
{
for (var i = 0; i < 100; i++)
{
account.draw(drawAmount);
}
}
}
- 取钱者线程:
public class DepositThread extends Thread
{
private Account account;
private double depositAmount;
public DepositThread(String name, Account account,
double depositAmount)
{
super(name);
this.account = account;
this.depositAmount = depositAmount;
}
// 线程体
public void run()
{
for (var i = 0; i < 100; i++)
{
account.deposit(depositAmount);
}
}
}
- 主程序:
public class DrawTest
{
public static void main(String[] args)
{
var acct = new Account("1234567", 0);
new DrawThread("取钱", acct, 800).start();
new DepositThread("存钱", acct, 800).start();
new DepositThread("存钱", acct, 800).start();
new DepositThread("存钱", acct, 800).start();
}
}
2.使用Condition控制线程通信
- 当使用lock对象来保证同步时,Java提供了Condition类来保持协调,Condition可以让那些已经得到Lock对象却无法继续执行的线程释放Lock对象,Condition也可以唤醒其他处于等待的线程;
- Condition提供的三种方法:
- await();
- signal();
- signalAll();
- 程序示例:
import java.util.concurrent.*;
import java.util.concurrent.locks.*;
public class Account
{
// 建一个锁对象
private final Lock lock = new ReentrantLock();
// 将一个Condition对象绑定在这个Lock上
private final Condition cond = lock.newCondition();
private String accountNo;
private double balance;
private boolean flag = false;
public Account(){}
public Account(String accountNo, double balance)
{
this.accountNo = accountNo;
this.balance = balance;
}
public void setAccountNo(String accountNo)
{
this.accountNo = accountNo;
}
public String getAccountNo()
{
return this.accountNo;
}
public double getBalance()
{
return this.balance;
}
public void draw(double drawAmount)
{
// 锁方法
lock.lock();
try
{
if (!flag)
{
cond.await(); //类似于wait()方法
}
else
{
System.out.println(Thread.currentThread().getName()
+ "取钱:" + drawAmount);
balance -= drawAmount;
System.out.println("余额为" + balance);
flag = false;
cond.signalAll(); //类似notify()
}
}
catch (InterruptedException ex)
{
ex.printStackTrace();
}
// 解锁
finally
{
lock.unlock();
}
}
public void deposit(double depositAmount)
{
lock.lock();
try
{
if (flag)
{
cond.await();
}
else
{
System.out.println(Thread.currentThread().getName()
+ "存钱" + depositAmount);
balance += depositAmount;
System.out.println("余额为" + balance);
flag = true;
cond.signalAll();
}
}
catch (InterruptedException ex)
{
ex.printStackTrace();
}
finally
{
lock.unlock();
}
}
public int hashCode()
{
return accountNo.hashCode();
}
public boolean equals(Object obj)
{
if (this == obj)
return true;
if (obj != null
&& obj.getClass() == Account.class)
{
var target = (Account) obj;
return target.getAccountNo().equals(accountNo);
}
return false;
}
}
3.使用阻塞队列(BlockingQueue)控制线程通信
- BlockingQueue是Queue的子接口,它是一种线程工具:当生产者线程试图从BQ中放入数据,如果队列已满,则阻塞;当消费者线程试图从BQ中拿出数据,如果队列为空,则阻塞;
- BQ提供两个支持阻塞的方法:
- put(E e):尝试把E元素放入BQ中;
- take():从BQ中取出元素;
- 同时,BQ继承了Queue接口,BQ也能使用Queue的方法;(763)
- BQ的使用例程:
import java.util.concurrent.*;
public class BlockingQueueTest
{
public static void main(String[] args)
throws Exception
{
// 创建一个容量为2的BQ队列
BlockingQueue<String> bq = new ArrayBlockingQueue<>(2);
bq.put("Java"); // 等同于add("java")
bq.put("Java");
bq.put("Java"); //超出队列要求,程序阻塞;
}
}
7.线程组和未处理的异常(767)
8.线程池
1.使用线程池管理线程
- 当系统需要在启动时就创建大量线程时,采用线程池可以提高效率;
- Java5新增了一个Executors工厂类来产生线程池:
- newCachedThreadPool():创建一个具有缓存功能的线程池,线程将会被存在这个线程池中;
- newFixedThreadPool(int nThread):创建一个可重用的,具有固定线程数的线程池;
- newSingleThreadExecutor():创建一个只有单线程的线程池;
- newScheduledThreadPool(int corePoolSize):创建具有指定数的线程池;可以在指定延迟后执行线程任务;
- newSingleThreadScheduledExecutor():创建只有一个线程的线程池,它可以在指定延迟后执行线程任务;
- 使用线程池来执行线程任务的步骤如下:
- 调用Executors类的静态工厂方法创建一个ExecutorService对象,该对象代表一个线程池;
- 创建Runnable实现类或Callable实现类的实例,作为线程执行任务;
- 调用ExecutorService对象的submit()方法来提交Runnable实例或Callable实例;
- 当不想提交任何任务时,调用ExecutorService对象的shutdown()方法来关闭线程池;
import java.util.concurrent.*;
public class ThreadPoolTest
{
public static void main(String[] args)
throws Exception
{
//创建一个线程池,包含6个线程
ExecutorService pool = Executors.newFixedThreadPool(6);
// 使用lambda表达式创建Runnable对象
Runnable target = () -> {
for (var i = 0; i < 100; i++)
{
System.out.println(Thread.currentThread().getName()
+ "线程名称" + i);
}
};
// 提交两个线程
pool.submit(target);
pool.submit(target);
// 关闭线程池
pool.shutdown();
}
}
2.使用ForkJoinPool利用多CPU
- Java7提供了ForkJoinPool来支持将一个任务拆分成多个小任务并行计算,再把多个小任务的结果合并成总的计算结果;FJP是ExecutorService的实现类,是一种特殊的线程池;
- 没有返回值的FJP:把一个任务拆成2个任务
import java.util.concurrent.*;
// RecursiveAction代表没有返回值的FPJ子类;
class PrintTask extends RecursiveAction
{
// 每个小任务打印的数字
private static final int THRESHOLD = 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 < THRESHOLD)
{
for (var i = start; i < end; i++)
{
System.out.println(Thread.currentThread().getName()
+ "的i值" + i);
}
}
else
{
// 将任务分解成两个小任务
int middle = (start + end) / 2;
var left = new PrintTask(start, middle);
var right = new PrintTask(middle, end);
// 执行两个任务
left.fork();
right.fork();
}
}
}
public class ForkJoinPoolTest
{
public static void main(String[] args)
throws Exception
{
var pool = new ForkJoinPool();
// 提交可分解的FJP任务
pool.submit(new PrintTask(0, 300));
pool.awaitTermination(2, TimeUnit.SECONDS);
// 关闭线程池
pool.shutdown();
}
}
- 有返回值的FJP:
import java.util.concurrent.*;
import java.util.*;
//RecursiveTask<T>代表了能够返回值的可分解任务
class CalTask extends RecursiveTask<Integer>
{
private static final int THRESHOLD = 20;
private int arr[];
private int start;
private int end;
public CalTask(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 (var i = start; i < end; i++)
{
sum += arr[i];
}
return sum;
}
else
{
//递归调用
int middle = (start + end) / 2;
var left = new CalTask(arr, start, middle);
var right = new CalTask(arr, middle, end);
left.fork();
right.fork();
return left.join() + right.join(); // ��
}
}
}
public class Sum
{
public static void main(String[] args)
throws Exception
{
var arr = new int[100];
var rand = new Random();
var total = 0;
// ��ʼ��100������Ԫ��
for (int i = 0, len = arr.length; i < len; i++)
{
int tmp = rand.nextInt(20);
// ������Ԫ�ظ�ֵ����������Ԫ�ص�ֵ��ӵ�sum�ܺ��С�
total += (arr[i] = tmp);
}
System.out.println(total);
// ����һ��ͨ�ó�
ForkJoinPool pool = ForkJoinPool.commonPool();
// �ύ�ɷֽ��CalTask����
Future<Integer> future = pool.submit(new CalTask(arr, 0, arr.length));
System.out.println(future.get());
// �ر��̳߳�
pool.shutdown();
}
}