java线程

java线程

1.进程,线程什么?他们之间什么关系?

进程:相当于一个软件,在电脑上打开一个软件相当于开辟了一个新的线程。

线程:线程是操作系统进行调度操作的最小单位。比如说用户登录就相当于一个进程中的一个线程

进程是操作系统分配资源的最小单位,而线程是操作系统调度执行的最小单位。一个进程可以包含多个线程,它们共享进程的资源,但每个线程有自己的栈空间。

对于一个java程序来说:

运行一个java程序相当于启动一个进程,而用户的每一个请求程序都会创建一个线程用于处理,而当程序启动时会自动创建一个主线程用于执行main方法中的代码和垃圾回收线程,垃圾回收线程是后台线程,用于周期性地回收不再使用的内存。

2.线程与Cpu之间的关系

由上述可知‘线程是操作系统进行调度操作的最小单位’那么调度线程又是什么意思呢?

总所周知线程想要运行那么必须要抢占到时间片这样子Cpu才会去执行对应的线程,而调度就是操作系统给线程进行时间片的分配。

也就是说操作系统通过线程调度算法来决定哪个线程在什么时候运行。线程的状态转换受到调度器的控制。

3.线程的生命周期

在这里插入图片描述

线程的生命周期:新建、就绪、运行、阻塞、锁池。这些状态反映了线程在不同阶段的行为和状态转换。

4.对线程的操作

接下来要说的是线程的内部比如线程名字、优先级别等

  1. 创建线程:

    • Thread(Runnable target): 创建一个新线程,并指定要运行的 Runnable 对象。

    • Thread(String name): 创建一个新线程,并指定线程的名称。

    • Thread(Runnable target, String name): 创建一个新线程,同时指定 Runnable 对象和线程的名称。

  2. 启动线程:

    • void start(): 启动线程,使其处于就绪状态,并开始执行 run 方法中的代码。

  3. 线程状态查询:

    • boolean isAlive(): 检查线程是否处于活动状态,即线程已启动但尚未终止。

    • boolean isDaemon(): 检查线程是否是守护线程。

  4. 线程等待和休眠:

    • void join(): 等待线程终止。

    • void sleep(long millis): 使当前线程进入休眠状态,暂停执行指定的毫秒数。

  5. 线程中断:

    • void interrupt(): 中断线程的执行。通常会导致线程抛出 InterruptedException 异常。

    • boolean isInterrupted(): 检查线程是否被中断。

    • static boolean interrupted(): 检查线程是否被中断,并清除中断状态。

  6. 线程优先级:

    • void setPriority(int newPriority): 设置线程的优先级。

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

  7. 线程等待通信:

    • void wait(): 使当前线程等待,直到另一个线程调用 notify()notifyAll() 方法来唤醒它。

    • void notify(): 唤醒正在等待的线程之一。

    • void notifyAll(): 唤醒所有等待的线程。

  8. 线程同步:

    • synchronized: 使用 synchronized 关键字来创建同步块,确保多个线程对共享资源的安全访问。

  9. 线程退出:

    • void stop(): 强制终止线程,已过时,不推荐使用。

    • void run(): 线程的执行体,通常是需要执行的具体代码。

    • void yield(): 暂停当前正在执行的线程对象,并执行其他线程。

5.常用方法解释

Thread和Runnable

在 Java 中,你可以通过两种方式来创建并运行线程:通过直接实例化 Thread 类,或者通过实现 Runnable 接口并将其传递给 Thread 类。

继承 Thread 类:

  • 当你直接继承 Thread 类并创建一个新的子类时,你的子类将是一个线程类,因此可以重写 run 方法来定义线程的执行逻辑。

  • 这种方式适用于在定义线程的同时扩展线程类的行为。

class MyThread extends Thread {
    public void run() {
        // 线程执行的逻辑
    }
}
​
MyThread thread = new MyThread();
thread.start();

