Java多线程(下)

一、线程安全

1.1、线程安全

如果有多个线程在同时运行,而这些线程可能会同时运行这段代码。程序每次运行结果和单线程运行的结果是一样的,而且其他的变量的值也和预期的是一样的,就是线程安全的。
通过模拟卖票来演示并发问题:
模拟票:

public class Ticket implements Runnable {
    private int ticket = 100;

    /*
     * 执行卖票操作
     */
    @Override
    public void run() {
        //每个窗口卖票的操作
        //窗口 永远开启
        while (true) {
            if (ticket > 0) {//有票 可以卖
                //出票操作
                //使用sleep模拟一下出票时间
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                // TODO Auto‐generated catch block
                    e.printStackTrace();
                }
                //获取当前线程对象的名字
                String name = Thread.currentThread().getName();
                System.out.println(name + "正在卖:" + ticket--);
            }
        }
    }
}

测试类:

public class Demo {
	public static void main(String[] args) {
		//创建线程任务对象
		Ticket ticket = new Ticket();
		//创建三个窗口对象
		Thread t1 = new Thread(ticket, "窗口1");
		Thread t2 = new Thread(ticket, "窗口2");
		Thread t3 = new Thread(ticket, "窗口3");
		//同时卖票
		t1.start();
		t2.start();
		t3.start();
	}
}

运行结果可能会有这种情况:

  • 同一张票被卖了两次
  • 出现了0和-1张票

这种问题,几个窗口(线程)票数不同步了,这种问题称为线程不安全。

线程安全问题都是由全局变量及静态变量引起的。若每个线程中对全局变量、静态变量只有读操作,而无写操作,一般来说,这个全局变量是线程安全的;若有多个线程同时执行写操作,一般都需要考虑线程同步,否则的话就可能影响线程安全。

1.2、线程同步

  • 同一个对象被多个线程同时操作。当我们使用多个线程访问同一资源的时候,且多个线程中对资源有写的操作,就容易出现线程安全问题。
  • 要解决上述多线程并发访问一个资源的安全性问题:也就是解决重复票与不存在票问题,Java中提供了同步机制(synchronized)来解决。
  • 线程同步其实就是一种等待机制,多个需要同时访问此对象的线程进入这个对象的等待池形成队列,等待前面线程使用完毕,下一个线程再使用
  • 在线程中,只有队列并不能保证线程安全,还需要加锁

由于同一进程的多个线程共享同一块存储空间,在带来方便的同时,也带来了访问冲突问题,为了保证数据在方法中被访问时的正确性,在访问时加入锁机制 synchronized,当一个线程获得对象的排它锁,独占资源,其他线程必须等待,使用后释放锁即可。

  • 同步机制存在以下问题:

    • 一个线程持有锁会导致其他所有需要此锁的线程挂起
    • 在多线程竞争下,加锁,释放锁会导致比较多的上下文切换和调度延时,引起性能问题
    • 如果一个优先级高的线程等待一个优先级低的线程释放锁,会导致优先级倒置,引起性能问题
  • Java提供了三种方式完成同步操作:

    • 同步代码块。
    • 同步方法。
    • 锁机制。

