第九篇 Java多线程

参考资料:《Java线程》、《JDK API 1.6 中文版》
学习视频:b站《尚硅谷Java入门视频教程》(主讲:宋红康老师)

第九篇 Java多线程

一、基本概念

  • 线程是程序中的执行线程。Java 虚拟机允许应用程序并发地运行多个执行线程
  • 每个线程都有一个优先级,高优先级线程的执行优先于低优先级线程。每个线程都可以或不可以标记为一个守护程序。当某个线程中运行的代码创建一个新 Thread对象时,该新线程的初始优先级被设定为创建线程的优先级,并且当且仅当创建线程是守护线程时,新线程才是守护程序。
  • 当Java虚拟机启动时,通常都会有单个非守护线程(它通常会调用某个指定类的main方法)。Java虚拟机会继续执行线程,直到下列任一情况出现时为止:
    ✒️ 调用了Runtime类的exit方法,并且安全管理器允许退出操作发生。
    ✒️ 非守护线程的所有线程都已停止运行,无论是通过从对 run 方法的调用中返回,还是通过抛出一个传播到run方法之外的异常。
  • 创建新执行线程有两种方法。一种方法是将类声明为Thread的子类。该子类应重写Thread类的run方法;另一种方法是声明实现Runnable接口的类。该类然后实现run方法。然后可以分配该类的实例,在创建Thread时作为一个参数来传递并启动。—— 摘自官方文档JDK API 1.6 中文版

1、程序、进程和线程

  • 程序(Program):为完成特定任务或功能所编写的一组指令集合,即一段静态的代码/静态对象
  • 进程(Process):程序执行的结果,是动态的过程,拥有生命周期,作为系统资源分配的单位。系统在运行时会为其分配不同的内存。
  • 线程(Thread):进程的进一步细化,是程序内的一条执行路径。
    ✒️一个Java程序至少有3个线程:main( )主线程、gc( )垃圾资源回收线程、异常处理线程
    ✒️Java线程可分为两类:一是守护线程,二是用户线程(java垃圾回收是一个典型的守护线程)
    ✒️setDaemon( true )可以将一个用户线程变成守护线程,若JVM中都是守护线程,则JVM将退出

2、并行和并发

  • 并行:多个CPU同时执行多个任务
  • 并发:单个CPU执行多个任务(和串口传输类似,实际上不是同一时刻在执行 —— 时间片)

二、线程的调度

关于线程调度的具体实现和原理,详见《Java线程》第六章

1、线程的调度策略和调度方法

  • 时间片+抢占式:**优先级高的线程占用CPU
    ✒️时间片策略:同优先级线程组成先进先出队列
    ✒️抢占式策略:高优先级线程优先占用CPU

PS:高优先级线程优先抢占CPU执行权,只是抢占的概率高,并不意味着高优先级一定比低优先级的线程先执行

2、线程的优先级

(1)线程优先等级
定义在Thread类里的常量 优先级
MIN_PRIORITY 1
NORM_PRIORITY(默认优先级) 5
MAX_PRIORITY 10
(2)获取和设置线程优先级
  • setPriority( int newPriority ):设置线程优先级为newPriority
  • getPriority( ):获取线程当前优先级

三、线程的创建和使用

1、方式一:继承于Thread类

(1)使用步骤
  • 创建一个子类继承于Thread类
  • 重写Thread类的run( )
  • 实例化Thread类的子类对象
  • 由该对象调用start( )启动线程
package com.javaThread.java;

/* 线程1遍历100内偶数 */
class subThread1 extends Thread{
    
    
    /* 1、创建子类继承于Thread */
    @Override
    /* 2、重写run方法 */
    /* 3、到main里实例化线程1对象 */
    /* 4、到main里用线程1对象调用start方法 */
    public void run() {
    
    
        for (int i = 0; i < 100; i++) {
    
    
            if(i%2 == 0) System.out.println(this.getName()+":"+i);
        }
    }
}
/* 线程2遍历100内奇数 */
class subThread2 extends Thread{
    
    
    @Override
    public void run() {
    
    
        for (int i = 0; i < 100; i++) {
    
    
            if(i%2 != 0) System.out.println(this.getName()+":"+i);
        }
    }
}
public class ThreadDemo1 {
    
    
    public static void main(String[] args) throws InterruptedException {
    
    
        subThread1 t1 = new subThread1();
        subThread2 t2 = new subThread2();
        t1.start();
        t2.start();
        while (true) {
    
    
            System.out.println("Hello world");
            Thread.sleep(1000);
        }
    }
}
(2)匿名子类的写法
package com.javaThread.java;