实现 Runnable 接口:

  • 通过实现 Runnable 接口,你可以将一个类的实例传递给 Thread 类来创建一个新的线程。这种方式允许你的类继续扩展其他类,而不会受到 Java 单继承的限制。

  • 这种方式更具灵活性,因为一个类可以实现多个接口,而线程只是其中的一种用途。

  1. class MyRunnable implements Runnable {
        public void run() {
            // 线程执行的逻辑
        }
    }
    ​
    MyRunnable myRunnable = new MyRunnable();
    Thread thread = new Thread(myRunnable);
    thread.start();

总的来说,如果你只需要简单地创建一个线程执行某些任务,使用 Runnable 更为灵活,因为你可以将 Runnable 实例传递给多个线程。如果你需要更复杂的线程控制和状态管理,你可以继承 Thread 类。然而,通常情况下,推荐使用 Runnable 接口,因为它避免了单继承的限制,并提供了更好的代码组织和可维护性。二者都是创建一个线程,而前者是通过继承来自定义线程而后者是通过实现接口来自定义线程内容,后者相对于前者更加灵活

Start和Run的区别

start() 方法和 run() 方法是线程中的两个不同方法,它们的主要区别在于如何启动线程以及执行的方式:

  1. start() 方法:

    • 用于启动一个新线程,并执行线程的 run() 方法。

    • 当你调用 start() 方法时,系统会创建一个新的线程,并在新线程中执行 run() 方法中的代码。

    • 这个新线程会在后台运行,与主线程并行执行。

    • start() 方法只能调用一次,重复调用会抛出 IllegalThreadStateException 异常。

  2. run() 方法:

    • 是线程的入口点,包含线程的实际执行逻辑。

    • 如果你直接调用 run() 方法,那么它将在当前线程中以普通的方法调用的方式执行,而不会创建新的线程。

    • 这意味着 run() 方法的代码会在当前线程中运行,不会并发执行,不会启动新线程。

总结:start() 方法用于启动新线程并执行 run() 方法中的代码,而直接调用 run() 方法则会在当前线程中执行 run() 方法的代码,不会创建新线程。通常情况下,我们应该使用 start() 方法来启动新线程,而不是直接调用 run() 方法。

Join和Sleep的区别

join()sleep() 是 Java 中用于线程控制的两个不同方法,它们之间的主要区别在于它们的作用和使用方式:

  1. join() 方法:

    • join() 方法是一个实例方法,用于等待调用它的线程(通常是主线程)等待另一个线程执行完毕。

    • 当一个线程调用 join() 方法等待另一个线程时,它会被阻塞,直到另一个线程执行完毕或者指定的时间到期。

    • 典型的用法是在主线程中启动多个子线程,然后使用 join() 来等待所有子线程完成后再继续执行主线程的任务。

  2. sleep() 方法:

    • sleep() 方法是一个静态方法,用于使当前线程休眠一段指定的时间,让出 CPU 时间片给其他线程。

    • 它不会释放锁或者让其他线程运行,只是让当前线程暂时休眠。

    • 通常用于模拟等待或者控制线程执行的速度。

总结:

  • join() 通常用于线程之间的协同工作,等待其他线程的执行结果。

  • sleep() 用于线程的暂停,让出 CPU 时间片,用于控制线程执行的速度或者等待一段时间。

  • 两者都可以用于线程控制,但应根据具体需求来选择使用哪个方法。

Sleep之后需要重新抢占时间片吗?

sleep() 方法会导致当前线程进入休眠状态,但它并不会主动释放 CPU 时间片。线程会在休眠时间结束后重新进入就绪状态,然后需要竞争 CPU 时间片才能继续执行。在重新竞争到 CPU 时间片之前,其他处于就绪状态的线程有机会被调度并执行。

这意味着,尽管线程在休眠期间不会执行任何操作,但一旦休眠时间结束,它会尝试重新竞争 CPU 时间片来继续执行。这与线程的优先级、调度策略等因素有关,不保证立即获得 CPU 时间片。其他线程也有机会运行,具体的执行顺序由操作系统的调度算法决定。