1.3、三大不安全案例

  1. 模拟卖票
    重复数:多个线程都抢到同一个票
    负数:剩最后一张票时,同时抢到,其中一个拿走后,剩下两个就会变成负数

    // 不安全的买票
    // 线程不安全,有负数,重复数
    public class UnsafeBuyTicket {
        public static void main(String[] args) {
            BuyTicket station = new BuyTicket();
    
            new Thread(station, "客户").start();
            new Thread(station, "第三方抢票").start();
            new Thread(station, "黄牛").start();
        }
    }
    
    
    class BuyTicket implements Runnable {
    
        // 票
        private int ticketNums = 10;
        boolean flag = true; // 外部停止方式
    
        @Override
        public void run() {
    
            // 买票
            while (flag) {
                buy();
            }
        }
    
        private void buy() {
            // 判断是否有票
            if (ticketNums <= 0) {
                flag = false;
                return;
            }
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
    
            // 买票
            System.out.println(Thread.currentThread().getName() + "拿到第" + ticketNums-- + "张票");
        }
    }
    
  2. 银行取钱

    // 不安全的取钱
    // 两个人去银行取同一个账户的钱
    public class UnsafeBank {
        public static void main(String[] args) {
            // 账户
            Account account = new Account(100, "结婚基金");
    
            Drawing you = new Drawing(account, 50, "you");
            Drawing wife = new Drawing(account, 100, "wife");
    
            you.start();
            wife.start();
        }
    }
    
    
    // 账户
    class Account {
        int money; // 余额
        String name; // 卡名
    
        public Account(int money, String name) {
            this.money = money;
            this.name = name;
        }
    }
    
    // 银行  取款
    class Drawing extends Thread {
        Account account; // 账户
        // 取了多少钱
        int drawingMoney;
        // 现在手里有多少钱
        int nowMoney;
    
        public Drawing(Account account, int drawingMoney, String name) {
            super(name);
            this.account = account;
            this.drawingMoney = drawingMoney;
        }
    
        // 取钱
        @Override
        public void run() {
            // 判断有没有钱
            if (account.money - drawingMoney < 0) {
                System.out.println(Thread.currentThread().getName() + "钱不够,取不了");
                return;
            }
    
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
    
            // 卡内余额 = 余额 - 取的钱
            account.money = account.money - drawingMoney;
            // 手里的钱
            nowMoney = nowMoney + drawingMoney;
            System.out.println(account.name + "余额为:" + account.money);
            // this.getName() 等价于 Thread.currentThread().getName()
            System.out.println(this.getName() + "手里的钱" + nowMoney);
        }
    }
    
  3. 线程不安全的集合

    // 线程不安全的集合
    public class UnsafeList {
        public static void main(String[] args) {
            List<String> list = new ArrayList<>();
            for (int i = 0; i < 10000; i++) {
                new Thread(() -> {
                    list.add(Thread.currentThread().getName());
                }).start();
            }
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(list.size());
        }
    }
    

    ArrayList集合线程不安全的原因:
    ArrayList源码:

    public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable
    {
       /**
        * 列表元素集合数组
        * 如果新建ArrayList对象时没有指定大小,那么会将EMPTY_ELEMENTDATA赋值给elementData,
        * 并在第一次添加元素时,将列表容量设置为DEFAULT_CAPACITY 
        */
       transient Object[] elementData; 
    
       /**
        * 列表大小,elementData中存储的元素个数,即集合长度
        */
       private int size;
    }
    

    ArrayList的add方法:

    public boolean add(E e) {
    
        /**
         * 添加一个元素时,做了如下三步操作
         * 1.判断列表的capacity容量是否足够,是否需要扩容
         * 2.真正将元素放在列表的元素数组里面
         * 3.将集合长度+1
         */
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        elementData[size++] = e;
        return true;
    }
    
    • 索引越界异常:
      列表大小为9时,即size=9
      线程A开始进入add方法,这时它获取到size的值为9,调用ensureCapacityInternal方法进行容量判断。
      线程B此时也进入add方法,它获取到size的值也为9,也开始调用ensureCapacityInternal方法。
      线程A发现需求大小为10,而elementData的大小就为10,可以容纳。于是它不再扩容,返回。
      线程B也发现需求大小为10,也可以容纳,返回。
      线程A开始进行设置值操作, elementData[size++] = e 操作。此时size变为10。
      线程B也开始进行设置值操作,它尝试设置elementData[10] = e,而elementData没有进行过扩容,它的下标最大为9。于是此时会报出一个数组越界的异常ArrayIndexOutOfBoundsException。
    • 一个线程的值覆盖另一个线程添加的值
      列表大小为0,即size=0
      线程A开始添加一个元素,值为A。此时它执行第一条操作,将A放在了elementData下标为0的位置上。
      接着线程B刚好也要开始添加一个值为B的元素,且走到了第一步操作。此时线程B获取到size的值依然为0,于是它将B也放在了elementData下标为0的位置上。
      线程A开始将size的值增加为1
      线程B开始将size的值增加为2

    总结:造成ArrayList线程不安全的因素就是size值,当A、B两个线程都执行到add操作时,如果A线程添加元素后,没有更新集合长度,B线程执行时就会覆盖A线程的值,并产生一个空的位置。
    如果是添加最后一个元素时,A线程添加元素并更新集合长度,B线程添加元素时就会造成索引越界。