public class ThreadDemo2 {
    
    
    public static void main(String[] args) {
    
    
        /* 创建Thread类的匿名子类 */
        new Thread(){
    
    
            @Override
            public void run() {
    
    
                for (int i = 0; i < 100; i++) {
    
    
                    if(i%2 == 0) System.out.println(this.getName()+":"+i);
                }
            }
        }.start();
        
         /* 创建Thread类的匿名子类 */
        new Thread(){
    
    
            @Override
            public void run() {
    
    
                for (int i = 0; i < 100; i++) {
    
    
                    if(i%2 != 0) System.out.println(this.getName()+":"+i);
                }
            }
        }.start();
    }
}
(3)Thread类常用方法
  • start( ):启动当前线程,会调用当前线程的run( )方法
  • run( ):创建线程要执行的操作声明在此方法中(类似Linux多线程中传入pthread_create( )中的指针函数)
  • currentThread( ):静态方法,返回当前线程
  • getName( ):获取当前线程名
  • setName( ):设置当前线程名
  • yield( ):释放CPU执行权,重新调度(释放后可能被其他线程抢占,也可能又被自己抢到)
  • join( ):阻塞等待其他线程执行完,再结束阻塞
  • sleep( long millis ):线程阻塞等待一定时间(毫秒级别)
  • isAlive( ):判断线程死活,返回线程状态true/false
  • 其他方法(Deprecated - 已过时):stop( )强制结束
(4)经典例子:多窗口售票v1.0
/* 三个窗口(线程)同时卖票,一共100张票 */
class Windows extends Thread{
    
    
    public Windows(String threadName){
    
    
        super(threadName);
    }
    private static int tickets = 100;
    @Override
    public void run() {
    
    
        while (tickets > 0){
    
    
            System.out.println(this.getName()+" > NO."+tickets+" has been sold");
            tickets --;
        }
    }
}
public class ThreadDemo3 {
    
    
    public static void main(String[] args) {
    
    
        Windows w1 = new SellTickets("windows_1");
        Windows w2 = new SellTickets("windows_2");
        Windows w3 = new SellTickets("windows_3");
        w1.start();
        w2.start();
        w3.start();
    }
}
  • BUG记录:v1.0中存在线程安全问题(重票或者错票)

2、方式二:实现Runnable接口

  • 说明:在官方API文档中写着创建线程有两种方式,一是继承于Thread类,二是实现Runnable接口。实际上,在JDK 5.0后又新增的另外两种方式,实现Callable接口和使用线程池的方式。(一共就有四种创建线程的方式)
(1)使用步骤
  • 创建一个实现Runnable接口的类(implements)
  • 实现Runnable中的抽象方法run( )
  • 创建实现类的对象
  • 将此对象传到Thread类的构造器中并创建Thread对象
  • 通过Thread对象去调用start( )方法
/* 创建线程方式二:implements Runnable的方式*/
/* 1.创建一个实现Runnable接口的类(implements) */
class Thread1 implements Runnable{
    
    
/* 2.实现Runnable中的抽象方法run() */
    @Override
    public void run() {
    
    
        for (int i = 0; i < 100; i++) {
    
    
            if(i%2 == 0) System.out.println(i);
        }
    }
}
public class ThreadDemo4 {
    
    
    public static void main(String[] args) {
    
    
        /* 3.创建实现类的对象 */
        Thread1 t1 = new Thread1();
        /* 4.将此对象传到Thread类的构造器中并创建Thread对象 */
        Thread thread1 = new Thread(t1);
        //Thread thread1 = new Thread(new Thread1());  //3+4
        /* 5.通过Thread对象去调用start()方法 */
        thread1.start();
    }
}
(2)经典例子:多窗口售票v1.1
/* 实现Runnable接口的方式创建的线程 */
class Windows implements Runnable{
    
    
    private int tickets = 100;	//和v1.0不同,这里不用static
    @Override
    public void run() {
    
    
        while(tickets > 0){
    
    
            System.out.println(Thread.currentThread().getName()+"> NO."+tickets+" has been sold");
            tickets --;
        }
    }
}
public class ThreadDemo5 {
    
    
    public static void main(String[] args) {
    
    
        Windows windows = new Windows();
        /* 一个windows可传到三个Thread类的构造器中 */
        Thread t1 = new Thread(windows);
        Thread t2 = new Thread(windows);
        Thread t3 = new Thread(windows);
        /* 共用一个windows对象,也就共享100张票 */
        t1.setName("window_1");
        t2.setName("window_2");
        t3.setName("window_3");
        t1.start();
        t2.start();
        t3.start();
    }
}
/* 同样这里也有线程安全的问题,出现了重票的错票的问题,待解决 */

