1. 线程相关的概念
- 程序:是为了完成特定的任务,用某种语言编写的一组指令的集合,简单的来说就是我们的代码。
- 进程:进程时指运行中的程序,比如QQ,启动QQ就启动了一个进程,操作系统就会为该进程分配内存空间。进程时程序的一次执行过程,或是正在运行的一个程序。是动态的过程,它有自身的产生、存在、消亡的过程。
- 线程:线程由进程创建,是进程的一个实体,一个进程可以拥有多个线程。
- 单线程:同一时刻,只允许执行一个线程。
- 多线程:同一时刻,可以执行多个线程,例如QQ可以打开多个窗口。一个迅雷可以下载多个文件/软件
- 并发:同一时刻,多个任务交替执行,造成一种”貌似同时“的错觉,简单来说,单核cpu实现的多任务就是并发
- 并行:同一时刻,多个任务同时执行,多核cpu可以实现并行
1.1 用java查看cpu
public class CpuNum {
public static void main(String[] args) {
Runtime runtime = Runtime.getRuntime();
int number = runtime.availableProcessors();
System.out.println("个数为"+number);
}
}
2. 创建线程的方式
2.1 继承Thread类,重写run方法
Thread类实际上也实现了Runnable接口
2.1.1 题目
开启一个线程,要求该线程每隔一秒在控制台输出 ”喵喵,我是小猫咪“
public class Thread01 {
public static void main(String[] args) {
Cat cat = new Cat();
cat.start();
}
}
// 当一个类继承了Thread类,该类就可以当作线程使用
class Cat extends Thread{
// 重写Run方法,在里面写上自己的业务逻辑
@Override
public void run() {
while (true) {
// 每隔1秒输出语句
System.out.println("喵喵,我是小猫咪");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
对上题进行改进:当输出80次后,结束该线程。
public class Thread01 {
public static void main(String[] args) {
Cat cat = new Cat();
cat.start();
}
}
// 当一个类继承了Thread类,该类就可以当作线程使用
class Cat extends Thread{
int times = 0;
// 重写Run方法,在里面写上自己的业务逻辑
@Override
public void run() {
while (true) {
// 每隔1秒输出语句
System.out.println("喵喵,我是小猫咪");
times++;
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (times==80){
// 推出while循环,此时线程也就退出了
break;
}
}
}
}
注意:当我们启动main方法的时候,会进入到一个进程,然后执行main方法,其实就是启动了一个线程,之后在main方法中执行了start方法,这时就启动了一个线程。
看上述代码,当我们执行start方法的时候,主线程不会阻塞(即不会等到cat.start方法执行完了之后再执行下面的代码),它还会执行下面的方法。
例子:
public class Thread01 {
public static void main(String[] args) throws InterruptedException {
Cat cat = new Cat();
cat.start();
for (int i=0;i<100;i++){
System.out.println("这是主线程"+i+"主线程的名称"+Thread.currentThread().getName());
Thread.sleep(1000);
}
}
}
// 当一个类继承了Thread类,该类就可以当作线程使用
class Cat extends Thread{
int times = 0;
// 重写Run方法,在里面写上自己的业务逻辑
@Override
public void run() {
while (true) {
// 每隔1秒输出语句
System.out.println("喵喵,我是小猫咪"+"我的线程名称是"+Thread.currentThread().getName());
times++;
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (times==80){
// 推出while循环,此时线程也就退出了
break;
}
}
}
}
所以 主线程和子线程是交替执行的
我们将主线程的for循环调整至10次,继续执行
重要:得到的结论就是当主线程结束的时候,如果还有其余线程没有结束,那么整个应用程序是不会结束的
2.1.2 JConsole查看线程的执行情况
执行main方法后切换到控制台执行命令jconsole即可
2.1.3 为什么是执行start
当执行start方法后会启动线程是因为内部调用run方法,为什么不直接使用run方法呢?
直接使用run方法
public class Thread01 {
public static void main(String[] args) throws InterruptedException {
Cat cat = new Cat();
cat.run();
for (int i=0;i<10;i++){
System.out.println("这是主线程"+i+"主线程的名称"+Thread.currentThread().getName());
Thread.sleep(1000);
}
}
}
// 当一个类继承了Thread类,该类就可以当作线程使用
class Cat extends Thread{
int times = 0;
// 重写Run方法,在里面写上自己的业务逻辑
@Override
public void run() {
while (true) {
// 每隔1秒输出语句
System.out.println("喵喵,我是小猫咪"+"我的线程名称是"+Thread.currentThread().getName());
times++;
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (times==10){
// 推出while循环,此时线程也就退出了
break;
}
}
}
}
**可以看到执行cat.run方法的时候直接阻塞在这个方法,而且线程是main线程,也就是主线程,而不是Thread01,直到cat.run方法执行结束后才执行下面的for循环。 由此可见,只有start方法才是真正启动线程,run方法只是一个普通的方法 **
源码查看:
public synchronized void start() {
/**
* This method is not invoked for the main method thread or "system"
* group threads created/set up by the VM. Any new functionality added
* to this method in the future may have to also be added to the VM.
*
* A zero status value corresponds to state "NEW".
*/
if (threadStatus != 0)
throw new IllegalThreadStateException();
/* Notify the group that this thread is about to be started
* so that it can be added to the group's list of threads
* and the group's unstarted count can be decremented. */
group.add(this);
boolean started = false;
try {
start0();
started = true;
} finally {
try {
if (!started) {
group.threadStartFailed(this);
}
} catch (Throwable ignore) {
/* do nothing. If start0 threw a Throwable then
it will be passed up the call stack */
}
}
}
private native void start0();
可以看到里面调用了strat0方法,这是native方法,不是由java语言编写,真正实现多线程的是start0方法。
start方法调用start0方法后,该线程并不会立马执行,只是将线程变成了可运行状态,具体什么时候执行,取决于cpu,由cpu统一调度。
2.2 实现Runnable接口,重写run方法
说明:
- java是单继承的,在某些情况下一个类可能已经继承了某个父类,这时在用继承Thread类的方法来创建线程显然是不可能了
- java提供了另一个方式创建线程,就是通过实现Runnable接口来创建线程
2.2.1 代码演示
案例:编写程序,使得该程序每隔1秒在控制台输出“hi”,当输出10次后自动退出。
public class Thread02 {
public static void main(String[] args) {
T2 t2 = new T2();
}
}
class T2 implements Runnable{
int count = 0;
@Override
public void run() {
while(true){
System.out.println("hi,我的线程名称是"+Thread.currentThread().getName());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (count>=10){
break;
}
}
}
}
此时一个问题出来了,Runnable接口没有start方法,只有run方法,但是通过上述例子已经得知run方法不能创建一个线程,run方法只是一个普通的方法,那么应该如果进行开启一个线程呢?
public class Thread02 {
public static void main(String[] args) {
T2 t2 = new T2();
Thread thread = new Thread(t2);
thread.start();
}
}
class T2 implements Runnable{
int count = 0;
@Override
public void run() {
while(true){
System.out.println("hi,我的线程名称是"+Thread.currentThread().getName());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (count>=10){
break;
}
}
}
}
这里我们创建Thread实例,调用其一个构造器,将t2实例放入构造器中,再调用Thread实例的start方法
为什么呢?因为这里底层使用了静态代理模式
2.2.2 代码模拟,实现Runnable接口开发线程的机制
创建一个类ThreadProxy来模拟极简的Thread类
public class Thread02 {
public static void main(String[] args) {
Tiger tiger = new Tiger();// 实现Runnable接口
ThreadProxy threadProxy = new ThreadProxy(tiger);
threadProxy.start();
}
}
// 你可以把Proxy当作Thread类来对待,你把他看作一个Thread类,它是线程代理类,模拟了极简的Thread类
class ThreadProxy implements Runnable {
// 熟悉targer,类型是Runnable
private Runnable target = null;
@Override
public void run() {
if (target != null) {
target.run();
}
}
public ThreadProxy(Runnable target){
this.target = target;
}
public void start(){
start0();
}
public void start0(){
run();
}
}
class Animal{
}
class Tiger extends Animal implements Runnable{
@Override
public void run() {
System.out.println("老虎嗷嗷叫");
}
}
首先明白什么是代理模式,通俗语言来说就是我没有一个东西,但是你有,所以就需要你来帮忙做。
上述代码中,Runnable接口中没有start方法,所以我们再ThreadProxy类中接收实现Runnable接口的类,之后在ThreadProxy类中创建start方法,在start方法立马执行实现Runnable接口类的run方法。这就是代理模式,即tiger类实现Runnable没有start方法,我们创建ThreadProxy类,将tiger传进ThreadProxy中,在ThreadProxy类中执行start方法,但是此start方法执行的run方法还是tiger自己的。
2.3 多个子线程案例
案例:编写一个程序,创建两个线程,一个线程每隔1秒输出 “hello world” 输出10次,退出。另一个线程每隔1秒输出 “hi” 输出5次退出。要求用实现Runnable方式实现。
public class Thread03 {
public static void main(String[] args) {
Thread thread = new Thread(new T1());
Thread thread1 = new Thread(new T3());
thread.start();
thread1.start();
}
}
class T1 implements Runnable{
int count = 0;
@Override
public void run() {
while(true) {
System.out.println("hello,world");
count++;
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (count >= 10) {
break;
}
}
}
}
class T3 implements Runnable{
int count = 0;
@Override
public void run() {
while(true) {
System.out.println("hi");
count++;
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (count >= 10) {
break;
}
}
}
}
我们看到貌似输出没有规律。后面会对此进行说明
2.4 继承Thread和实现Runnable的区别
- 从java的设计来看,通过继承Thread或者实现Runnable接口来创建线程本质上没有区别,因为Thread类本身就实现了Runnable接口
- 实现Runnable接口更适合多个线程共享一个资源的情况,而且避免了单继承的限制性
public class Thread04 {
public static void main(String[] args) {
T5 t5 = new T5();
Thread thread = new Thread(t5);
Thread thread1 = new Thread(t5);
thread.start();
thread1.start();
}
}
class T5 implements Runnable{
@Override
public void run() {
}
}
我们创建一个类实现Runnable,在main方法中,创建T5实例,然后创建多个Thread类实例,调用start方法,就是启动多个线程,但是因为传入的都是T5实例,所以其实多个线程执行的run方法都是同一个run方法
2.5 多线程售票
案例:编程模拟三个售票窗口同时售票100张,分别使用继承Thread类和实现Runnable接口的方式,并分析会有什么问题
2.5.1 继承Thread类
public class SellTicket {
public static void main(String[] args) {
SellTicket01 sellTicket01 = new SellTicket01();
SellTicket01 sellTicket02 = new SellTicket01();
SellTicket01 sellTicket03 = new SellTicket01();
sellTicket01.start();
sellTicket02.start();
sellTicket03.start();
}
}
class SellTicket01 extends Thread{
private static int ticketNumber = 100;
@Override
public void run() {
while(true){
if (ticketNumber<=0){
System.out.println("售票结束,退出程序");
break;
}
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
ticketNumber--;
System.out.println("当前线程 "+Thread.currentThread().getName()+"售出了一张票 "+"剩余票数 "+ticketNumber);
}
}
}
问题来了,超卖了
为什么会出现现在这种情况呢?
因为现在有三个线程,在共同抢夺一个资源ticketNumber,三个线程都在while循环,假如此时tickerNumber只剩1张了,现在三个线程都同时进入到判断ticketNumber是否<=0,且都通过了,所以三个线程一起减票,所以就出现超卖的情况。
2.5.2 实现接口的方式
public class SellTicketByRunnable {
public static void main(String[] args) {
SellTicketByRunnable2 sell = new SellTicketByRunnable2();
Thread thread = new Thread(sell);
Thread thread2 = new Thread(sell);
Thread thread3 = new Thread(sell);
thread.start();
thread2.start();
thread3.start();
}
}
class SellTicketByRunnable2 implements Runnable{
private int ticketNumber = 100;
@Override
public void run() {
while(true){
if (ticketNumber<=0){
System.out.println("售票结束,退出程序");
break;
}
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
ticketNumber--;
System.out.println("当前线程 "+Thread.currentThread().getName()+"售出了一张票 "+"剩余票数 "+ticketNumber);
}
}
}
其实还是出现了超卖的现象
3. 线程终止
基本说明:
- 当线程完成任务后,会自动退出
- 还可以通过控制变量来控制run方法退出的方式来停止线程
案例:启动线程 T 要求在main线程中去停止 T 请编程实现
略
4. 线程的常用方法
第一组:
- setName
- getName
- start
- run
- setPriority:设置线程优先级 MAX_PRIORITY:10 ,MIN_PRIORITY:1 ,NORM_PRIORITY:5
- getPriority:获取线程优先级
- sleep
- interrupt:中断线程,注意中断线程不是终止线程。所以一般用于中断正在休眠的线程
演示interrupt
public class ThreadExit_ {
public static void main(String[] args) throws InterruptedException {
T t = new T();
t.start();
t.interrupt();
}
}
class T extends Thread{
@Override
public void run() {
while(true){
try {
System.out.println("线程还在运行中");
Thread.sleep(50000);
} catch (InterruptedException e) {
System.out.println("被Interrupted了");
}
}
}
}
后面在一直sleep,因为我们只调用了一次interrupt方法
第二组:
- yield:线程的礼让。让出cpu资源,让其他线程执行,但是礼让的时间不确定,所以也不一定礼让成功。
- join:线程的插队,插队的线程一旦插队成功,则肯定先执行完插队的线程的所有任务。
案例:创建一个子线程,每隔1s输出hello,输出20次,主线程每隔1s,输出hi,输出20次。要求两个线程同时执行,当主线程输出5次后,就让子线程全部运行完毕,主线程再继续输出。
演示join
public class YieldJoin {
public static void main(String[] args) throws InterruptedException {
T t = new T();
t.start();
for (int i=0;i<20;i++){
System.out.println("hi");
Thread.sleep(1000);
if (i==4){
t.join();// 让子线程插队
}
}
}
}
class T extends Thread {
int count = 0;
@Override
public void run() {
while (true) {
try {
System.out.println("hello");
count++;
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (count >= 20) {
break;
}
}
}
}
演示yield方法
public class YieldJoin {
public static void main(String[] args) throws InterruptedException {
T t = new T();
t.start();
for (int i=0;i<20;i++){
System.out.println("hi");
Thread.sleep(1000);
if (i==4){
Thread.yield();
}
}
}
}
class T extends Thread {
int count = 0;
@Override
public void run() {
while (true) {
try {
System.out.println("hello");
count++;
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (count >= 20) {
break;
}
}
}
}
因为礼让不一定成功,礼让是和cpu内核相关,如果cpu资源丰富,那就基本不会礼让,只有在资源紧缺的是时候才会体现出来礼让
5. 用户线程和守护线程
- 用户线程:也叫工作线程,当线程的任务执行完或通知方式结束
- 守护线程:一般是为工作线程服务的,当所有的用户线程结束,守护线程会自动结束
- 最常见的守护线程:垃圾回收机制
5.1 将用户线程设置为守护线程
public class ThreadMethod03 {
public static void main(String[] args) throws InterruptedException {
T2 t2 = new T2();
t2.start();
for (int i=1;i<=10;i++){
System.out.println("主线程");
Thread.sleep(1000);
}
}
}
class T2 extends Thread {
@Override
public void run() {
while (true) {
try {
System.out.println("hello");
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
上述代码中,主线程结束后,子线程还在继续执行。所以子线程不是守护线程,因为守护线程它是所有线程结束后就结束
那么如何设置用户线程为守护线程?
setDaemon方法可以将用户线程设置为守护线程
public class ThreadMethod03 {
public static void main(String[] args) throws InterruptedException {
T2 t2 = new T2();
t2.setDaemon(true);// 将用户线程设置为守护线程
t2.start();
for (int i=1;i<=5;i++){
System.out.println("主线程");
Thread.sleep(1000);
}
}
}
class T2 extends Thread {
@Override
public void run() {
while (true) {
try {
System.out.println("hello");
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
6. 线程的生命周期
-
JDK中用Thread.State枚举表示了线程的 6 种状态(官方)
-
线程状态迁移图
7. 线程同步机制
根据之前的买票问题,会出现超卖的问题
线程同步机制:
- 在多线程编程,一些敏感的数据不允许被多个线程同时访问,此时就使用同步访问技术,保证数据在任何同一时刻,最多有一个线程访问,以此来保证数据的完整性。
- 也可以理解:线程同步,即当有一个线程在对内存进行操作时,其他线程都不能对这个内存地址进行擦欧总,直到该线程完成操作,其他线程才能对该内存地址进行操作。
7.1 Synchronized
使用方法:
- 同步代码块
synchronized (对象){ // 这个对象就相当于是一把锁
// 需要被同步的代码
}
- synchronized声明整个方法
public synchronized void m (String name){
// 需要被同步的代码
}
7.2 解决售票问题
public class SellTicketByRunnable {
public static void main(String[] args) {
SellTicketByRunnable2 sell = new SellTicketByRunnable2();
Thread thread = new Thread(sell,"窗口1");
Thread thread2 = new Thread(sell,"窗口2");
Thread thread3 = new Thread(sell,"窗口3");
thread.start();
thread2.start();
thread3.start();
}
}
class SellTicketByRunnable2 implements Runnable{
private int ticketNumber = 100;
private boolean loop = true;
private static Object object;
public synchronized void sell(){
if (ticketNumber<=0){
System.out.println("售票结束,退出程序");
loop=false;
return;
}
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
ticketNumber--;
System.out.println("当前线程 " + Thread.currentThread().getName() + "售出了一张票 " + "剩余票数 " + ticketNumber);
}
@Override
public void run() {
while(loop){
sell();
}
}
}
将sell方法单独抽出为卖票方法,并且给该方法加上synchronized
这是优化实现RUnnable接口的售票,我们只是实例化一个SellTicketByRunnable2 对象,然后创建三个Thread类并且传入SellTicketByRunnable2 对象。所以本质上执行的还是一个对象的方法,而且锁对象也是同一个。但是如果是继承Thread类呢?还能这样子写吗
我们使用继承Thrad类的类.class即可
7.3 互斥锁
- java语言中引入互斥锁的概念来保证共享数据操作的完整性
- 每个对象都对应于一个可称为 “互斥锁” 的标记,这个标记用来保证在任意时刻,只有一个线程能访问该对象
- 关键字synchronized来与对象的互斥锁联系,当某个对象用synchronized修饰时,表明该对象在任一时刻只能由一个线程访问
- 同步的局限性:导致程序执行效率降低
- 同步方法(非静态)的锁可以是this,也可以是其他对象(要求是同一个对象)
- 同步方法(静态的)的锁为当前类本身
注意事项:
- 同步方法如果没有使用static修饰,默认锁对象为this
- 如果方法使用static修饰,默认锁对象为 当前类.class
- 实现的落地步骤:1:需要分析上锁的代码 2:选择同步代码块或者同步方法 3:要求多个线程的锁对象为同一个即可
8. 线程死锁
基本结束: 多个线程都占用了对方的锁资源,但是不肯相让,导致了死锁,在编程中一定要避免死锁的发生。
public class DeadLock_ {
public static void main(String[] args) {
DeadLockDemo deadLockDemo = new DeadLockDemo(true);
DeadLockDemo deadLockDemo2 = new DeadLockDemo(false);
deadLockDemo.start();
deadLockDemo2.start();
}
}
class DeadLockDemo extends Thread{
static Object o1 = new Object();
static Object o2 = new Object();
boolean flag;
public DeadLockDemo(boolean flag){
this.flag = flag;
}
@Override
public void run() {
if (flag){
synchronized (o1){
System.out.println(Thread.currentThread().getName()+"进入1");
synchronized (o2){
System.out.println(Thread.currentThread().getName()+"进入2");
}
}
}else {
synchronized (o2){
System.out.println(Thread.currentThread().getName()+"进入3");
synchronized (o1){
System.out.println(Thread.currentThread().getName()+"进入4");
}
}
}
}
}
出现死锁,所以这种写法一定要避免
9. 释放锁
以下操作会释放锁:
- 当前线程的同步方法,同步代码块执行结束
- 当前线程在同步代码块,同步方法中遇见break,return
- 当前线程在同步代码块,同步方法中出现了未处理的error和exception,导致异常结束
- 当前线程在同步代码块,同步方法中执行了线程对象的wait方法,当前线程暂停,并释放锁
以下操作不会释放锁:
- 线程执行同步代码块或者同步方法时,程序调用Thread.sleep或者Thread.yield方法暂停当前线程执行,不会释放锁
- 线程执行同步代码块时,其他线程调用了该线程的suspend方法将该线程挂起,该线程不会释放锁
- 理应避免suspend和resume方法来控制线程,不推荐
10. sleep和wait的异同
相同:一旦执行方法,都可以使得当前线程进入阻塞状态
不同:
- 两个方法声明的位置不同,Thread类中声明sleep,object类中声明wait
- 调用的要求不同,sleep可以在任何需要的场景下调用,wait必须在同步代码块中进行调用
- 如果两个方法都使用在同步代码块或方法中,sleep不会释放锁,wait会释放锁
11. Lock
- 从JDK 5.0开始,Java提供了更强大的线程同步机制——通过显式定义同步锁对象来实现同步。同步锁使用Lock对象充当。
- java.util.concurrent.locks.Lock接口是控制多个线程对共享资源进行访问的工具。锁提供了对共享资源的独占访问,每次只能有一个线程对Lock对象加锁,线程开始访问共享资源之前应先获得Lock对象。
- ReentrantLock 类实现了 Lock ,它拥有与 synchronized 相同的并发性和内存语义,在实现线程安全的控制中,比较常用的是ReentrantLock,可以显式加锁、释放锁
使用方法
class A {
private final ReentrantLock lock = new ReenTrantLock();
public void m() {
lock.lock();
try {
//保证线程安全的代码; }
finally{
lock.unlock();
}
}
}
11.1 synchronized 与 Lock 的对比
- Lock是显式锁(手动开启和关闭锁,别忘记关闭锁),synchronized是隐式锁,出了作用域自动释放
- Lock只有代码块锁,synchronized有代码块锁和方法锁
- 使用Lock锁,JVM将花费较少的时间来调度线程,性能更好。并且具有更好的扩展性(提供更多的子类)
使用优先级:
Lock > 同步代码块(已经进入了方法体,分配了相应资源) > 同步方法(在方法体之外)
12. 创建线程的另外两种方式
12.1 实现Callable
与使用Runnable相比, Callable功能更强大些
- 相比run()方法,可以有返回值
- 方法可以抛出异常
- 支持泛型的返回值
- 需要借助FutureTask类,比如获取返回结果
Future接口
- 可以对具体Runnable、Callable任务的执行结果进行取消、查询是否完成、获取结果等。
- FutrueTask是Futrue接口的唯一的实现类
- FutureTask 同时实现了Runnable, Future接口。它既可以作为Runnable被线程执行,又可以作为Future得到Callable的返回值
实例:
public class Callable_ {
public static void main(String[] args) throws ExecutionException, InterruptedException {
// 创建实现了Callable的接口的实例
NumThread numThread = new NumThread();
// 获取call方法返回值,如果不需要的返回值,就不需要创建FutureTask
FutureTask futureTask = new FutureTask(numThread);
// 真正的执行还是需要创建Thread
Thread thread = new Thread(futureTask);
thread.start();
// 获取call方法的结果
Object sum = futureTask.get();
System.out.println(sum);
}
}
class NumThread implements Callable {
@Override
public Object call() throws Exception {
int sum = 0;
for (int i = 0; i < 100; i++) {
if (i % 2 == 0) {
System.out.println(i);
sum += i;
}
}
return sum;
}
}
12.2 使用线程池
背景:经常创建和销毁、使用量特别大的资源,比如并发情况下的线程,对性能影响很大。
思路:提前创建好多个线程,放入线程池中,使用时直接获取,使用完放回池中。可以避免频繁创建销毁、实现重复利用。类似生活中的公共交通工具。
好处:
- 提高响应速度(减少了创建新线程的时间)
- 降低资源消耗(重复利用线程池中线程,不需要每次都创建)
- 便于线程管理
public class ThreadPool {
public static void main(String[] args) {
// 创建提供指定线程数量的线程池
ExecutorService executorService = Executors.newFixedThreadPool(10);
// 适合使用Runnable
// executorService.execute();
// 适合使用于Callable
executorService.submit(new NumThread1());
// 关闭线程池
executorService.shutdown();
}
}
class NumThread1 implements Callable {
@Override
public Object call() throws Exception {
int sum = 0;
for (int i = 0; i < 100; i++) {
if (i % 2 == 0) {
System.out.println(i);
sum += i;
}
}
return sum;
}
}
创建线程池的七个参数
- corePoolSize:核心线程数,也是线程池中常驻的线程数,线程池初始化时默认是没有线程的,当任务来临时才开始创建线程去执行任务
- maximumPoolSize:最大线程数,在核心线程数的基础上可能会额外增加一些非核心线程,需要注意的是只有当workQueue队列填满时才会创建多于corePoolSize的线程(线程池总线程数不超过maxPoolSize)
- keepAliveTime:非核心线程的空闲时间超过keepAliveTime就会被自动终止回收掉,注意当corePoolSize=maxPoolSize时,keepAliveTime参数也就不起作用了(因为不存在非核心线程);
- unit:keepAliveTime的时间单位
- workQueue:用于保存任务的队列,可以为无界、有界、同步移交三种队列类型之一,当池子里的工作线程数大于corePoolSize时,这时新进来的任务会被放到队列中
- threadFactory:创建线程的工厂类,默认使用Executors.defaultThreadFactory(),也可以使用guava库的ThreadFactoryBuilder来创建
- handler:线程池无法继续接收任务(队列已满且线程数达到maximunPoolSize)时的饱和策略,取值有AbortPolicy、CallerRunsPolicy、DiscardOldestPolicy、DiscardPolicy