二、线程同步

2.1、同步代码块

同步代码块: synchronized 关键字可以用于方法中的某个区块中,表示只对这个区块的资源实行互斥访问。
格式:

synchronized(同步锁Obj){
	需要同步操作的代码
}

同步锁:

  • 对象的同步锁只是一个概念,可以想象为在对象上标记了一个锁。
    • 锁对象 可以是任意类型。
    • 多个线程对象 要使用同一把锁。
  • Obj 称之为 同步监视器
    • Obj 可以是任何对象 , 但是推荐使用共享资源作为同步监视器
    • 同步方法中无需指定同步监视器,因为同步方法的同步监视器就是this,就是这个对象本身 , 或者是class
  • 同步监视器的执行过程
    • 第一个线程访问 , 锁定同步监视器 , 执行其中代码
    • 第二个线程访问 , 发现同步监视器被锁定 , 无法访问
    • 第一个线程访问完毕 , 解锁同步监视器
    • 第二个线程访问, 发现同步监视器没有锁 , 然后锁定并访

取钱案例:

// 取钱
// synchronized 默认锁的是this
@Override
public void run() {
    // 锁的对象就是变化的量,需要增删改的对象
    synchronized (account) {
        // 判断有没有钱
        if (account.money - drawingMoney < 0) {
            System.out.println(Thread.currentThread().getName() + "钱不够,取不了");
            return;
        }

        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        // 卡内余额 = 余额 - 取的钱
        account.money = account.money - drawingMoney;
        // 手里的钱
        nowMoney = nowMoney + drawingMoney;
        System.out.println(account.name + "余额为:" + account.money);
        // this.getName() 等价于 Thread.currentThread().getName()
        System.out.println(this.getName() + "手里的钱" + nowMoney);
    }
}

2.2、同步方法

同步方法:使用synchronized修饰的方法,就叫做同步方法,保证A线程执行该方法的时候,其他线程只能在方法外等着。
synchronized方法控制对 “对象” 的访问,每个对象对应一把锁,每个 synchronized 方法都必须获得调用该方法的对象的锁才能执行,否则线程会阻塞,方法一旦执行,就独占该锁,直到该方法返回才释放锁,后面被阻塞的线程才能获得这个锁,继续执行
缺陷:若将一个大的方法申明为synchronized将会影响效率

格式:

public synchronized void method(){
	可能会产生线程安全问题的代码
}

同步锁是谁?
对于非static方法,同步锁就是this。
对于static方法,我们使用当前方法所在类的字节码对象(类名.class)。

卖票案例:

    private synchronized  void buy() {
        // 判断是否有票
        if (ticketNums <= 0) {
            flag = false;
            return;
        }
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        // 买票
        System.out.println(Thread.currentThread().getName() + "拿到第" + ticketNums-- + "张票");
    }

2.3、死锁

多个线程各自占有一些共享资源,并且互相等待其他线程占有的资源才能运行,而导致两个或者多个线程都在等待对方释放资源,都停止执行的情形。
某一个同步块同时拥有 “ 两个以上对象的锁 ” 时,就可能会发生 “ 死锁 ” 的问题。

// 死锁:多个线程互相抱着对方需要的资源,然后形成僵持
public class DeadLock {
    public static void main(String[] args) {
        MakeUp m1 = new MakeUp(0, "一号");
        MakeUp m2 = new MakeUp(1, "二号");
        m1.start();
        m2.start();
    }
}

// 口红
class Lipstick {

}

// 镜子
class Mirror {

}


class MakeUp extends Thread {
    // 需要的资源只有一份,用static来保证只有一份
    static Lipstick lipstick = new Lipstick();
    static Mirror mirror = new Mirror();

    int choice; // 选择
    String userName; // 使用化妆品的人

    public MakeUp(int choice, String userName) {
        this.choice = choice;
        this.userName = userName;
    }

