当前位置: 首页 > news >正文

Java高级-多线程

5aa1e6d795bbc

本篇讲解java多线程

基本概念: 程序、进程、线程

**程序(program)**是为完成特定任务、用某种语言编写的一组指令的集合。即指一段静态的代码,静态对象。

**进程(process)**是程序的一次执行过程,或是正在运行的一个程序。是一个动态的过程:有它自身的产生、存在和消亡的过程。 ——生命周期

  • 如:运行中的QQ,运行中的MP3播放器
  • 程序是静态的,进程是动态的
  • 进程作为资源分配的单位, 系统在运行时会为每个进程分配不同的内存区域

线程(thread),进程可进一步细化为线程,是一个程序内部的一条执行路径。

若一个进程同一时间并行执行多个线程,就是支持多线程的

线程作为调度和执行的单位,每个线程拥有独立的运行栈和程序计数器(pc),线程切换的开销小

一个进程中的多个线程共享相同的内存单元/内存地址空间 --> 它们从同一堆中分配对象,可以访问相同的变量和对象。这就使得线程间通信更简便、高效。但多个线程操作共享的系统资源可能就会带来安全的隐患。

进程与线程图解

单核CPU和多核CPU的理解

  • 单核CPU,其实是一种假的多线程,因为在一个时间单元内,也只能执行一个线程的任务。例如:虽然有多车道,但是收费站只有一个工作人员在收费,只有收了费才能通过,那么CPU就好比收费人员。如果有某个人不想交钱, 那么收费人员可以把他“挂起”(晾着他,等他想通了,准备好了钱,再去收费) 。 但是因为CPU时间单元特别短,因此感觉不出来。
  • 如果是多核的话,才能更好的发挥多线程的效率。(现在的服务器都是多核的)
  • 一个Java应用程序java.exe,其实至少有三个线程: main()主线程, gc()垃圾回收线程,异常处理线程。当然如果发生异常,会影响主线程。

并行与并发

  • 并行: 多个CPU同时执行多个任务。比如:多个人同时做不同的事。
  • 并发: 一个CPU(采用时间片)同时执行多个任务。比如:秒杀、多个人做同一件事。

使用多线程的优点

  1. 提高应用程序的响应。对图形化界面更有意义,可增强用户体验。

  2. 提高计算机系统CPU的利用率

  3. 改善程序结构。将既长又复杂的进程分为多个线程,独立运行,利于理解和修改

线程的创建和使用

Java语言的JVM允许程序运行多个线程,它通过java.lang.Thread类来体现。

  • Thread类的特性

    每个线程都是通过某个特定Thread对象的run()方法来完成操作的,经常把run()方法的主体称为线程体
    通过该Thread对象的start()方法来启动这个线程,而非直接调用run()

Thread类

Thread类的使用

  1. 构造器

Thread(): 创建新的Thread对象

Thread(String threadname): 创建线程并指定线程实例名

Thread(Runnable target): 指定创建线程的目标对象,它实现了Runnable接口中的run方法