3、两种创建线程方式的对比

  • 开发中,优先考虑用实现Runnable接口的方式创建线程,原因如下:
    ✒️实现的方式没有类的单继承性的局限性
    ✒️实现的方式更适合处理多线程操作共享数据的情况
  • 联系:实际上,Thread类中也实现的Runnable接口
  • 相同点:两种方式都需要重写run( )方法,将线程执行的逻辑声明在run( )中

四、线程的生命周期

线程的生命周期

  • 其中,suspend( )、resume( )都是Deprecated - 已过时的,因为该方法可能会产生死锁。如果某一线程suspend挂起时持有锁,则在该线程重新开始之前任何线程都不能访问该资源。如果重新开始此线程的另一线程调用resume之前锁定该监视器,则会发生死锁。这类死锁通常会证明自己是“冻结”的进程。

五、线程的同步

1、多窗口售票的BUG

  • 重票、错票的产生:当某一个线程在操作tickets时,由于中间的时间差(可能极小却不可忽略)或者线程状态变化等问题,操作尚未完成就有其他线程也操作tickets。
    (PS:例如当某一个线程拿到tickets=100,还未到tickets--就有其他线程参与进来,这时也拿到了tickets=100。最终出现票号为99的重票;同样的当某一个线程操作完tickets--到等于0,还未结束循环时,另一个线程参与进来,拿到tickets=0就会出现票号为-1的错票)
  • 在Java中,通过以下两种同步方式来解决线程安全问题

2、方式一:同步代码块

synchronized(同步监视器){
    
    
    //需要被同步的代码
    ……  ……  ……
}
  • 操作共享数据的代码,即为需要被同步的代码
  • 共享数据:多个线程共同操作的变量。如:窗口售票中的tickets
  • 同步监视器(锁):任何一个类的对象都可以充当同步监视器。要求多个线程必须共用同一把锁
    ✒️实现Runnable接口的方式创建线程,可以考虑用this充当同步监视器
    ✒️继承Thread类的方式创建线程,可以考虑用当前类作同步监视器,总之需要保证锁是唯一的
  • 局限性:同步虽然解决的线程安全问题,但是在操作同步代码时只能有一个线程参与,其余等待,相当于此过程是单线程的。

3、方式二:同步方法

  • 同步方法仍然涉及到同步监视器,只是不需要显式的声明
  • 静态同步方法,同步监视器是:this
  • 非静态同步方法,同步监视器是:当前类

4、线程同步解决窗口售票的BUG

(1)经典例子:多窗口售票v2.0.0
/* 实现Runnable接口的方式创建的线程 */
/* 同步代码块解决线程安全问题 */
class Windows implements Runnable{
    
    
    private int tickets = 100;
    Object obj = new Object();
    @Override
    public void run() {
    
    
        while (true) {
    
    
            synchronized (obj) {
    
     //可传this,此时的this是windows的唯一对象
                /* 为了出现重票和错票的概率变大,这里加入sleep() */
                try{
    
    
                    Thread.sleep(50);
                }catch (InterruptedException e) {
    
    
                    e.printStackTrace();
                }
                if(tickets > 0) {
    
    
                    System.out.println(Thread.currentThread().getName() + "> NO." + tickets + " has been sold");
                    tickets --;
                }else{
    
    
                    break;
                }
            }
        }
    }
}
public class ThreadDemo5 {
    
    
    public static void main(String[] args) {
    
    
        Windows windows = new Windows();
        Thread t1 = new Thread(windows);
        Thread t2 = new Thread(windows);
        Thread t3 = new Thread(windows);
        t1.setName("window_1");
        t2.setName("window_2");
        t3.setName("window_3");
        t1.start();
        t2.start();
        t3.start();
    }
}
(2)经典例子:多窗口售票v2.0.1
/* 继承Thread类的方式创建的线程 */
/* 同步方法解决线程安全问题 */
class Windows extends Thread {
    
    
    public Windows(String threadName) {
    
    
        super(threadName);
    }
    private static int tickets = 100;
    private static boolean flag = true;//标志位,控制循环
    @Override
    public void run() {
    
    
        while(flag) {
    
    
            sellTickets();  //调用同步方法
        }
    }
    /* 同步方法实现,注意是静态的同步方法 */
    private static synchronized void sellTickets() {
    
    
        /* sleep提高错票重票的概率 */
        try{
    
    
            Thread.sleep(50);
        } catch (InterruptedException e) {
    
    
            e.printStackTrace();
        }
        if (tickets > 0) {
    
    
            System.out.println(Thread.currentThread().getName() + " > NO." + tickets + " has been sold");
            tickets--;
        }else{
    
    
            flag = false;
        }
    }
}