为什么run()方法只能try…catch…不能throws?

因为run()方法在父类中没有抛出任何异常子类不能比父类抛出更多的异常

关于wait、notify、notifyAll方法的使用

首先这三个方法都是Object类的方法也就是对象的方法并不是Thread类的方法,转换成如何使用的角度就是我们是对对象进行调用而不是对线程。

那么我们对对象进行调用对应的线程状态会发生什么改变?

情景:我们现在有a,b,c三个线程,他们都需要用到a对象,现在我们对a对象进行一个wait操作

在了解他们会发生什么之前我们需要先了解显示对象锁、内置对象锁

显示对象锁(使用synchronized关键字或其他同步机制):这种锁会导致线程按照顺序获取锁,一次只能有一个线程获取锁并执行同步块或方法,其他线程需要等待锁的释放才能执行。这样可以确保线程安全,但可能会导致性能瓶颈。(不是说sync==显示对象锁!!!!!)

内置对象锁(内部锁或监视器锁):每个Java对象都有一个内置的锁,可以被用来创建同步块或方法。当使用内置锁时,多个线程可以并发地进入临界区,但需要手动编写代码来确保线程安全性,否则可能出现竞态条件等问题。

总的来说:显示对象锁,他只有一个线程需要执行对应的代码块就必须获取锁,内置对象锁只是监控作用此时代码块可以接受多线程进入但是会导致线程不安全。注意synchronized 可以达到所的目的但是他不等于显示对象锁。

代码举例
//内置锁
synchronized (myObject) {
    // 同步代码块
    // ...
    myObject.wait(); // 调用wait方法,释放myObject的内置锁
    // ...
}
//显示锁
 lock.lock();
sync、内 置锁、显示锁、对象锁、类锁区分

synchronized 关键字本质上是同步代码块,可以用于类锁、内置锁(对象锁),但它不等同于显示锁。类锁、内置锁就是条件不同前者是class文件后者是对象本身(this)

显示锁是指使用 Lock 接口的具体实现类,如 ReentrantLock,它提供了更多的灵活性和功能,可以实现更复杂的同步控制。

内置锁就是sync中的对象锁也就是wait释放的锁

因此我们需要分情况而论结果
当我们拥有显示对象锁时:
  1. 线程a、b、c都需要使用a对象,并且线程a正在执行。

  2. 线程a在使用a对象时,调用了 a.wait(),这会使线程a释放对a对象的锁,并进入阻塞状态。

  3. 线程b和c之后通过JVM的线程调度获取了执行机会,并尝试进入 synchronized(a) 的同步块。

  4. 如果没有其他线程通过 a.notify()a.notifyAll() 唤醒线程a,线程b和c会一直等待,直到线程a被唤醒。

  5. 如果线程b或c中的一个通过 a.notify()a.notifyAll() 唤醒了线程a,线程a会从阻塞状态变为就绪状态,等待CPU时间片分配。

  6. 之后,线程a、b、c会根据CPU的调度顺序来争夺CPU时间片,进行运行。

当我们没有显示对象锁时:

线程bc就会在a线程变成阻塞状态时根据jvm的调度获取到时间片进行相应的操作。

总结

当我们对某个线程上的对象调用wait方法=将该线程的状态从运行变为阻塞

当我们在别的线程上调用notify/notifyAll=将阻塞状态变为就绪状态

至于会不会wait之后线程会不会阻塞其他线程的运行就要根据有无显示锁而定

需要注意的是wait释放的是对象锁并不是显示锁

6.线程调度(了解)

1.常见的线程调度模型有哪些?

抢占式调度模型:
那个线程的优先级比较高,抢到的CPU时间片的概率就高一些/多一些。
java采用的就是抢占式调度模型。
​
均分式调度模型:
平均分配CPU时间片。每个线程占有的CPU时间片时间长度一样。
平均分配,一切平等。
有一些编程语言,线程调度模型采用的是这种方式。