Thread(Runnable target, String name): 创建新的Thread对象

  1. 创建线程的两种方式

    JDK1.5之前创建新执行线程有两种方法:

    • 继承Thread类的方式

      1. 定义子类继承Thread类。

      2. 子类中重写Thread类中的run方法。

      3. 创建Thread子类对象,即创建了线程对象。

      4. 调用线程对象start()方法:启动线程,调用run方法

        //1. 创建一个继承于Thread类的子类
        class MyThread extends Thread {//2. 重写Thread类的run()@Overridepublic void run() {for (int i = 0; i < 100; i++) {if(i % 2 == 0){System.out.println(Thread.currentThread().getName() + ":" + i);}}}
        }//另一个类中
        public static void main(String[] args) {//3. 创建Thread类的子类的对象MyThread t1 = new MyThread();//4.通过此对象调用start():①启动当前线程 ② 调用当前线程的run()t1.start();
        }
        

        注意:

        1. 调用start()方法,如果调用的是run(),并不会开启新的线程,而是当前线程直接执行内部的代码,和之前定义方法然后让对象调用是一样的。
        2. 如果想再生成一个线程,那就再new一个线程对象。不可以还让已经start()的线程去执行。会报IllegalThreadStateException

        使用匿名子类简化创建方式:

        new Thread(){public void run(){for (int i = 0; i < 100; i++) {System.out.println(Thread.currentThread().getName() + ":" + i);}}
        }.start();
        
    • 实现Runnable接口的方式

      1. 创建一个实现了Runnable接口的类
      2. 实现类去实现Runnable中的抽象方法:run()
      3. 创建实现类的对象
      4. 将此对象作为参数传递到Thread类的构造器中,创建Thread类的对象
      5. 通过Thread类的对象调用start()
      class MyThreadObj implements Runnable{//1. 创建一个实现了Runnable接口的类@Overridepublic void run() {//2.实现类去实现Runnable中的抽象方法:run()for (int i = 0; i < 100; i++) {System.out.println(Thread.currentThread().getName() + ":" + i);}}
      }public class ThreadTest {public static void main(String[] args) {//3.创建实现类的对象MyThreadObj myThreadObj = new MyThreadObj();//4.将此对象作为参数传递到Thread类的构造器中,创建Thread类的对象Thread thread = new Thread(myThreadObj);thread.setName("hengxing");thread.start();//5.通过Thread类的对象调用`start()`}
      }
      
  2. 比较创建线程的两种方式

    开发中:优先选择–>实现Runnable接口的方式

    原因:

    1. 实现的方式没有类的单继承性的局限性
    2. 实现的方式更适合来处理多个线程有共享数据的情况。

    联系:public class Thread implements Runnablethread其实也是实现了Runnable接口,实际上和第二种方式没区别

    相同点:两种方式都需要重写run(),将线程要执行的逻辑声明在run()中。

Thread类的常用方法

方法作用
void start()启动线程,并执行对象的run()方法
run()线程在被调度时执行的操作
String getName()返回线程的名称
void setName(String name)设置该线程名称
static Thread currentThread()返回当前线程。
在Thread子类中就是this,通常用于主线程和Runnable实现类
static void yield()线程让步
- 暂停当前正在执行的线程,把执行机会让给优先级相同或更高的线程
- 若队列中没有同优先级的线程,忽略此方法
join()当某个程序执行流中调用其他线程的join()方法时, 调用线程将被阻塞,直到join()方法加入的 join 线程执行完为止
低优先级的线程也可以获得执行
但是要注意:执行前确保线程已被启动。这个方法是等待join的线程完成,但是你如果连线程都没有开始执行,那不就直接结束了吗?
static void sleep(long millis)令当前活动线程在指定时间段内放弃对CPU控制,使其他线程有机会被执行,时间到后重排队。
抛出InterruptedException异常
boolean isAlive()返回boolean,判断线程是否还活着

线程的调度

调度策略

  • 时间片
image-20230131153103245
  • 抢占式: 高优先级的线程抢占CPU

Java的调度方法

  • 同优先级线程组成先进先出队列(先到先服务),使用时间片策略
  • 对高优先级,使用优先调度的抢占式策略

线程的优先级

  • MAX_PRIORITY: 10
  • MIN _PRIORITY: 1
  • NORM_PRIORITY: 5 --> 默认优先级

如何获取和设置当前线程的优先级:

getPriority(): 获取线程的优先级

setPriority(int p): 设置线程的优先级

说明:

高优先级的线程要抢占低优先级线程cpu的执行权。但是只是从概率上讲,高优先级的线程高概率的情况下被执行。并不意味着只有当高优先级的线程执行完以后,低优先级的线程才执行。

线程创建时继承父线程的优先级

理解线程

线程的分类

Java中的线程分为两类:一种是守护线程,一种是用户线程

  • 它们在几乎每个方面都是相同的,唯一的区别是判断JVM何时离开
  • 守护线程是用来服务用户线程的,通过在start()方法前调用thread.setDaemon(true)可以把一个用户线程变成一个守护线程
  • Java垃圾回收就是一个典型的守护线程
  • 若JVM中都是守护线程,当前JVM将退出

线程的生命周期

线程有五种状态:

  1. 新建: 当一个Thread类或其子类的对象被声明并创建时,新生的线程对象处于新建状态
  2. 就绪: 处于新建状态的线程被start()后,将进入线程队列等待CPU时间片,此时它已具备了运行的条件,只是没分配到CPU资源
  3. 运行: 当就绪的线程被调度并获得CPU资源时,便进入运行状态, run()方法定义了线程的操作和功能
  4. 阻塞: 在某种特殊情况下,被人为挂起或执行输入输出操作时,让出 CPU 并临时中止自己的执行,进入阻塞状态
  5. 死亡: 线程完成了它的全部工作或线程被提前强制性地中止或出现异常导致结束

多线程

  1. 关于声明周期我们需要关注两个概念:

    状态、相应方法

    状态a --> 状态b:哪些方法执行了(回调方法)

    某个方法主动调用:状态a --> 状态b (例如:wait(),sleep()

  2. 阻塞只是临时状态,死亡才是最终状态。程序如果一直卡在阻塞状态,就是一种异常的状态。例如:死锁。

线程的同步

同步是为了解决线程安全问题。先来看一个例子会更好理解:

创建三个窗口卖票,总票数为100张。使用实现Runnable接口的方式

public class WindowTest1 {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("窗口1:");t2.setName("窗口2:");t3.setName("窗口3:");t1.start();t2.start();t3.start();}
}class window implements Runnable{private int ticket = 100;@Overridepublic void run() {if (ticket <= 0) {break;}try {Thread.sleep(100);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(Thread.currentThread().getName() + ":买票票号为;" + ticket--);}
}

我们会发现,有重票的情况出现,这是因为在某个线程操作车票的过程中,尚未操作完成时,其他线程参与进来,也操作车票。

Runnable窗口买票

当多条语句在操作同一个线程共享数据时,一个线程对多条语句只执行了一部分,还没有执行完,另一个线程参与进来执行。导致共享数据的错误。这就是线程安全问题。

理想状态下,三个线程会同时进入判断语句,均判断票号为0,跳出循环

理想状态

极端状态下,三个线程均进入阻塞状态,结束阻塞后,都执行后面的买票代码。后两个线程便会输出错票。

极端状态

所以我们使用同步代码块的方式解决这个问题。

同步代码块

先来介绍同步代码块:

synchronized (同步监视器){//需要被同步的代码
}
  1. 操作共享数据的代码,即为需要被同步的代码。 -->不能包含代码多了,也不能包含代码少了。

  2. 共享数据:多个线程共同操作的变量。比如:ticket就是共享数据。

  3. 同步监视器,俗称:。任何一个类的对象,都可以充当锁。

    ⭐要求:多个线程必须要共用同一把锁。

在上面的例子中,我们需要在操作共享数据时使用同步锁:

@Override
public void run() {while (true) {synchronized (this) {if (ticket <= 0) {break;}try {Thread.sleep(100);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(Thread.currentThread().getName() + ":买票票号为;" + ticket--);}}
}

但如果时使用继承方式实现的线程,由于其生成了多个对象,所以不能使用this作为当前同步的锁,考虑使用window.class当前类名来作为锁(类在程序中只会加载一次,这个知识会在讲“反射”时提到。)

@Override
public void run() {while (true) {synchronized (window.class) {if (ticket <= 0) {break;}try {Thread.sleep(100);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(Thread.currentThread().getName() + ":买票票号为;" + ticket--);}}
}

同步方法

如果一整个方法都需要同步,那不妨将方法声明为同步方法。

private synchronized void ticket(){if (ticket <= 0) return;try {Thread.sleep(100);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(Thread.currentThread().getName() + ":买票票号为;" + ticket--);
}

但如果是在使用继承Thread类方式实现的线程中,你会发现同步锁失效了。这就是我们的另一个知识点:

细心的你一定发现了,同步方法没有要求我们写同步监视器,那,他就不存在了吗?

不是的。它默认使用this代替。恰巧我们这种方式实现的线程又会生成多个对象,用当前对象肯定不行。

解决方式就是将此同步方法声明为静态的,这时他会使用当前类来代替this–>window.class

class window extends Thread{private static int ticket = 100;@Overridepublic void run() {while (true) {ticket();}}private static synchronized void ticket(){if (ticket <= 0) {return;}try {Thread.sleep(100);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(Thread.currentThread().getName() + ":买票票号为;" + ticket--);}
}

同步的利弊

同步的方式,解决了线程的安全问题。—好处

操作同步代码时,只能有一个线程参与,其他线程等待。相当于是一个单线程的过程,效率低。 —局限性

懒汉式单例改进

之前我们写懒汉式单例提到,它是线程不安全的。现在进行改进

public static Bank getInstance(){//方式一:只解决线程安全,效率低,所有synchronized (Bank.class) {if (bank == null) {bank = new Bank();}return bank;}//方式二:效率更高,之后的线程不必在同步锁外等待if (bank == null) {synchronized (Bank.class) {if (bank == null) {bank = new Bank();}}}return bank;
}

死锁问题

不同的线程分别占用对方需要的同步资源不放弃,都在等待对方放弃自己需要的同步资源,就形成了线程的死锁

出现死锁后,不会出现异常,不会出现提示,只是所有的线程都处于阻塞状态,无法继续

如何解决?

  • 专门的算法、原则
  • 尽量减少同步资源的定义
  • 尽量避免嵌套同步

一个死锁的实例:

class A {public synchronized void foo(B b) { //同步监视器:A类的对象:aSystem.out.println("当前线程名: " + Thread.currentThread().getName()+ " 进入了A实例的foo方法"); // ①try {Thread.sleep(200);} catch (InterruptedException ex) {ex.printStackTrace();}System.out.println("当前线程名: " + Thread.currentThread().getName()+ " 企图调用B实例的last方法"); // ③b.last();}public synchronized void last() {//同步监视器:A类的对象:aSystem.out.println("进入了A类的last方法内部");}
}class B {public synchronized void bar(A a) {//同步监视器:bSystem.out.println("当前线程名: " + Thread.currentThread().getName()+ " 进入了B实例的bar方法"); // ②try {Thread.sleep(200);} catch (InterruptedException ex) {ex.printStackTrace();}System.out.println("当前线程名: " + Thread.currentThread().getName()+ " 企图调用A实例的last方法"); // ④a.last();}public synchronized void last() {//同步监视器:bSystem.out.println("进入了B类的last方法内部");}
}public class DeadLock implements Runnable {A a = new A();B b = new B();public void init() {Thread.currentThread().setName("主线程");// 调用a对象的foo方法a.foo(b);System.out.println("进入了主线程之后");}public void run() {Thread.currentThread().setName("副线程");// 调用b对象的bar方法b.bar(a);System.out.println("进入了副线程之后");}public static void main(String[] args) {DeadLock dl = new DeadLock();new Thread(dl).start();//副线程启动dl.init();//主线程启动}
}

Lock(锁)

从JDK 5.0开始, Java提供了更强大的线程同步机制——通过显式定义同步锁对象来实现同步。同步锁使用Lock对象充当。

java.util.concurrent.locks.Lock接口是控制多个线程对共享资源进行访问的工具。 锁提供了对共享资源的独占访问,每次只能有一个线程对Lock对象加锁,线程开始访问共享资源之前应先获得Lock对象。

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

注意:Lock方式中并没有同步监视器这个概念,但是我们可以把private ReentrantLock lock = new ReentrantLock();中的lock视为同步监视器,如果线程间没有使用同一个lock对象,就相当于没有使用同一把锁。lock不可调用wait()notify()notifyAll()方法,但是可以通过相关的Condition对象来实现更多操作。

使用方式

//1.实例化ReentrantLock
private ReentrantLock lock = new ReentrantLock();@Override
public void run() {while(true){try{//2.调用锁定方法lock()lock.lock();//需要同步的代码}finally {//3.调用解锁方法:unlock()lock.unlock();}}
}

因为最后一步一定要解锁,所以使用try--finally的方式

synchronized 与 Lock 的对比

  1. Lock是显式锁(手动开启和关闭锁,别忘记关闭锁), synchronized是隐式锁,出了作用域自动释放
  2. Lock只有代码块锁, synchronized有代码块锁和方法锁
  3. 使用Lock锁, JVM将花费较少的时间来调度线程,性能更好。并且具有更好的扩展性(提供更多的子类)

推荐使用顺序

优先使用顺序:

Lock --> 同步代码块(已经进入了方法体,分配了相应资源)–> 同步方法(在方法体之外)

练习

银行有一个账户。

有两个储户分别向同一个账户存3000元,每次存1000,存3次。每次存完打印账户余额。

  1. 使用继承Thread方式,synchronized同步方法
public class DepositTest {public static void main(String[] args) {Account account = new Account();Customer c1 = new Customer(account);Customer c2 = new Customer(account);c1.setName("Tom");c2.setName("Jerry");c1.start();c2.start();}
}class Account{double balance;public synchronized void deposit(double awt){if (awt <= 0) {return;}balance += awt;try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(Thread.currentThread().getName() + ":sucessful deposit.The balance is " + balance);}
}class Customer extends Thread{Account acct;public Customer(Account acct) {this.acct = acct;}@Overridepublic void run() {for (int i = 0; i < 3; i++) {acct.deposit(1000);}}
}
  1. 使用继承Thread方式,lock同步
public void deposit(double awt){if (awt <= 0) {return;}lock.lock();try {balance += awt;try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(Thread.currentThread().getName() + ":sucessful deposit.The balance is " + balance);} finally {lock.unlock();}
}
  1. 使用继承Runnable方式,synchronized同步方法
import java.util.concurrent.locks.ReentrantLock;
public class DepositTest {public static void main(String[] args) {Account account = new Account();Customer c1 = new Customer(account);Customer c2 = new Customer(account);Thread t1 = new Thread(c1);Thread t2 = new Thread(c2);t1.setName("Tom");t2.setName("Jerry");t1.start();t2.start();}
}class Account{double balance;ReentrantLock lock = new ReentrantLock();public synchronized void deposit(double awt){if (awt <= 0) {return;}balance += awt;try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(Thread.currentThread().getName() + ":sucessful deposit.The balance is " + balance);}class Customer implements Runnable{Account acct;public Customer(Account acct) {this.acct = acct;}@Overridepublic void run() {for (int i = 0; i < 3; i++) {acct.deposit(1000);}}
}
  1. 使用继承Runnable方式,lock同步
import java.util.concurrent.locks.ReentrantLock;
public class DepositTest {public static void main(String[] args) {Account account = new Account();Customer c1 = new Customer(account);Customer c2 = new Customer(account);Thread t1 = new Thread(c1);Thread t2 = new Thread(c2);t1.setName("Tom");t2.setName("Jerry");t1.start();t2.start();}
}class Account{double balance;ReentrantLock lock = new ReentrantLock();public void deposit(double awt){if (awt <= 0) {return;}lock.lock();try {balance += awt;try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(Thread.currentThread().getName() + ":sucessful deposit.The balance is " + balance);} finally {lock.unlock();}}
}class Customer implements Runnable{Account acct;public Customer(Account acct) {this.acct = acct;}@Overridepublic void run() {for (int i = 0; i < 3; i++) {acct.deposit(1000);}}
}

线程通信

使用两个线程打印 1-100。线程1, 线程2 交替打印

/*** 使用两个线程打印 1-100。线程1, 线程2 交替打印*/
public class CommunicationTest {public static void main(String[] args) {Number number = new Number();Thread t1 = new Thread(number);Thread t2 = new Thread(number);t1.setName("线程一");t2.setName("线程二");t1.start();t2.start();}
}class Number implements Runnable{private int number = 1;@Overridepublic void run() {while (true) {synchronized (this) {notify();//将阻塞的进程唤醒if (number > 100) {return;}System.out.println(Thread.currentThread().getName() + ":" + number++);    try {wait();//令当前进程阻塞,等待唤醒。} catch (InterruptedException e) {e.printStackTrace();}}}}
}

涉及到的三个方法:

wait():一旦执行此方法,当前线程就进入阻塞状态,并释放同步监视器。

notify():一旦执行此方法,就会唤醒被wait的一个线程。如果有多个线程被wait,就唤醒优先级高的那个。

notifyAll():一旦执行此方法,就会唤醒所有被wait的线程。

说明:

  1. wait()notify()notifyAll()三个方法必须使用在同步代码块或同步方法中,lock锁的方式都不可以。意味着这三个方法是依赖于同步监视器的。

  2. wait()notify()notifyAll()三个方法的调用者必须是同步代码块或同步方法中的同步监视器。

    否则,会出现IllegalMonitorStateException异常。

  3. wait()notify()notifyAll()三个方法三个方法是定义在java.lang.Object类中。

面试题:sleep() 和 wait()的异同?

相同点:一旦执行方法,都可以使得当前的线程进入阻塞状态。

不同点:

  1. 两个方法声明的位置不同:

    Thread类中声明sleep()

    Object类中声明wait()

  2. 调用的要求不同:sleep()可以在任何需要的场景下调用。 wait()必须使用在同步代码块或同步方法中

  3. 关于是否释放同步监视器:如果两个方法都使用在同步代码块或同步方法中,sleep()不会释放锁,wait()会释放锁。

线程通信的应用

经典例题:生产者/消费者问题

生产者(Productor)将产品交给店员(Clerk),而消费者(Customer)从店员处取走产品,店员一次只能持有固定数量的产品(比如:20),如果生产者试图生产更多的产品,店员会叫生产者停一下,如果店中有空位放产品了再通知生产者继续生产;如果店中没有产品了,店员会告诉消费者等一下,如果店中有产品了再通知消费者来取走产品。

分析:

  1. 是否是多线程问题?是,生产者线程,消费者线程
  2. 是否有共享数据?是,店员(或产品)
  3. 如何解决线程的安全问题?同步机制,有三种方法
  4. 是否涉及线程的通信?是,生产者通知消费者进行消费,消费者通知生产者进行生产
public class CommunicationTest {public static void main(String[] args) {Clerk clerk = new Clerk();Customer customer = new Customer(clerk);Productor productor = new Productor(clerk);customer.setName("customer");productor.setName("productor");productor.start();customer.start();}
}class Clerk{private int number = 0;public synchronized void produce(){if (number >= 20) {//等待消费try {wait();} catch (InterruptedException e) {e.printStackTrace();}return;}System.out.println(Thread.currentThread().getName() + "开始生产第" + ++number + "个产品");notify();//生产后,唤醒消费者}public synchronized void custom(){if (number <= 0) {//等待生产try {wait();} catch (InterruptedException e) {e.printStackTrace();}return;}System.out.println(Thread.currentThread().getName() + "开始消费第" + number-- + "个产品");notify();//消费后,唤醒生产者}}class Productor extends Thread{Clerk clerk;public Productor(Clerk clerk) {this.clerk = clerk;}@Overridepublic void run() {while (true) {try {Thread.sleep(250);} catch (InterruptedException e) {e.printStackTrace();}clerk.produce();}}
}class Customer extends Thread{Clerk clerk;public Customer(Clerk clerk) {this.clerk = clerk;}@Overridepublic void run() {while (true) {try {Thread.sleep(500);} catch (InterruptedException e) {e.printStackTrace();}clerk.custom();}}
}

新增线程创建方式

JDK5.0 新增

新增方式一:实现Callable接口

与使用Runnable相比, Callable功能更强大些

  • 相比run()方法,可以有返回值
  • 方法可以抛出异常
  • 支持泛型的返回值
  • 需要借助FutureTask类,比如获取返回结果

实现步骤为:

  1. 创建一个实现Callable的实现类

  2. 实现call方法,将此线程需要执行的操作声明在call()

  3. 创建Callable接口实现类的对象

  4. 将此Callable接口实现类的对象作为传递到FutureTask构造器中,创建FutureTask的对象

  5. FutureTask的对象作为参数传递到Thread类的构造器中,创建Thread对象,并调用start()

  6. 获取Callable中call方法的返回值(可选)

    get()返回值即为FutureTask构造器参数Callable实现类重写的call()的返回值。

//1.创建一个实现`Callable`的实现类
class Number implements Callable{private int count = 0;//2.实现call方法,将此线程需要执行的操作声明在call()中@Overridepublic Object call() throws Exception {for (int i = 0; i < 100; i++) {if (i % 2 == 0){System.out.println(i);}count += i;}return count;}
}public class NewThread {public static void main(String[] args) {//3.创建Callable接口实现类的对象Number number = new Number();//4.将此Callable接口实现类的对象作为传递到FutureTask构造器中,创建FutureTask的对象FutureTask futureTask = new FutureTask(number);//5.将FutureTask的对象作为参数传递到Thread类的构造器中,创建Thread对象,并调用start()Thread thread = new Thread(futureTask);thread.start();//6.获取Callable中call方法的返回值//get()返回值即为FutureTask构造器参数Callable实现类重写的call()的返回值。try {Object o = futureTask.get();System.out.println("总和为:" + o);} catch (InterruptedException e) {e.printStackTrace();} catch (ExecutionException e) {e.printStackTrace();}}
}

如何理解实现Callable接口的方式创建多线程比实现Runnable接口创建多线程方式强大?

  1. call()可以有返回值的。
  2. call()可以抛出异常,被外面的操作捕获,获取异常的信息
  3. Callable是支持泛型的

新增方式二:使用线程池

背景: 经常创建和销毁、使用量特别大的资源,比如并发情况下的线程,对性能影响很大

思路: 提前创建好多个线程,放入线程池中,使用时直接获取,使用完放回池中。可以避免频繁创建销毁、实现重复利用。类似生活中的公共交通工具

好处:

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

线程池相关API

JDK 5.0起提供了线程池相关API: ExecutorServiceExecutors

ExecutorService:真正的线程池接口。常见子类ThreadPoolExecutor

  • void execute(Runnable command) :执行任务/命令,没有返回值,一般用来执行
    Runnable
  • <T> Future<T> submit(Callable<T> task):执行任务,有返回值,一般又来执行
    Callable
  • ``void shutdown()`:关闭连接池

Executors:工具类、线程池的工厂类,用于创建并返回不同类型的线程池

  • Executors.newCachedThreadPool():创建一个可根据需要创建新线程的线程池
  • Executors.newFixedThreadPool(n); 创建一个可重用固定线程数的线程池
  • Executors.newSingleThreadExecutor() :创建一个只有一个线程的线程池
  • Executors.newScheduledThreadPool(n):创建一个线程池,它可安排在给定延迟后运
    行命令或者定期地执行

使用实例:

public static void main(String[] args) {//1. 创建线程池ExecutorService service = Executors.newFixedThreadPool(10);//2. 放入线程并启动service.execute(new OddNum());//Runnable线程启动FutureTask futureTask = new FutureTask(new EvenNum());service.submit(futureTask);//Callable线程启动//3. 关闭线程池service.shutdown();
}

线程管理

由于我们接收线程池对象时是使用多态方式接收的,我们可以查看newFixedThreadPool源码,看到它返回的是ThreadPoolExecutor,若想使用线程管理,就必须先进行强转。

/*** Creates a thread pool that reuses a fixed number of threads* operating off a shared unbounded queue.  At any point, at most* {@code nThreads} threads will be active processing tasks.* If additional tasks are submitted when all threads are active,* they will wait in the queue until a thread is available.* If any thread terminates due to a failure during execution* prior to shutdown, a new one will take its place if needed to* execute subsequent tasks.  The threads in the pool will exist* until it is explicitly {@link ExecutorService#shutdown shutdown}.** @param nThreads the number of threads in the pool* @return the newly created thread pool* @throws IllegalArgumentException if {@code nThreads <= 0}*/
public static ExecutorService newFixedThreadPool(int nThreads) {return new ThreadPoolExecutor(nThreads, nThreads,0L, TimeUnit.MILLISECONDS,new LinkedBlockingQueue<Runnable>());
}

转换为ThreadPoolExecutor再进行管理

ThreadPoolExecutor service1 = (ThreadPoolExecutor) service;
service1.setCorePoolSize(3);//核心池的大小

相关文章:

Java高级-多线程

本篇讲解java多线程 基本概念&#xff1a; 程序、进程、线程 **程序(program)**是为完成特定任务、用某种语言编写的一组指令的集合。即指一段静态的代码&#xff0c;静态对象。 **进程(process)**是程序的一次执行过程&#xff0c;或是正在运行的一个程序。是一个动态的过程…...

mysql高级(事务、存储引擎、索引、锁、sql优化、MVCC)

文章目录1.事务1.1 四大特性ACID1.2 并发事务2.存储引擎2.1 InnoDB2.2 MyISAM2.3 Memory2.4 存储引擎特点2.5 存储引擎的选择3.性能分析3.1 查看执行频次3.2 慢查询日志3.3 profile3.4 explain4.索引4.1 索引结构B-TreeBTreeHash面试题4.2 索引分类思考题4.3 语法4.4 使用规则最…...

Java后端开发功能模块思路

文章目录前言一、查找接口及参数信息1.1 找访问路径1.2 参数及返回结果信息1.3 编写功能模块函数二、代码设计思路三、总结前言 对于正在学习Java后端开发的同学来说&#xff0c;对于Java后端功能模块的开发过程及思路要有一个整体清晰的流程。才能保证在开发过程中更加的顺畅…...

CAPL(vTESTStudio) - DoIP - TCP发送_05

TCP发送 参数定义 版本号:02 FD or 01 FE or 其他任意值数据类型:00 05 or 00 06 or 80 01 or其他任意值数据长度:想要发送的任意长度...

使用IntelliJ IDEA搭建datax-web开发环境

记录&#xff1a;372场景&#xff1a;使用IntelliJ IDEA搭建datax-web开发环境&#xff0c;以及datax-web基本使用。版本&#xff1a;JDK 1.8Python 2.7.5datax-web开源地址&#xff1a;https://github.com/WeiYe-Jing/datax-web1.配置Maven环境1.1安装目录目录&#xff1a;D:\…...

[SSD固态硬盘技术 14] GC垃圾回收太重要了

今天介绍臭名昭著的垃圾收集 过程(或“GC”),maybe 这是对JAVA 工程师而言。当遇到GC导致速度降低时候, 他们真的想跳脚。 我想到我的小孩打疫苗,哭的哇哇叫, 在他的眼里疫苗应该也是讨厌的吧, 但事实真的如此吗? 但首先,让我们考虑一下如果根本没有 GC,闪存系统会发…...

lamada表达式、stream、collect整理

lamada表达式格式 格式&#xff1a;( parameter-list ) -> { expression-or-statements } 实例&#xff1a;简化匿名内部类的写法 原本写法&#xff1a; public class LamadaTest { public static void main(String[] args) { new Thread(new Runnable() { …...

Nacos 入门微服务项目实战

Nacos 核心源码精讲 - IT贱男 - 掘金小册全方位源码精讲&#xff0c;深度剖析 Nacos 注册中心和配置中心的核心思想。「Nacos 核心源码精讲」由IT贱男撰写&#xff0c;375人购买https://s.juejin.cn/ds/BuC3Vs9/ Hi&#xff0c;大家好&#xff0c;欢迎大家来学习《Nacos 核心源…...

【c++】类和对象:让你明白“面向一个对象有多重要”:构造函数,析构函数,拷贝构造函数的深入学习

文章目录 什么是面向对象&#xff1f;一&#xff1a;类是什么&#xff1f; 1.类的访问限定符 2.封装 3.类的实例化 4.this指针二&#xff1a;类的6个默认成员函数 1.构造函数 2.析构函数 3.拷贝构造函数什么是面向对象&#xff1f; c语言是面向…...

职场IT老手教你3步教你玩转可视化大屏设计,让领导眼前一亮!

我是制造企业的IT中心的研发人员&#xff0c;平常工作就是配合业务部门出出报表&#xff0c;选型一些商业软件&#xff0c;并在内部负责实施运维。最近领导出去参观了一些数字化转型比较领先的工厂和制造企业&#xff0c;回来就甩给我几张图&#xff0c;问能不能我们也做几个这…...

【光伏功率预测】基于EMD-PCA-LSTM的光伏功率预测模型(Matlab代码实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…...

大数据Kylin(二):Kylin安装使用

文章目录 Kylin安装使用 一、Kylin安装要求 二、Kylin安装 1、Kylin安装前环境准备...

我们的微服务中为什么需要网关?

说起 Spring Cloud Gateway 的使用场景&#xff0c;我相信很多小伙伴都能够脱口而出认证二字&#xff0c;确实&#xff0c;在网关中完成认证操作&#xff0c;确实是 Gateway 的重要使用场景之一&#xff0c;然而并不是唯一的使用场景。在微服务中使用网关的好处可太多了&#x…...

互联网医院源码 线上问诊 智慧医院源码 C#源码

互联网医院平台源码 智慧医院管理系统源码 开发环境&#xff1a;ASP.NET C# VS2019 SQL2008 依托于实体医院利用互联网技术对接院内业务信息系统&#xff0c;向患者提供基于线上问诊、预约挂号、缴费结算、医患互动、诊后随访、健康科普和复诊等全面的医疗健康互联网服务。…...

基于昇腾计算语言AscendCL开发AI推理应用

01 初始AscendCL AscendCL&#xff08;Ascend Computing Language&#xff0c;昇腾计算语言&#xff09;是昇腾计算开放编程框架&#xff0c;是对底层昇腾计算服务接口的封装&#xff0c;它提供运行时资源&#xff08;例如设备、内存等&#xff09;管理、模型加载与执行、算子…...

JS document.write()换行

换行效果&#xff1a; 通过传递多个参数&#xff0c;即可实现换行效果&#xff1a; document.write("<br>",ar) 效果&#xff1a; 示例源码&#xff1a; <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8&quo…...

Java高级-集合-Collection部分

本篇讲解java集合 集合 集合框架的概述 集合、数组都是对多个数据进行存储操作的结构&#xff0c;简称Java容器。 说明&#xff1a;此时的存储&#xff0c;主要指的是内存层面的存储&#xff0c;不涉及到持久化的存储&#xff08;.txt,.jpg,.avi&#xff0c;数据库中&#xf…...

Android性能优化:getResources()与Binder交火导致的界面卡顿优化

欢迎&#xff1a;https://juejin.cn/post/7198430801851531324/ 欢迎&#xff1a;https://nasdaqgodzilla.github.io/2023/02/10/Android%E6%80%A7%E8%83%BD%E4%BC%98%E5%8C%96%EF%BC%9AgetResources-%E4%B8%8EBinder%E4%BA%A4%E7%81%AB%E5%AF%BC%E8%87%B4%E7%9A%84%E7%95%8C%E…...

常见的内存操作函数

&#x1f466;个人主页&#xff1a;Weraphael ✍&#x1f3fb;作者简介&#xff1a;目前是C语言学习者 ✈️专栏&#xff1a;C语言航路 &#x1f40b; 希望大家多多支持&#xff0c;咱一起进步&#xff01;&#x1f601; 如果文章对你有帮助的话 欢迎 评论&#x1f4ac; 点赞&a…...

python关键字

文章目录1 and、or、not2 if、elif、else3 for、while4 True、False5 continue、break6 pass7 try、except、finally、raise8 import、from、as9 def、return10 class11 lambda12 del13 global、nonlocal14 in、is15 None16 assert17 with18 yield1 and、or、not and、or、not…...

测试微信模版消息推送

进入“开发接口管理”--“公众平台测试账号”&#xff0c;无需申请公众账号、可在测试账号中体验并测试微信公众平台所有高级接口。 获取access_token: 自定义模版消息&#xff1a; 关注测试号&#xff1a;扫二维码关注测试号。 发送模版消息&#xff1a; import requests da…...

label-studio的使用教程(导入本地路径)

文章目录 1. 准备环境2. 脚本启动2.1 Windows2.2 Linux 3. 安装label-studio机器学习后端3.1 pip安装(推荐)3.2 GitHub仓库安装 4. 后端配置4.1 yolo环境4.2 引入后端模型4.3 修改脚本4.4 启动后端 5. 标注工程5.1 创建工程5.2 配置图片路径5.3 配置工程类型标签5.4 配置模型5.…...

基于FPGA的PID算法学习———实现PID比例控制算法

基于FPGA的PID算法学习 前言一、PID算法分析二、PID仿真分析1. PID代码2.PI代码3.P代码4.顶层5.测试文件6.仿真波形 总结 前言 学习内容&#xff1a;参考网站&#xff1a; PID算法控制 PID即&#xff1a;Proportional&#xff08;比例&#xff09;、Integral&#xff08;积分&…...

云计算——弹性云计算器(ECS)

弹性云服务器&#xff1a;ECS 概述 云计算重构了ICT系统&#xff0c;云计算平台厂商推出使得厂家能够主要关注应用管理而非平台管理的云平台&#xff0c;包含如下主要概念。 ECS&#xff08;Elastic Cloud Server&#xff09;&#xff1a;即弹性云服务器&#xff0c;是云计算…...

Qt Http Server模块功能及架构

Qt Http Server 是 Qt 6.0 中引入的一个新模块&#xff0c;它提供了一个轻量级的 HTTP 服务器实现&#xff0c;主要用于构建基于 HTTP 的应用程序和服务。 功能介绍&#xff1a; 主要功能 HTTP服务器功能&#xff1a; 支持 HTTP/1.1 协议 简单的请求/响应处理模型 支持 GET…...

Spring Boot+Neo4j知识图谱实战:3步搭建智能关系网络!

一、引言 在数据驱动的背景下&#xff0c;知识图谱凭借其高效的信息组织能力&#xff0c;正逐步成为各行业应用的关键技术。本文聚焦 Spring Boot与Neo4j图数据库的技术结合&#xff0c;探讨知识图谱开发的实现细节&#xff0c;帮助读者掌握该技术栈在实际项目中的落地方法。 …...

【Oracle】分区表

个人主页&#xff1a;Guiat 归属专栏&#xff1a;Oracle 文章目录 1. 分区表基础概述1.1 分区表的概念与优势1.2 分区类型概览1.3 分区表的工作原理 2. 范围分区 (RANGE Partitioning)2.1 基础范围分区2.1.1 按日期范围分区2.1.2 按数值范围分区 2.2 间隔分区 (INTERVAL Partit…...

《C++ 模板》

目录 函数模板 类模板 非类型模板参数 模板特化 函数模板特化 类模板的特化 模板&#xff0c;就像一个模具&#xff0c;里面可以将不同类型的材料做成一个形状&#xff0c;其分为函数模板和类模板。 函数模板 函数模板可以简化函数重载的代码。格式&#xff1a;templa…...

Linux 中如何提取压缩文件 ?

Linux 是一种流行的开源操作系统&#xff0c;它提供了许多工具来管理、压缩和解压缩文件。压缩文件有助于节省存储空间&#xff0c;使数据传输更快。本指南将向您展示如何在 Linux 中提取不同类型的压缩文件。 1. Unpacking ZIP Files ZIP 文件是非常常见的&#xff0c;要在 …...

机器学习的数学基础:线性模型

线性模型 线性模型的基本形式为&#xff1a; f ( x ) ω T x b f\left(\boldsymbol{x}\right)\boldsymbol{\omega}^\text{T}\boldsymbol{x}b f(x)ωTxb 回归问题 利用最小二乘法&#xff0c;得到 ω \boldsymbol{\omega} ω和 b b b的参数估计$ \boldsymbol{\hat{\omega}}…...