    @Override
    public void run() {
        // 化妆
        try {
            makeUp();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    // 化妆,互相持有对方的锁,就是需要拿到对方的资源
    private void makeUp() throws InterruptedException {
        if (choice == 0) {
            // 获得口红的锁
            synchronized (lipstick) {
                System.out.println(this.userName + "获得口红的锁");
                Thread.sleep(1000);

                // 一秒钟后想获得镜子
                synchronized (mirror) {
                    System.out.println(this.userName + "获得镜子的锁");
                }
            }
        }else {
            // 获得镜子的锁
            synchronized (mirror) {
                System.out.println(this.userName + "获得镜子的锁");
                Thread.sleep(2000);

                // 一秒钟后想获得口红
                synchronized (lipstick) {
                    System.out.println(this.userName + "获得口红的锁");
                }
            }
        }
    }
}


// 输出结果
/* 
一号获得口红的锁
二号获得镜子的锁
 */

产生死锁的四个必要条件

  1. 互斥条件:一个资源每次只能被一个进程使用。
  2. 请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放。
  3. 不剥夺条件 : 进程已获得的资源,在末使用完之前,不能强行剥夺。
  4. 循环等待条件 : 若干进程之间形成一种头尾相接的循环等待资源关系

只要想办法破其中的任意一个或多个条件 就可以避免死锁发生

2.4、Lock锁

从JDK 5.0开始,Java提供了更强大的线程同步机制——通过显式定义同步锁对象来实现同步。同步锁使用Lock对象充当。
java.util.concurrent.locks.Lock接口是控制多个线程对共享资源进行访问的工具。 锁提供了对共享资源的独占访问,每次只能有一个线程对Lock对象加锁,线程开始访问共享资源之前应先获得Lock对象
java.util.concurrent.locks.Lock 机制提供了比synchronized代码块和synchronized方法更广泛的锁定操作,同步代码块/同步方法具有的功能Lock都有,除此之外更强大,更体现面向对象。

ReentrantLock(可重入锁)类实现了 Lock,它拥有与 synchronized 相同的并发性和内存语义,在实现线程安全的控制中,比较常用的是ReentrantLock,可以显式加锁、释放锁。

  • Lock锁也称同步锁,加锁与释放锁方法化了,如下:

    • public void lock():加同步锁。
    • public void unlock():释放同步锁。
    // 测试lock锁
    public class TestLock {
        public static void main(String[] args) {
            TestLock2 testLock2 = new TestLock2();
    
            new Thread(testLock2).start();
            new Thread(testLock2).start();
            new Thread(testLock2).start();
        }
    }
    
    
    class TestLock2 implements Runnable {
        int ticketNums = 10;
    
        // 定义Lock锁
        private final ReentrantLock lock = new ReentrantLock();
    
        @Override
        public void run() {
            while (true) {
                try {
                    lock.lock(); // 加锁
                    if (ticketNums > 0) {
                        try {
                            Thread.sleep(1000);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        System.out.println(ticketNums--);
                    }else {
                        break;
                    }
                }finally {
                    lock.unlock(); // 解锁
                }
            }
        }
    }
    

synchronized 与 Lock 的对比

  • Lock是显式锁(手动开启和关闭锁,别忘记关闭锁)synchronized是隐式锁,出了作用域自动释放
  • Lock只有代码块锁,synchronized有代码块锁和方法锁
  • 使用Lock锁,JVM将花费较少的时间来调度线程,性能更好。并且具有更好的扩展性(提供更多的子类)
  • 优先使用顺序:
    • Lock > 同步代码块(已经进入了方法体,分配了相应资源)> 同步方法(在方法体之外)

三、等待唤醒机制

3.1、线程间通信

概念:多个线程在处理同一个资源,但是处理的动作(线程的任务)却不相同。
比如:线程A用来生成包子的,线程B用来吃包子的,包子可以理解为同一资源,线程A与线程B处理的动作,一个是生产,一个是消费,那么线程A与线程B之间就存在线程通信问题。

为什么要处理线程间通信
多个线程并发执行时, 在默认情况下CPU是随机切换线程的,当我们需要多个线程来共同完成一件任务,并且我们希望他们有规律的执行, 那么多线程之间需要一些协调通信,以此来帮我们达到多线程共同操作一份数据。

如何保证线程间通信有效利用资源
多个线程在处理同一个资源,并且任务不同时,需要线程通信来帮助解决线程之间对同一个变量的使用或操作。 就是多个线程在操作同一份数据时, 避免对同一共享变量的争夺。也就是我们需要通过一定的手段使各个线程能有效的利用资源。而这种手段即—— 等待唤醒机制。

3.2、等待唤醒机制

等待唤醒机制概念
这是多个线程间的一种协作机制。谈到线程我们经常想到的是线程间的竞争(race),比如去争夺锁,但这并不是全部,线程间也会有协作机制。
就是在一个线程进行了规定操作后,就进入等待状态(wait()), 等待其他线程执行完他们的指定代码过后再将其唤醒(notify());在有多个线程进行等待时, 如果需要,可以使用 notifyAll()来唤醒所有的等待线程。

wait/notify 就是线程间的一种协作机制。

等待唤醒中的方法
等待唤醒机制就是用于解决线程间通信的问题的,使用到的3个方法的含义如下:

  1. wait:线程不再活动,不再参与调度,进入 wait set 中,因此不会浪费 CPU 资源,也不会去竞争锁了,这时的线程状态即是 WAITING。它还要等着别的线程执行一个特别的动作,也即是“通知(notify)”在这个对象上等待的线程从wait set 中释放出来,重新进入到调度队列(ready queue)中
  2. notify:则选取所通知对象的 wait set 中的一个线程释放;例如,餐馆有空位置后,等候就餐最久的顾客最先入座。
  3. notifyAll:释放所通知对象的 wait set 上的全部线程。

注意:
哪怕只通知了一个等待的线程,被通知线程也不能立即恢复执行,因为它当初中断的地方是在同步块内,而此刻它已经不持有锁,所以她需要再次尝试去获取锁(很可能面临其它线程的竞争),成功后才能在当初调用 wait 方法之后的地方恢复执行。
总结如下:
如果能获取锁,线程就从 WAITING 状态变成 RUNNABLE 状态;
否则,从 wait set 出来,又进入 entry set,线程就从 WAITING 状态又变成 BLOCKED 状态

调用wait和notify方法需要注意的细节:

  1. wait方法与notify方法必须要由同一个锁对象调用。因为:对应的锁对象可以通过notify唤醒使用同一个锁对象调用的wait方法后的线程。
  2. wait方法与notify方法是属于Object类的方法的。因为:锁对象可以是任意对象,而任意对象的所属类都是继承了Object类的。
  3. wait方法与notify方法必须要在同步代码块或者是同步函数中使用。因为:必须要通过锁对象调用这2个方法。

3.3、线程间通信–管程法

// 测试:生产者消费者模型-->利用缓冲区解决:管程法

// 生产者,消费者,产品,缓冲区

public class TestPC {
    public static void main(String[] args) {
        SynContainer container = new SynContainer();
        
        new Productor(container).start();
        new Consumer(container).start();
    }
}


// 生产者
class Productor extends Thread {
    SynContainer container;
    public Productor(SynContainer container) {
        this.container = container;
    }
    // 生产

    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            container.push(new Chicken(i));
            System.out.println("生产了" + i + "只鸡");
        }
    }
}


// 消费者
class Consumer extends Thread {
    SynContainer container;
    public Consumer(SynContainer container) {
        this.container = container;
    }

    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println("消费了" + container.pop().id + "只鸡");
        }
    }
}