2.java中提供了哪些方法是和线程调度有关系的呢?

getPriority()、SetPriority()

  • 最低优先级1

  • 默认优先级是5

  • 最高优先级10

优先级比较高的获取CPU时间片可能会多一些。(但也不完全是,大概率是多的。)

3.Java进程的优先级

static int MAX_PRIORITY 最高优先级(10) static int MIN_PRIORITY 最低优先级(1) static int NORM_PRIORITY 默认优先级(5)

注意:

  • main线程的默认优先级是:5

  • 优先级较高的,只是抢到的CPU时间片相对多一些。大概率方向更偏向于优先级比较高的。

7.线程数据安全

为什么会产生线程安全?

首先我们知道每一个线程都会在jvm中开辟一个属于自己的栈,里面的栈帧等于我们使用的方法,而方法就有可能会涉及到方法区中共享数据的修改,在多线程的情况下我们可能会遇到共享区域中的数据被频繁修改从而导致数据错误。总的来说就是多线程并发执行可能导致共享数据被修改导致线程发生数据错误的情况。

补充术语

异步、并发:线程t1和线程t2,各自执行各自的,t1不管t2,t2不管t1,谁也不需要等谁,这种编程模型叫做:异步编程模型。其实就是:多线程并发(效率较高。)

同步: 线程t1和线程t2,在线程t1执行的时候,必须等待t2线程执行结束,或者说在t2线程执行的时候,必须等待t1线程执行结束,两个线程之间发生了等待关系,这就是同步编程模型。效率较低。线程排队执行。同步就是排队。

如何保证线程安全?

线程不安全的原因是因为多线程并发执行,我们的解决思路是将并发变成排队执行,上锁我们的解决方法。

synchronized-线程同步

synchronized(){
    // 线程同步代码块。
}

重点: synchronized后面小括号() 中传的这个“数据”是相当关键的。这个数据必须是 多线程共享 的数据。才能达到多线程排队。 ()中写什么?

那要看你想让哪些线程同步。

假设t1、t2、t3、t4、t5,有5个线程,你只希望t1 t2 t3排队,t4 t5不需要排队。怎么办?

你一定要在()中写一个t1 t2 t3共享的对象。而这个对象对于t4 t5来说不是共享的。

这里的共享对象是:账户对象。 账户对象是共享的,那么this就是账户对象!!! ()不一定是this,这里只要是多线程共享的那个对象就行。

注意: 在java语言中,任何一个对象都有“一把锁”,其实这把锁就是标记。(只是把它叫做锁。) 100个对象,100把锁。1个对象1把锁。

总结synchronized

synchronized有三种写法: 第一种:同步代码块

灵活

synchronized(线程共享对象){ 同步代码块; }

第二种:在实例方法上使用synchronized

表示共享对象一定是 this 并且同步代码块是整个方法体。 第三种:在静态方法上使用synchronized

表示找 类锁。类锁永远只有1把。

就算创建了100个对象,那类锁也只有1把。

注意区分:

对象锁:1个对象1把锁,100个对象100把锁。
类锁:100个对象,也可能只是1把类锁。

我们以后开发中应该怎么解决线程安全问题?

是一上来就选择线程同步吗?synchronized

不是,synchronized会让程序的执行效率降低,用户体验不好。 系统的用户吞吐量降低。用户体验差。在不得已的情况下再选择线程同步机制。

第一种方案:避免共享状态: 尽量避免多个线程之间共享状态,使用局部变量或者将状态封装在对象内部。如果必须共享状态,确保正确同步。
​
第二种方案:如果必须是实例变量,那么可以考虑创建多个对象,这样实例变量的内存就不共享了。(一个线程对应1个对象,100个线程对应100个对象,对象不共享,就没有数据安全问题了。)
​
第三种方案:如果不能使用局部变量,对象也不能创建多个,这个时候就只能选择synchronized了。线程同步机制。