public class ThreadDemo6 {
    
    
    public static void main(String[] args) {
    
    
        Windows w1 = new Windows("windows_1");
        Windows w2 = new Windows("windows_2");
        Windows w3 = new Windows("windows_3");
        w1.start();
        w2.start();
        w3.start();
    }
}

5、线程死锁问题

  • 死锁:不同的线程分别占用对方的同步资源,都在等待对方释放自己所需的同步资源,造成了线程的死锁。
    ✒️出现死锁后不会报错和抛异常,所有线程处在阻塞状态,无法继续。
    ✒️避免嵌套同步、尽量减少同步资源的定义以避免出现死锁
/* 死锁的简单例子 */
public class ThreadDemo6 {
    
    
    public static void main(String[] args) {
    
    
        StringBuffer s1 = new StringBuffer();
        StringBuffer s2 = new StringBuffer();
        /* 线程1 - 匿名类*/
        new Thread(){
    
    
            @Override
            public void run() {
    
    
                synchronized (s1){
    
    
                    s1.append("a");
                    s2.append("1");
                    
                    /* sleep增加死锁概率 */
                    try{
    
    
                        Thread.sleep(200);
                    } catch (InterruptedException e) {
    
    
                        e.printStackTrace();
                    }
                    synchronized (s2){
    
    
                        s1.append("b");
                        s2.append("2");
                        System.out.println(s1);
                        System.out.println(s2);
                    }
                }
            }
        }.start();

        /* 当线程1先执行,拿到s1阻塞,此时线程2可能刚好拿了s2也阻塞,
       	 * 接着就出现线程1拿着s1等s2,线程2拿着s2等s1 */
        /* 线程2 */
        new Thread(new Runnable() {
    
    
            @Override
            public void run() {
    
    
                synchronized (s2){
    
    
                    s1.append("c");
                    s2.append("3");

                    /* sleep增加死锁概率 */
                    try{
    
    
                        Thread.sleep(200);
                    } catch (InterruptedException e) {
    
    
                        throw new RuntimeException(e);
                    }
                    synchronized (s1){
    
    
                        s1.append("d");
                        s2.append("4");
                        System.out.println(s1);
                        System.out.println(s2);
                    }
                }
            }
        }).start();
    }
}

6、方式三:Lock锁(JDK 5.0新增)

  • 问:synchronized和Lock的异同?
    答:虽然synchronized和Lock都能解决线程安全的问题,但是使用synchronized的方式在执行完相应的同步代码后,即使发生异常,也会自动释放占用的锁(同步监视器)。而用Lock的方式则需要手动的启动(lock( ) -上锁),手动的结束同步(unlock( ) - 解锁),在发生异常时若没有unlock( ),很有可能导致死锁,所以unlock( )需要写在finally中以保证异常情况下锁也能够被释放;且synchronized是非公平锁,Lock可以选择公平或非公平锁。(默认非公平)
  • 用法见下例代码:
    import java.util.concurrent.locks.Lock;
    import java.util.concurrent.locks.ReentrantLock;
    
    class Window implements Runnable{
          
          
        private int tickets = 100;
        /* 实例化ReentrantLock对象 */
        ReentrantLock l = new ReentrantLock();
        @Override
        public void run() {
          
          
            while(true){
          
          
                /* 给同步代码上锁 */
                l.lock();
                try{
          
          
                    if(tickets > 0){
          
          
                        try{
          
          
                            Thread.sleep(200);
                        } catch (InterruptedException e) {
          
          
                            e.printStackTrace();
                        }
                        System.out.println(Thread.currentThread().getName()+">\tNO."+tickets+" has been sold");
                        tickets --;
                    }else break;
                }finally {
          
          
                    /* 手动解锁 */
                    l.unlock();
                }
            }
        }
    }
    
    public class LockTest{
          
          
        public static void main(String[] args) {
          
          
            Window w = new Window();
            Thread t1 = new Thread(w);
            Thread t2 = new Thread(w);
            Thread t3 = new Thread(w);
            t1.setName("window 1");t1.start();
            t2.setName("window 2");t2.start();
            t3.setName("window 3");t3.start();
        }
    }
    
    