// 产品
class Chicken {
    int id; // 产品编号
    public Chicken(int id) {
        this.id = id;
    }
}


// 缓冲区
class SynContainer {
    // 容器大小
    Chicken[] chickens = new Chicken[10];
    // 容器计数器
    int count = 0;
    
    // 生产者放入产品
    public synchronized void push(Chicken chicken) {
        // 如果容器满了,就需要等待消费者消费
        if (count == chickens.length) {
            // 通知消费者消费,生产者等待
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        
        // 如果没有满,就需要丢入产品
        chickens[count] = chicken;
        count++;
        
        // 可以通知消费者消费了
        this.notifyAll();
    }
    
    // 消费者消费产品
    public synchronized Chicken pop() {
        // 判断能否消费
        if (count == 0) {
            // 等待生产者生产,消费者等待
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        // 如果可以消费
        count--;
        Chicken chicken = chickens[count];
        
        // 通知生产者生产
        this.notifyAll();
        return chicken;
    }
}

3.4、信号灯法

// 测试:生产者消费者问题2:信号灯法,标志位解决
public class TestPC2 {
    public static void main(String[] args) {
        TV tv = new TV();
        new Player(tv).start();
        new Watcher(tv).start();
    }
}


// 生产者-->演员
class Player extends Thread{
    TV tv;
    public Player(TV tv) {
        this.tv = tv;
    }

    @Override
    public void run() {
        for (int i = 0; i < 20; i++) {
            if (i % 2 == 0) {
                this.tv.play("电视剧");
            }else {
                this.tv.play("广告");
            }
        }
    }
}

// 消费者-->观众
class Watcher extends Thread{
    TV tv;
    public Watcher(TV tv) {
        this.tv = tv;
    }

    @Override
    public void run() {
        for (int i = 0; i < 20; i++) {
            tv.watch();
        }
    }
}

// 产品-->节目
class TV {
    // 演员表演,观众等待   true
    String voice; // 表演的节目
    boolean flag = true;
    public synchronized void play (String voice) {
        if (!flag) {
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println("演员表演了" + voice);
        // 通知观众观看
        this.notifyAll();
        this.voice = voice;
        this.flag = !this.flag;
    }

    // 观众观看,演员等待  false
    public synchronized void watch() {
        if (flag) {
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println("观众观看了" + voice);
        // 通知演员表演
        this.notifyAll();
    }
}

四、线程池

4.1、概念

其实就是一个容纳多个线程的容器,其中的线程可以反复使用,省去了频繁创建线程对象的操作,无需反复创建线程而消耗过多资源。

  • 好处
    • 提高响应速度(减少了创建新线程的时间)
    • 降低资源消耗(重复利用线程池中线程,不需要每次都创建)
    • 便于线程管理(…)
      • corePoolSize:核心池的大小
      • maximumPoolSize:最大线程数
      • keepAliveTime:线程没有任务时最多保持多长时间后会终止

4.2、使用

Java里面线程池的顶级接口是 java.util.concurrent.Executor,但是严格意义上讲 Executor 并不是一个线程池,而只是一个执行线程的工具。真正的线程池接口是 java.util.concurrent.ExecutorService
要配置一个线程池是比较复杂的,尤其是对于线程池的原理不是很清楚的情况下,很有可能配置的线程池不是较优的,因此在 java.util.concurrent.Executors 线程工厂类里面提供了一些静态工厂,生成一些常用的线程池。官方建议使用Executors工程类来创建线程池对象。

  • Executors类中有个创建线程池的方法如下:
    • public static ExecutorService newFixedThreadPool(int nThreads) :返回线程池对象。(创建的是有界线程池,也就是池中的线程个数可以指定最大数量)
    • 获取到了一个线程池ExecutorService 对象,调用一个使用线程池对象的方法:public Future<?> submit(Runnable task) :获取线程池中的某一个线程对象,并执行

Future接口:用来记录线程任务执行完毕后产生的结果。线程池创建与使用。

使用线程池中线程对象的步骤:

  1. 创建线程池对象。
  2. 创建Runnable接口子类对象。(task)
  3. 提交Runnable接口子类对象。(take task)
  4. 关闭线程池(一般不做)。
// 测试:线程池
public class TestPool {
    public static void main(String[] args) {
        // 1、创建线程池
        // 参数为线程池大小
        ExecutorService service = Executors.newFixedThreadPool(10);

        service.execute(new MyThread());
        service.execute(new MyThread());
        service.execute(new MyThread());

        // 关闭连接
        service.shutdown();
    }
}

class MyThread implements Runnable{
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName());
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值