8.死锁(DeadLock)

死锁相当于两个线程互相卡住了对方的脖子都在等对方松手从而导致线程无法执行。

在这里插入图片描述

/**
 * 比如:t1想先穿衣服在穿裤子
 *       t2想先穿裤子在传衣服
 * 此时:t1拿到衣服,t2拿到裤子;
 * 由于t1拿了衣服,t2找不到衣服;t2拿了裤子,t1找不到裤子
 * 就会导致死锁的发生!
 */
public class Thread_DeadLock {
    public static void main(String[] args) {
        Dress dress = new Dress();
        Trousers trousers = new Trousers();
        //t1、t2共享dress和trousers。
        Thread t1 = new Thread(new MyRunnable1(dress, trousers), "t1");
        Thread t2 = new Thread(new MyRunnable2(dress, trousers), "t2");
        t1.start();
        t2.start();
    }
}
​
class MyRunnable1 implements Runnable{
    Dress dress;
    Trousers trousers;
​
    public MyRunnable1() {
    }
​
    public MyRunnable1(Dress dress, Trousers trousers) {
        this.dress = dress;
        this.trousers = trousers;
    }
​
    @Override
    public void run() {
        synchronized(dress){
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            synchronized (trousers){
                System.out.println("--------------");
            }
        }
    }
}
​
class MyRunnable2 implements Runnable{
    Dress dress;
    Trousers trousers;
​
    public MyRunnable2() {
    }
​
    public MyRunnable2(Dress dress, Trousers trousers) {
        this.dress = dress;
        this.trousers = trousers;
    }
​
    @Override
    public void run() {
        synchronized(trousers){
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            synchronized (dress){
                System.out.println("。。。。。。。。。。。。。。");
            }
        }
    }
}
​
class Dress{
​
}
​
class Trousers{
​
}

9.守护线程

守护线程(Daemon Thread)是一种特殊类型的线程,它的主要作用是为其他线程提供服务和支持。守护线程通常用于在后台执行任务,不会阻止程序的主线程或其他非守护线程继续执行。当所有非守护线程都结束时,守护线程会自动退出,无论它是否完成了任务。

守护线程的主要作用包括:

  1. 垃圾回收(Garbage Collection): Java 的垃圾回收器通常作为守护线程运行,负责回收不再使用的对象,释放内存。

  2. 后台任务: 守护线程可以用于执行一些后台任务,例如定期清理临时文件、日志文件的轮换、网络数据的监控等。

  3. 服务提供者: 守护线程可以作为某种服务的提供者,等待客户端请求并提供服务,例如网络服务、定时任务等。

需要注意的是,守护线程并不适用于所有情况。因为它会在程序的其他线程都结束后自动退出,所以如果它在执行任务时有必要等待一些操作完成,那么可能会导致任务未能完成。通常情况下,守护线程用于执行一些后台工作,而不是需要确保一定完成的关键任务。

在 Java 中,可以通过 setDaemon(true) 方法将一个线程设置为守护线程,例如:

java
Thread daemonThread = new Thread(() -> {
    // 守护线程的任务
});
daemonThread.setDaemon(true); // 将线程设置为守护线程
daemonThread.start(); // 启动线程

需要注意的是,守护线程的状态不应该被依赖,因为它可能在任何时候退出,包括未完成的任务。

那么守护线程一般用来什么呢?

守护线程一般用来执行一些不需要等待的、在后台默默运行的任务,它们的主要作用包括:

  1. 垃圾回收(Garbage Collection): Java 虚拟机中的垃圾回收线程就是守护线程。它负责回收不再使用的对象,释放内存空间。垃圾回收通常是一个周期性的后台任务,不需要干扰应用程序的正常运行。

  2. 定时任务: 守护线程可以用于执行定时任务,例如定时清理临时文件、定时备份数据等。这些任务通常需要在后台执行,不需要阻塞主线程。

  3. 日志处理: 在某些情况下,日志记录可以作为守护线程来处理,特别是当日志处理不是应用程序的核心功能时。

  4. 网络服务: 守护线程可以用于提供网络服务,等待客户端请求并提供响应。这可以是一个简单的聊天服务器、文件服务器等。

  5. 监控任务: 守护线程可以用于监控系统状态、资源利用率等信息,以便在需要时采取适当的措施。