7、编程练习

银行有一个账户,有两个储户分别向同一个账户存3000元,每次存1000,存5次。每次存完打印账户余额。
问题:该程序是否有安全问题,如果有,如何解决?
【提示】
​ 1,明确哪些代码是多线程运行代码,须写入run()方法
​ 2,明确什么是共享数据。
​ 3,明确多线程运行代码中哪些语句是操作共享数据的。
【拓展问题】能否实现储户交替存钱的操作?

import java.util.Random;
import java.util.concurrent.locks.ReentrantLock;

class Account{
    
      //账号
    private double balance = 0; //当前余额
    /* 存钱并显示当前余额的方法,操作共享数据需要同步 */
    public synchronized void saveMoney(double money){
    
    
        if(money < 0) return;
        balance = balance + money;
        String customerName = Thread.currentThread().getName();
        System.out.println(customerName+"> "+"Deposit "+money+"¥ succeed.Your current balance is "+balance+"¥");
    }
}
class Customer extends Thread{
    
      //客户——线程
    private Account acc;
    private ReentrantLock lock = new ReentrantLock();
    /* 提供构造器传入账户,可实现多客户共用一个账户 */
    public Customer(Account account) {
    
    
        this.acc = account;
    }
    /* 重写run方法,根据题目 */
    @Override
    public void run() {
    
    
        for(int i=0;i<5;i++){
    
    
            /* 提高出现存钱问题概率,加入睡眠 */
            try{
    
    
                Thread.sleep(500);
            } catch (InterruptedException e) {
    
    
                e.printStackTrace();
            }
            acc.saveMoney(1000);
        }
    }
}
public class AccountTest{
    
    
    public static void main(String[] args) {
    
    
        Account acc = new Account();
        Customer c1 = new Customer(acc);
        Customer c2 = new Customer(acc);
        Customer c3 = new Customer(acc);
        c1.setName("Customer 1");
        c2.setName("Customer 2");
        c1.start();
        c2.start();
    }
}

在这里插入图片描述

六、线程间通信

  • wait( ):调用此方法当前线程进入阻塞状态,并释放同步监视器
  • notify( )、notifyAll( ):调用此方法会唤醒正在处于wait的线程,多个线程则唤醒优先级高的线程
    (PS:以上只能在同步代码块或者同步方法中使用-sychronized,且其调用者必须是同步监视器,否则会出现IllegalMonitorStateException)
  • 【拓展问题】实现两个储户交替存钱的操作?
import java.util.concurrent.locks.ReentrantLock;

class Account {
    
      //账号
    public double balance = 0; //当前余额

    /* 存钱并显示当前余额的方法,操作共享数据需要同步 */
    static ReentrantLock l = new ReentrantLock();

    public synchronized void saveMoney(double money) {
    
    
        if (money < 0) {
    
    
            System.out.println("error");
            return;
        }
        else {
    
    
            notify();
            balance = balance + money;
            String customerName = Thread.currentThread().getName();
            System.out.println(customerName + "> " + "Deposit " + money + "$ succeed.Your current balance is " + balance + "$");
            try {
    
    
                wait(500);
            } catch (InterruptedException e) {
    
    
                e.printStackTrace();
            }
        }
    }
}

class Customer extends Thread {
    
      //客户——线程
    private Account acc;
    /* 提供构造器传入账户,可实现多客户共用一个账户 */
    public Customer(Account account) {
    
    
        this.acc = account;
    }
    /* 重写run方法,根据题目 */
    @Override
    public void run() {
    
    
        for (int i = 0; i < 5; i++) {
    
    
            try {
    
    
                Thread.sleep(2000);
            } catch (InterruptedException e) {
    
    
                e.printStackTrace();
            }
            acc.saveMoney(1000);
        }
    }
}

public class AccountTest {
    
    
    public static void main(String[] args) {
    
    
        Account acc = new Account();
        Customer c1 = new Customer(acc);
        Customer c2 = new Customer(acc);
        c1.setName("Customer 1");
        c2.setName("Customer 2");
        c1.start();
        c2.start();
    }
}