总的来说,守护线程通常用于执行一些不需要与用户交互或阻塞主要应用程序流程的任务。它们在后台默默运行,提供支持和服务,但不干扰主要业务逻辑的执行。需要注意的是,守护线程在主线程结束后会自动退出,所以不应该用于执行关键任务或需要确保完全执行的操作。

10.定时器

定时器(Timer)是 Java 中用于执行定时任务的工具类。它允许你在未来的某个时间点或以固定的时间间隔执行指定的任务。在 Java 中,定时器主要通过 java.util.Timer 类来实现。

现实中业务:每天晚上数据备份

Java 定时器的一些关键概念和用法:

  1. Timer 类: 定时器的核心类是 java.util.Timer。你可以创建一个 Timer 实例,并使用它来安排任务的执行。

  2. TimerTask 类: 定时器任务(TimerTask)是一个抽象类,你需要继承它并实现 run 方法来定义你要执行的任务。然后,将 TimerTask 对象传递给 Timer 来安排任务的执行。

  3. 定时任务的安排: 你可以使用 Timerschedule 方法来安排任务的执行。它有多个重载版本,允许你指定任务、首次执行的延迟时间以及任务执行的时间间隔。

  4. 取消任务: 如果需要取消已安排的任务,你可以使用 cancel 方法来取消任务的执行。

import java.util.Timer;
import java.util.TimerTask;
​
public class TimerExample {
    public static void main(String[] args) {
        Timer timer = new Timer();
​
        TimerTask task = new TimerTask() {
            @Override
            public void run() {
                System.out.println("定时任务执行了!");
            }
        };
​
        // 安排任务在延迟 2 秒后执行,并每隔 3 秒重复执行一次
        timer.schedule(task, 2000, 3000);
    }
}

在这个示例中,我们创建了一个 Timer 实例,然后定义了一个 TimerTask 的匿名内部类,重写了 run 方法来指定任务的执行内容。接着,我们使用 schedule 方法安排了任务的执行,首次执行延迟 2 秒,之后每隔 3 秒重复执行一次。

需要注意的是,Java 5 以后,推荐使用 java.util.concurrent 包中的定时任务框架,如 ScheduledExecutorService,因为它提供了更灵活和强大的任务调度功能。

定时器和线程有什么关系?组合起来用法?

定时器(Timer)和线程(Thread)可以结合使用,以实现在后台执行定时任务的目的。通常,你可以创建一个定时器(Timer)实例,然后将定时器任务(TimerTask)与线程一起使用,使定时任务在后台线程中按计划执行。

以下是如何将定时器和线程结合使用的基本步骤:

  1. 创建一个 Timer 实例。

  2. 创建一个 TimerTask 的子类,重写 run 方法来定义要执行的任务。

  3. 创建一个后台线程,该线程将定时器任务提交给 Timer 执行。

  4. 在后台线程中启动定时器,并将定时器任务安排到计划中。

  5. 控制后台线程的生命周期,以便在需要时停止线程和定时器。

import java.util.Timer;
import java.util.TimerTask;
​
public class TimerWithThreadExample {
    public static void main(String[] args) {
        // 创建一个Timer实例
        Timer timer = new Timer();
​
        // 创建一个TimerTask,定义要执行的任务
        TimerTask task = new TimerTask() {
            @Override
            public void run() {
                System.out.println("定时任务执行了!");
            }
        };
​
        // 创建一个后台线程并启动它
        Thread thread = new Thread(() -> {
            // 安排任务在延迟2秒后执行,并每隔3秒重复执行一次
            timer.schedule(task, 2000, 3000);
        });
        thread.start();
​
        // 主线程等待一段时间后停止后台线程和定时器
        try {
            Thread.sleep(10000); // 主线程等待10秒
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
​
        // 停止后台线程和定时器
        thread.interrupt();
        timer.cancel();
    }
}

在此示例中,我们创建了一个 Timer 实例和一个 TimerTask 子类。然后,我们创建一个后台线程,在该线程中将定时器任务提交给定时器执行。主线程等待一段时间后,通过调用 thread.interrupt() 停止后台线程,并通过 timer.cancel() 停止定时器。

这种方法使你能够在后台线程中执行定时任务,而不会阻塞主线程。需要注意的是,线程的生命周期需要小心管理,以确保在不需要时正确停止线程和定时器。此外,Java 5 及更高版本的开发者通常更倾向于使用 ScheduledExecutorService 来执行定时任务,因为它提供了更灵活和高级的任务调度功能。

匿名内部类

匿名内部类是一种在使用时不需要明确命名的内部类,通常用于创建临时的、一次性的类实例。这种类通常是在方法参数中创建的,或者作为某个类的成员。匿名内部类允许你在创建对象的同时定义和实现这个对象的类。

具体代码举例
public class AnonymousInnerClassExample {
    public static void main(String[] args) {
        // 使用匿名内部类创建Runnable对象并实现run方法
        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                System.out.println("这是一个匿名内部类的示例");
                //这里在创建实例化对象的时候重写run方法就=使用了匿名内部类
            }
        };
​
        // 创建线程并启动
        Thread thread = new Thread(runnable);
        thread.start();
    }
}
理解

匿名内部类就是在实例化的时候在后面重写类中的方法,这种重写只能该类使用,优点是轻便简洁不需要再重新去编写一个类来完成该事件。

实现线程的第三种方式:实现Callable接口(JDK8新特性)

这种方式实现的线程可以获取线程的返回值。

之前讲解的那两种方式是无法获取线程返回值的,因为run方法返回void。

任务需求:

系统委派一个线程去执行一个任务,该线程执行完任务之后,可能会有一个执行结果,我们怎么能拿到这个执行结果呢? 使用第三种方式:实现Callable接口方式。

优点

可以获取到线程的执行结果。

缺点

效率比较低,在获取t线程执行结果的时候,当前线程受阻塞,效率较低。

public class ThreadTest15 {
    public static void main(String[] args) throws Exception {  
    // 第一步:创建一个“未来任务类”对象。
    // 参数非常重要,需要给一个Callable接口实现类对象。
    FutureTask task = new FutureTask(new Callable() {
        @Override
        public Object call() throws Exception { // call()方法就相当于run方法。只不过这个有返回值
            // 线程执行一个任务,执行之后可能会有一个执行结果
            // 模拟执行
            System.out.println("call method begin");
            Thread.sleep(1000 * 10);
            System.out.println("call method end!");
            int a = 100;
            int b = 200;
            return a + b; //自动装箱(300结果变成Integer)
        }
    });
​
    // 创建线程对象
    Thread t = new Thread(task);
​
    // 启动线程
    t.start();
​
    // 这里是main方法,这是在主线程中。
    // 在主线程中,怎么获取t线程的返回结果?
    // get()方法的执行会导致“当前线程阻塞”
    Object obj = task.get();
    System.out.println("线程执行结果:" + obj);
​
    // main方法这里的程序要想执行必须等待get()方法的结束
    // 而get()方法可能需要很久。因为get()方法是为了拿另一个线程的执行结果
    // 另一个线程执行是需要时间的。
    System.out.println("hello world!");
}
}

三种创建方法:Thread、Runnable、Callable(线程有返回值、但是效率较低)

本篇文章为复习文章观看的文章为Java多线程(超详细!)_java 多线程_一个快乐的野指针~的博客-优快云博客

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值