在这里插入图片描述

七、创建线程另外两种方式(JDK 5.0新增)

1、实现Callable接口

  • 与Runnable相比,Callable的功能更加强大:
    ✒️可以有返回值
    ✒️可以抛出异常
    ✒️支持泛型
    ✒️可借助FutureTask类,比如获取返回结果
  • 使用步骤:
    ✒️创建一个实现Callable的实现类
    ✒️重写call方法,将线程执行的操作声明在call( )中
    ✒️new一个Callable接口实现类的对象传到FutureTask构造器中
    ✒️new一个FutureTask对象传到Thread类的构造器中
    ✒️使用Thread类对象调用start( )启动线程
  • 下面使用实现Callable接口的方式创建线程,依然是上面"储户存钱"的问题
  package com.ThreadDemos.java;
  
  import java.util.concurrent.Callable;
  import java.util.concurrent.ExecutionException;
  import java.util.concurrent.FutureTask;
  
  class Account{
    
    
      public double balance = 0;
  }
  /* 1.创建一个实现Callable的实现类 */
  class Customer implements Callable {
    
    
      static Account acc = new Account();
  /* 2.重写call方法,将线程执行的操作声明在call()中 */
      @Override
      public Object call() throws Exception {
    
    
          String threadName = Thread.currentThread().getName();
          for (int i = 0; i < 3; i++) {
    
    
              synchronized (acc) {
    
    
                  Thread.sleep(1000);
                  acc.balance += 1000;
                  System.out.println(threadName + "转入金额:"+1000+"$");
              }
          }
          return acc.balance;
      }
  }
  
  public class CallableTest {
    
    
      public static void main(String[] args) {
    
    
          Customer customer1 = new Customer();
          Customer customer2 = new Customer();
          /* 3.new一个Callable接口实现类的对象传到FutureTask构造器中 */
          FutureTask futureTask1 = new FutureTask(customer1);
          FutureTask futureTask2 = new FutureTask(customer2);
          /* 4.new一个FutureTask对象传到Thread类的构造器中 */
          Thread t1 = new Thread(futureTask1);
          Thread t2 = new Thread(futureTask2);
          /* 5.使用Thread类对象调用start()启动线程 */
          t1.setName("客户1");
          t2.setName("客户2");
          t1.start();
          t2.start();
          try {
    
    
              if((double)futureTask1.get() > (double)futureTask2.get()){
    
    
                  System.out.println("当前账户余额是:"+futureTask1.get()+"$");
              }else {
    
    
                  System.out.println("当前账户余额是:"+futureTask2.get()+"$");
              }
          } catch (InterruptedException | ExecutionException e) {
    
    
              e.printStackTrace();
          }
      }
  }

在这里插入图片描述

2、使用线程池创建

  • 概述:提前创建好多个线程组成线程池,使用时从线程池中直接获取,使用完重新放回池中。可以避免频繁创建、销毁的操作,实现重复利用;对于创建和销毁、使用线程量大的资源(如:高并发线程),能提高响应速度、降低资源销毁。
  • 优点:①减少了创建线程的时间,提高响应速度;②重复利用线程池中的线程,降低了资源消耗;③便于线程管理
  • 使用步骤:
    ✒️提供指定线程数量的线程池
    ✒️提供实现Runnable接口或者Callable接口实现类对象
    ✒️关闭线程池
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadPoolExecutor;

public class ThreadPollTest {
    
    
    public static void main(String[] args) {
    
    
        /* 1.提供指定线程数量的线程池 */
        ExecutorService service = Executors.newFixedThreadPool(10);
        /* 2.设置线程池的属性[可选] */
        ThreadPoolExecutor threadPoolExecutor = (ThreadPoolExecutor) service;
        threadPoolExecutor.setCorePoolSize(10); //设置核心池大小
        threadPoolExecutor.setMaximumPoolSize(10);  //设置最大线程数
        /* 3.提供实现Runnable接口或者Callable接口实现类对象 */
        service.submit(new Runnable() {
    
    
            @Override
            public void run() {
    
    
                for (int i = 0; i < 100; i++) {
    
    
                    if(i%2 == 0) System.out.print(i+" ");
                }
            }
        });
        /* 4.关闭线程池 */
        service.shutdown();
    }
}

猜你喜欢

转载自blog.csdn.net/weixin_54429787/article/details/127310038