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

Java线程知识点总结

文章目录

  • Java 线程基础
    • 线程简介
      • 什么是进程
      • 什么是线程
      • 进程和线程的区别
    • 创建线程
      • Thread
      • Runnable
      • Callable、Future、FutureTask
        • Callable
        • Future
        • FutureTask
        • Callable + Future + FutureTask 示例
    • 线程基本用法
      • 线程休眠
      • 线程礼让
      • 终止线程
      • 守护线程
    • 线程通信
      • wait/notify/notifyAll
      • join
      • 管道
    • 线程生命周期
    • 线程常见问题
      • sleep、yield、join 方法有什么区别
      • 为什么 sleep 和 yield 方法是静态的
      • Java 线程是否按照线程优先级严格执行
      • 一个线程两次调用 start()方法会怎样
      • `start` 和 `run` 方法有什么区别
      • 可以直接调用 `Thread` 类的 `run` 方法么
    • 参考资料

Java 线程基础

关键词:ThreadRunnableCallableFuturewaitnotifynotifyAlljoinsleepyeild线程状态线程通信

线程简介

什么是进程

简言之,进程可视为一个正在运行的程序。它是系统运行程序的基本单位,因此进程是动态的。进程是具有一定独立功能的程序关于某个数据集合上的一次运行活动。进程是操作系统进行资源分配的基本单位。

什么是线程

线程是操作系统进行调度的基本单位。线程也叫轻量级进程(Light Weight Process),在一个进程里可以创建多个线程,这些线程都拥有各自的计数器、堆栈和局部变量等属性,并且能够访问共享的内存变量。

进程和线程的区别

  • 一个程序至少有一个进程,一个进程至少有一个线程。
  • 线程比进程划分更细,所以执行开销更小,并发性更高。
  • 进程是一个实体,拥有独立的资源;而同一个进程中的多个线程共享进程的资源。

创建线程

创建线程有三种方式:

  • 继承 Thread
  • 实现 Runnable 接口
  • 实现 Callable 接口

Thread

通过继承 Thread 类创建线程的步骤:

  1. 定义 Thread 类的子类,并覆写该类的 run 方法。run 方法的方法体就代表了线程要完成的任务,因此把 run 方法称为执行体。
  2. 创建 Thread 子类的实例,即创建了线程对象。
  3. 调用线程对象的 start 方法来启动该线程。
public class ThreadDemo {public static void main(String[] args) {// 实例化对象MyThread tA = new MyThread("Thread 线程-A");MyThread tB = new MyThread("Thread 线程-B");// 调用线程主体tA.start();tB.start();}static class MyThread extends Thread {private int ticket = 5;MyThread(String name) {super(name);}@Overridepublic void run() {while (ticket > 0) {System.out.println(Thread.currentThread().getName() + " 卖出了第 " + ticket + " 张票");ticket--;}}}}

Runnable

实现 Runnable 接口优于继承 Thread,因为:

  • Java 不支持多重继承,所有的类都只允许继承一个父类,但可以实现多个接口。如果继承了 Thread 类就无法继承其它类,这不利于扩展。
  • 类可能只要求可执行就行,继承整个 Thread 类开销过大。

通过实现 Runnable 接口创建线程的步骤:

  1. 定义 Runnable 接口的实现类,并覆写该接口的 run 方法。该 run 方法的方法体同样是该线程的线程执行体。
  2. 创建 Runnable 实现类的实例,并以此实例作为 Thread 的 target 来创建 Thread 对象,该 Thread 对象才是真正的线程对象。
  3. 调用线程对象的 start 方法来启动该线程。
public class RunnableDemo {public static void main(String[] args) {// 实例化对象Thread tA = new Thread(new MyThread(), "Runnable 线程-A");Thread tB = new Thread(new MyThread(), "Runnable 线程-B");// 调用线程主体tA.start();tB.start();}static class MyThread implements Runnable {private int ticket = 5;@Overridepublic void run() {while (ticket > 0) {System.out.println(Thread.currentThread().getName() + " 卖出了第 " + ticket + " 张票");ticket--;}}}}

Callable、Future、FutureTask

继承 Thread 类和实现 Runnable 接口这两种创建线程的方式都没有返回值。所以,线程执行完后,无法得到执行结果。但如果期望得到执行结果该怎么做?

为了解决这个问题,Java 1.5 后,提供了 Callable 接口和 Future 接口,通过它们,可以在线程执行结束后,返回执行结果。

Callable

Callable 接口只声明了一个方法,这个方法叫做 call():

public interface Callable<V> {/*** Computes a result, or throws an exception if unable to do so.** @return computed result* @throws Exception if unable to compute a result*/V call() throws Exception;
}

那么怎么使用 Callable 呢?一般情况下是配合 ExecutorService 来使用的,在 ExecutorService 接口中声明了若干个 submit 方法的重载版本:

<T> Future<T> submit(Callable<T> task);
<T> Future<T> submit(Runnable task, T result);
Future<?> submit(Runnable task);

第一个 submit 方法里面的参数类型就是 Callable。

Future

Future 就是对于具体的 Callable 任务的执行结果进行取消、查询是否完成、获取结果。必要时可以通过 get 方法获取执行结果,该方法会阻塞直到任务返回结果。

public interface Future<V> {boolean cancel(boolean mayInterruptIfRunning);boolean isCancelled();boolean isDone();V get() throws InterruptedException, ExecutionException;V get(long timeout, TimeUnit unit)throws InterruptedException, ExecutionException, TimeoutException;
}

FutureTask

FutureTask 类实现了 RunnableFuture 接口,RunnableFuture 继承了 Runnable 接口和 Future 接口。

所以,FutureTask 既可以作为 Runnable 被线程执行,又可以作为 Future 得到 Callable 的返回值。

public class FutureTask<V> implements RunnableFuture<V> {// ...public FutureTask(Callable<V> callable) {}public FutureTask(Runnable runnable, V result) {}
}public interface RunnableFuture<V> extends Runnable, Future<V> {void run();
}

事实上,FutureTask 是 Future 接口的一个唯一实现类。

Callable + Future + FutureTask 示例

通过实现 Callable 接口创建线程的步骤:

  1. 创建 Callable 接口的实现类,并实现 call 方法。该 call 方法将作为线程执行体,并且有返回值。
  2. 创建 Callable 实现类的实例,使用 FutureTask 类来包装 Callable 对象,该 FutureTask 对象封装了该 Callable 对象的 call 方法的返回值。
  3. 使用 FutureTask 对象作为 Thread 对象的 target 创建并启动新线程。
  4. 调用 FutureTask 对象的 get 方法来获得线程执行结束后的返回值。
public class CallableDemo {public static void main(String[] args) {Callable<Long> callable = new MyThread();FutureTask<Long> future = new FutureTask<>(callable);new Thread(future, "Callable 线程").start();try {System.out.println("任务耗时:" + (future.get() / 1000000) + "毫秒");} catch (InterruptedException | ExecutionException e) {e.printStackTrace();}}static class MyThread implements Callable<Long> {private int ticket = 10000;@Overridepublic Long call() {long begin = System.nanoTime();while (ticket > 0) {System.out.println(Thread.currentThread().getName() + " 卖出了第 " + ticket + " 张票");ticket--;}long end = System.nanoTime();return (end - begin);}}}

线程基本用法

线程(Thread)基本方法清单:

方法描述
run线程的执行实体。
start线程的启动方法。
currentThread返回对当前正在执行的线程对象的引用。
setName设置线程名称。
getName获取线程名称。
setPriority设置线程优先级。Java 中的线程优先级的范围是 [1,10],一般来说,高优先级的线程在运行时会具有优先权。可以通过 thread.setPriority(Thread.MAX_PRIORITY) 的方式设置,默认优先级为 5。
getPriority获取线程优先级。
setDaemon设置线程为守护线程。
isDaemon判断线程是否为守护线程。
isAlive判断线程是否启动。
interrupt中断另一个线程的运行状态。
interrupted测试当前线程是否已被中断。通过此方法可以清除线程的中断状态。换句话说,如果要连续调用此方法两次,则第二次调用将返回 false(除非当前线程在第一次调用清除其中断状态之后且在第二次调用检查其状态之前再次中断)。
join可以使一个线程强制运行,线程强制运行期间,其他线程无法运行,必须等待此线程完成之后才可以继续执行。
Thread.sleep静态方法。将当前正在执行的线程休眠。
Thread.yield静态方法。将当前正在执行的线程暂停,让其他线程执行。

线程休眠

使用 Thread.sleep 方法可以使得当前正在执行的线程进入休眠状态。

使用 Thread.sleep 需要向其传入一个整数值,这个值表示线程将要休眠的毫秒数。

Thread.sleep 方法可能会抛出 InterruptedException,因为异常不能跨线程传播回 main 中,因此必须在本地进行处理。线程中抛出的其它异常也同样需要在本地进行处理。

public class ThreadSleepDemo {public static void main(String[] args) {new Thread(new MyThread("线程A", 500)).start();new Thread(new MyThread("线程B", 1000)).start();new Thread(new MyThread("线程C", 1500)).start();}static class MyThread implements Runnable {/** 线程名称 */private String name;/** 休眠时间 */private int time;private MyThread(String name, int time) {this.name = name;this.time = time;}@Overridepublic void run() {try {// 休眠指定的时间Thread.sleep(this.time);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(this.name + "休眠" + this.time + "毫秒。");}}}

线程礼让

Thread.yield 方法的调用声明了当前线程已经完成了生命周期中最重要的部分,可以切换给其它线程来执行 。

该方法只是对线程调度器的一个建议,而且也只是建议具有相同优先级的其它线程可以运行。

public class ThreadYieldDemo {public static void main(String[] args) {MyThread t = new MyThread();new Thread(t, "线程A").start();new Thread(t, "线程B").start();}static class MyThread implements Runnable {@Overridepublic void run() {for (int i = 0; i < 5; i++) {try {Thread.sleep(1000);} catch (Exception e) {e.printStackTrace();}System.out.println(Thread.currentThread().getName() + "运行,i = " + i);if (i == 2) {System.out.print("线程礼让:");Thread.yield();}}}}
}

终止线程

Thread 中的 stop 方法有缺陷,已废弃

使用 Thread.stop 停止线程会导致它解锁所有已锁定的监视器(由于未经检查的 ThreadDeath 异常会在堆栈中传播,这是自然的结果)。 如果先前由这些监视器保护的任何对象处于不一致状态,则损坏的对象将对其他线程可见,从而可能导致任意行为。

stop() 方法会真的杀死线程,不给线程喘息的机会,如果线程持有 ReentrantLock 锁,被 stop() 的线程并不会自动调用 ReentrantLock 的 unlock() 去释放锁,那其他线程就再也没机会获得 ReentrantLock 锁,这实在是太危险了。所以该方法就不建议使用了,类似的方法还有 suspend() 和 resume() 方法,这两个方法同样也都不建议使用了,所以这里也就不多介绍了。Thread.stop 的许多用法应由仅修改某些变量以指示目标线程应停止运行的代码代替。 目标线程应定期检查此变量,如果该变量指示要停止运行,则应按有序方式从其运行方法返回。如果目标线程等待很长时间(例如,在条件变量上),则应使用中断方法来中断等待。

当一个线程运行时,另一个线程可以直接通过 interrupt 方法中断其运行状态。

public class ThreadInterruptDemo {public static void main(String[] args) {MyThread mt = new MyThread(); // 实例化Runnable子类对象Thread t = new Thread(mt, "线程"); // 实例化Thread对象t.start(); // 启动线程try {Thread.sleep(2000); // 线程休眠2秒} catch (InterruptedException e) {System.out.println("3、main线程休眠被终止");}t.interrupt(); // 中断线程执行}static class MyThread implements Runnable {@Overridepublic void run() {System.out.println("1、进入run()方法");try {Thread.sleep(10000); // 线程休眠10秒System.out.println("2、已经完成了休眠");} catch (InterruptedException e) {System.out.println("3、MyThread线程休眠被终止");return; // 返回调用处}System.out.println("4、run()方法正常结束");}}
}

如果一个线程的 run 方法执行一个无限循环,并且没有执行 sleep 等会抛出 InterruptedException 的操作,那么调用线程的 interrupt 方法就无法使线程提前结束。

但是调用 interrupt 方法会设置线程的中断标记,此时调用 interrupted 方法会返回 true。因此可以在循环体中使用 interrupted 方法来判断线程是否处于中断状态,从而提前结束线程。

安全地终止线程有两种方法:

  • 定义 volatile 标志位,在 run 方法中使用标志位控制线程终止
  • 使用 interrupt 方法和 Thread.interrupted 方法配合使用来控制线程终止

【示例】使用 volatile 标志位控制线程终止

public class ThreadStopDemo2 {public static void main(String[] args) throws Exception {MyTask task = new MyTask();Thread thread = new Thread(task, "MyTask");thread.start();TimeUnit.MILLISECONDS.sleep(50);task.cancel();}private static class MyTask implements Runnable {private volatile boolean flag = true;private volatile long count = 0L;@Overridepublic void run() {System.out.println(Thread.currentThread().getName() + " 线程启动");while (flag) {System.out.println(count++);}System.out.println(Thread.currentThread().getName() + " 线程终止");}/*** 通过 volatile 标志位来控制线程终止*/public void cancel() {flag = false;}}}

【示例】使用 interrupt 方法和 Thread.interrupted 方法配合使用来控制线程终止

public class ThreadStopDemo3 {public static void main(String[] args) throws Exception {MyTask task = new MyTask();Thread thread = new Thread(task, "MyTask");thread.start();TimeUnit.MILLISECONDS.sleep(50);thread.interrupt();}private static class MyTask implements Runnable {private volatile long count = 0L;@Overridepublic void run() {System.out.println(Thread.currentThread().getName() + " 线程启动");// 通过 Thread.interrupted 和 interrupt 配合来控制线程终止while (!Thread.interrupted()) {System.out.println(count++);}System.out.println(Thread.currentThread().getName() + " 线程终止");}}
}

守护线程

什么是守护线程?

  • 守护线程(Daemon Thread)是在后台执行并且不会阻止 JVM 终止的线程当所有非守护线程结束时,程序也就终止,同时会杀死所有守护线程
  • 与守护线程(Daemon Thread)相反的,叫用户线程(User Thread),也就是非守护线程。

为什么需要守护线程?

  • 守护线程的优先级比较低,用于为系统中的其它对象和线程提供服务。典型的应用就是垃圾回收器。

如何使用守护线程?

  • 可以使用 isDaemon 方法判断线程是否为守护线程。
  • 可以使用 setDaemon 方法设置线程为守护线程。
    • 正在运行的用户线程无法设置为守护线程,所以 setDaemon 必须在 thread.start 方法之前设置,否则会抛出 llegalThreadStateException 异常;
    • 一个守护线程创建的子线程依然是守护线程。
    • 不要认为所有的应用都可以分配给守护线程来进行服务,比如读写操作或者计算逻辑。
public class ThreadDaemonDemo {public static void main(String[] args) {Thread t = new Thread(new MyThread(), "线程");t.setDaemon(true); // 此线程在后台运行System.out.println("线程 t 是否是守护进程:" + t.isDaemon());t.start(); // 启动线程}static class MyThread implements Runnable {@Overridepublic void run() {while (true) {System.out.println(Thread.currentThread().getName() + "在运行。");}}}
}

参考阅读:Java 中守护线程的总结

线程通信

当多个线程可以一起工作去解决某个问题时,如果某些部分必须在其它部分之前完成,那么就需要对线程进行协调。

wait/notify/notifyAll

  • wait - wait 会自动释放当前线程占有的对象锁,并请求操作系统挂起当前线程,让线程从 Running 状态转入 Waiting 状态,等待 notify / notifyAll 来唤醒。如果没有释放锁,那么其它线程就无法进入对象的同步方法或者同步控制块中,那么就无法执行 notify 或者 notifyAll 来唤醒挂起的线程,造成死锁。
  • notify - 唤醒一个正在 Waiting 状态的线程,并让它拿到对象锁,具体唤醒哪一个线程由 JVM 控制 。
  • notifyAll - 唤醒所有正在 Waiting 状态的线程,接下来它们需要竞争对象锁。

注意:

  • waitnotifynotifyAll 都是 Object 类中的方法,而非 Thread
  • waitnotifynotifyAll 只能用在 synchronized 方法或者 synchronized 代码块中使用,否则会在运行时抛出 IllegalMonitorStateException

为什么 waitnotifynotifyAll 不定义在 Thread 中?为什么 waitnotifynotifyAll 要配合 synchronized 使用?

首先,需要了解几个基本知识点:

  • 每一个 Java 对象都有一个与之对应的 监视器(monitor)
  • 每一个监视器里面都有一个 对象锁 、一个 等待队列、一个 同步队列

了解了以上概念,我们回过头来理解前面两个问题。

为什么这几个方法不定义在 Thread 中?

由于每个对象都拥有对象锁,让当前线程等待某个对象锁,自然应该基于这个对象(Object)来操作,而非使用当前线程(Thread)来操作。因为当前线程可能会等待多个线程的锁,如果基于线程(Thread)来操作,就非常复杂了。

为什么 waitnotifynotifyAll 要配合 synchronized 使用?

如果调用某个对象的 wait 方法,当前线程必须拥有这个对象的对象锁,因此调用 wait 方法必须在 synchronized 方法和 synchronized 代码块中。

生产者、消费者模式是 waitnotifynotifyAll 的一个经典使用案例:

public class ThreadWaitNotifyDemo02 {private static final int QUEUE_SIZE = 10;private static final PriorityQueue<Integer> queue = new PriorityQueue<>(QUEUE_SIZE);public static void main(String[] args) {new Producer("生产者A").start();new Producer("生产者B").start();new Consumer("消费者A").start();new Consumer("消费者B").start();}static class Consumer extends Thread {Consumer(String name) {super(name);}@Overridepublic void run() {while (true) {synchronized (queue) {while (queue.size() == 0) {try {System.out.println("队列空,等待数据");queue.wait();} catch (InterruptedException e) {e.printStackTrace();queue.notifyAll();}}queue.poll(); // 每次移走队首元素queue.notifyAll();try {Thread.sleep(500);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(Thread.currentThread().getName() + " 从队列取走一个元素,队列当前有:" + queue.size() + "个元素");}}}}static class Producer extends Thread {Producer(String name) {super(name);}@Overridepublic void run() {while (true) {synchronized (queue) {while (queue.size() == QUEUE_SIZE) {try {System.out.println("队列满,等待有空余空间");queue.wait();} catch (InterruptedException e) {e.printStackTrace();queue.notifyAll();}}queue.offer(1); // 每次插入一个元素queue.notifyAll();try {Thread.sleep(500);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(Thread.currentThread().getName() + " 向队列取中插入一个元素,队列当前有:" + queue.size() + "个元素");}}}}
}

join

在线程操作中,可以使用 join 方法让一个线程强制运行,线程强制运行期间,其他线程无法运行,必须等待此线程完成之后才可以继续执行。

public class ThreadJoinDemo {public static void main(String[] args) {MyThread mt = new MyThread(); // 实例化Runnable子类对象Thread t = new Thread(mt, "mythread"); // 实例化Thread对象t.start(); // 启动线程for (int i = 0; i < 50; i++) {if (i > 10) {try {t.join(); // 线程强制运行} catch (InterruptedException e) {e.printStackTrace();}}System.out.println("Main 线程运行 --> " + i);}}static class MyThread implements Runnable {@Overridepublic void run() {for (int i = 0; i < 50; i++) {System.out.println(Thread.currentThread().getName() + " 运行,i = " + i); // 取得当前线程的名字}}}
}

管道

管道输入/输出流和普通的文件输入/输出流或者网络输入/输出流不同之处在于,它主要用于线程之间的数据传输,而传输的媒介为内存。
管道输入/输出流主要包括了如下 4 种具体实现:PipedOutputStreamPipedInputStreamPipedReaderPipedWriter,前两种面向字节,而后两种面向字符。

public class Piped {public static void main(String[] args) throws Exception {PipedWriter out = new PipedWriter();PipedReader in = new PipedReader();// 将输出流和输入流进行连接,否则在使用时会抛出IOExceptionout.connect(in);Thread printThread = new Thread(new Print(in), "PrintThread");printThread.start();int receive = 0;try {while ((receive = System.in.read()) != -1) {out.write(receive);}} finally {out.close();}}static class Print implements Runnable {private PipedReader in;Print(PipedReader in) {this.in = in;}public void run() {int receive = 0;try {while ((receive = in.read()) != -1) {System.out.print((char) receive);}} catch (IOException e) {e.printStackTrace();}}}
}

线程生命周期

在这里插入图片描述

java.lang.Thread.State 中定义了 6 种不同的线程状态,在给定的一个时刻,线程只能处于其中的一个状态。

以下是各状态的说明,以及状态间的联系:

  • 新建(New) - 尚未调用 start 方法的线程处于此状态。此状态意味着:创建的线程尚未启动

  • 就绪(Runnable) - 已经调用了 start 方法的线程处于此状态。此状态意味着:线程已经在 JVM 中运行。但是在操作系统层面,它可能处于运行状态,也可能等待资源调度(例如处理器资源),资源调度完成就进入运行状态。所以该状态的可运行是指可以被运行,具体有没有运行要看底层操作系统的资源调度。

  • 阻塞(Blocked) - 此状态意味着:线程处于被阻塞状态。表示线程在等待 synchronized 的隐式锁(Monitor lock)。synchronized 修饰的方法、代码块同一时刻只允许一个线程执行,其他线程只能等待,即处于阻塞状态。当占用 synchronized 隐式锁的线程释放锁,并且等待的线程获得 synchronized 隐式锁时,就又会从 BLOCKED 转换到 RUNNABLE 状态。

  • 等待(Waiting) - 此状态意味着:线程无限期等待,直到被其他线程显式地唤醒。 阻塞和等待的区别在于,阻塞是被动的,它是在等待获取 synchronized 的隐式锁。而等待是主动的,通过调用 Object.wait 等方法进入。

    进入方法退出方法
    没有设置 Timeout 参数的 Object.wait 方法Object.notify / Object.notifyAll
    没有设置 Timeout 参数的 Thread.join 方法被调用的线程执行完毕
    LockSupport.park 方法(Java 并发包中的锁,都是基于它实现的)LockSupport.unpark
  • 定时等待(Timed waiting) - 此状态意味着:无需等待其它线程显式地唤醒,在一定时间之后会被系统自动唤醒

    进入方法退出方法
    Thread.sleep 方法时间结束
    获得 synchronized 隐式锁的线程,调用设置了 Timeout 参数的 Object.wait 方法时间结束 / Object.notify / Object.notifyAll
    设置了 Timeout 参数的 Thread.join 方法时间结束 / 被调用的线程执行完毕
    LockSupport.parkNanos 方法LockSupport.unpark
    LockSupport.parkUntil 方法LockSupport.unpark
  • 终止(Terminated) - 线程执行完 run 方法,或者因异常退出了 run 方法。此状态意味着:线程结束了生命周期。

线程常见问题

sleep、yield、join 方法有什么区别

  • yield 方法
    • yield 方法会 让线程从 Running 状态转入 Runnable 状态
    • 当调用了 yield 方法后,只有与当前线程相同或更高优先级的Runnable 状态线程才会获得执行的机会
  • sleep 方法
    • sleep 方法会 让线程从 Running 状态转入 Waiting 状态
    • sleep 方法需要指定等待的时间,超过等待时间后,JVM 会将线程从 Waiting 状态转入 Runnable 状态
    • 当调用了 sleep 方法后,无论什么优先级的线程都可以得到执行机会
    • sleep 方法不会释放“锁标志”,也就是说如果有 synchronized 同步块,其他线程仍然不能访问共享数据。
  • join
    • join 方法会 让线程从 Running 状态转入 Waiting 状态
    • 当调用了 join 方法后,当前线程必须等待调用 join 方法的线程结束后才能继续执行

为什么 sleep 和 yield 方法是静态的

Thread 类的 sleepyield 方法将处理 Running 状态的线程。

所以在其他处于非 Running 状态的线程上执行这两个方法是没有意义的。这就是为什么这些方法是静态的。它们可以在当前正在执行的线程中工作,并避免程序员错误的认为可以在其他非运行线程调用这些方法。

Java 线程是否按照线程优先级严格执行

即使设置了线程的优先级,也无法保证高优先级的线程一定先执行

原因在于线程优先级依赖于操作系统的支持,然而,不同的操作系统支持的线程优先级并不相同,不能很好的和 Java 中线程优先级一一对应。

一个线程两次调用 start()方法会怎样

Java 的线程是不允许启动两次的,第二次调用必然会抛出 IllegalThreadStateException,这是一种运行时异常,多次调用 start 被认为是编程错误。

startrun 方法有什么区别

  • run 方法是线程的执行体。
  • start 方法会启动线程,然后 JVM 会让这个线程去执行 run 方法。

可以直接调用 Thread 类的 run 方法么

  • 可以。但是如果直接调用 Threadrun 方法,它的行为就会和普通的方法一样。
  • 为了在新的线程中执行我们的代码,必须使用 Threadstart 方法。

参考资料

  • 进程和线程关系及区别
  • sleep(),wait(),yield()和 join()方法的区别
  • Java 并发编程:线程间协作的两种方式:wait、notify、notifyAll 和 Condition
  • Java 并发编程:Callable、Future 和 FutureTask
  • Java 中守护线程的总结
  • Java 并发

相关文章:

Java线程知识点总结

文章目录Java 线程基础线程简介什么是进程什么是线程进程和线程的区别创建线程ThreadRunnableCallable、Future、FutureTaskCallableFutureFutureTaskCallable Future FutureTask 示例线程基本用法线程休眠线程礼让终止线程守护线程线程通信wait/notify/notifyAlljoin管道线程…...

数据结构——第三章 栈与队列(4)

队列的应用1.基于队列的医院挂号模拟系统2.队列的运用1.基于队列的医院挂号模拟系统 代码实现分享 2.队列的运用 问题描述&#xff1a;某运动会设立N个比赛项目&#xff0c;每个运动成员可以参加1~3个项目。试问如何安排比赛日程&#xff0c;既可以使同一运动员参加的项目不…...

华为机试HJ73-计算日期到天数转换

HJ73 计算日期到天数转换 题目描述&#xff1a; 描述 根据输入的日期&#xff0c;计算是这一年的第几天。 保证年份为4位数且日期合法。 进阶&#xff1a;时间复杂度&#xff1a;O(n) &#xff0c;空间复杂度&#xff1a;O(1) 输入描述&#xff1a; 输入一行&#xff0c;每行…...

【阅读笔记】你不知道的JavaScript--this与对象2

目录this默认绑定隐式绑定隐式丢失显示绑定API 调用上下文new 绑定this 绑定优先级其余绑定例外对象字面量与对象属性描述符迭代器遍历this 默认绑定 默认绑定适配 独立函数调用 默认绑定 this 指向全局对象&#xff1b; 故直接调用函数&#xff0c;该函数内部的 this 即指向全…...

单板TVS接地不当造成辐射骚扰超标问题分析-EMC

【摘要】 某产品EMC辐射骚扰测试超标&#xff0c;通过近远场扫描配合定位分析&#xff0c;逐步找出骚扰源、传播路径&#xff0c;最终通过修改 PCB 走线切断传播路径解决此问题。 1 故障现象 某产品在进行 EMC 研发摸底测试时发现&#xff0c;整机辐射骚扰垂直方向测试超标&a…...

用Python Flask为女朋友做一个简单的网站(附可运行的源码)

&#x1f31f;所属专栏&#xff1a;献给榕榕&#x1f414;作者简介&#xff1a;rchjr——五带信管菜只因一枚&#x1f62e;前言&#xff1a;该专栏系为女友准备的&#xff0c;里面会不定时发一些讨好她的技术作品&#xff0c;感兴趣的小伙伴可以关注一下~&#x1f449;文章简介…...

vue3+rust个人博客建站日记5-所有界面

没有数据的前端&#xff0c;是没有灵魂的。明明标题是vue3 rust &#xff0c;但日记撰写至今&#xff0c;似乎只有第一篇提及了Rust&#xff0c;这可不行。是时候一股作气&#xff0c;完成大部分页面绘制工作了&#xff01; 最后再说一次&#xff0c;时间要加速了。 ——普奇神…...

青少年软件编程C++一级真题(202212)

1、输入一个整数x&#xff0c;输出这个整数加1后的值&#xff0c;即x1的值。 时间限制&#xff1a;1000 内存限制&#xff1a;65536 输入 一个整数x&#xff08;0 ≤ x ≤ 1000&#xff09;。 输出 按题目要求输出一个整数。 样例输入 9样例输出 10 #include<iost…...

【Spring】AOP底层原理(动态代理)-》 AOP概念及术语 -》 AOP实现

个人简介&#xff1a;Java领域新星创作者&#xff1b;阿里云技术博主、星级博主、专家博主&#xff1b;正在Java学习的路上摸爬滚打&#xff0c;记录学习的过程~ 个人主页&#xff1a;.29.的博客 学习社区&#xff1a;进去逛一逛~ AOP - 面向切面编程一、简述AOP二、AOP底层原理…...

Java8 新特性 之 lambda 表达 和 函数式接口

—— lambda 表达式 概念 lambda 表达式是一个匿名函数&#xff0c;可以把 lambda 表达式理解为是一段可以传递的代码。更简洁、更灵活&#xff0c;使 Java 的语言表达能力得到了提升lambda 表达式是作为接口的实现类的对象&#xff08;万事万物皆对象&#xff09; 使用语法…...

Netty服务端和客户端开发实例

一、Netty服务端开发在开始使用 Netty 开发 TimeServer 之前&#xff0c;先回顾一下使用 NIO 进行服务端开发的步骤。(1)创建ServerSocketChannel&#xff0c;配置它为非阻塞模式;(2)绑定监听&#xff0c;配置TCP 参数&#xff0c;例如 backlog 大小;(3)创建一个独立的I/O线程&…...

linux基本指令和权限

目录 一.shell命令以及运行原理 二.Linux常用指令 1. ls 指令 2. pwd命令 3.cd指令 4. touch指令 5.mkdir指令&#xff08;重要&#xff09; 6.rmdir指令 && rm 指令&#xff08;重要&#xff09; 7.man指令&#xff08;重要&#xff09; 8.cp指令&#xff08;重要&…...

滚蛋吧,正则表达式!

大家好&#xff0c;我是良许。 不知道大家有没有被正则表达式支配过的恐惧&#xff1f;看着一行火星文一样的表达式&#xff0c;虽然每一个字符都认识&#xff0c;但放在一起直接就让人蒙圈了~ 你是不是也有这样的操作&#xff0c;比如你需要使用「电子邮箱正则表达式」&…...

序列号和反序列化--java--Serializable接口--json序列化普通使用

序列化和反序列化序列化和反序列化作用为什么需要用途Serializable使用serialVersionUID不设置的后果什么时候修改Externalizable序列化的顺序json序列化序列化和反序列化 序列化&#xff1a;把对象转换为字节序列的过程称为对象的序列化。 反序列化:把字节序列恢复为对象的过…...

Java异步任务编排

多线程创建的五种方式&#xff1a; 继承Thread类实现runnable接口。实现Callable接口 FutureTask(可以拿到返回结果&#xff0c;阻塞式等待。)线程池创建。 ExcutorService service Excutors.newFixedThreadPool(10); service.excute(new Runnable01());另外一种创建线程池…...

Hive与HBase的区别及应用场景

当数据量达到一定量级的时候&#xff0c;存储和统计计算查询都会遇到问题&#xff0c;今天了解一下Hive和Hbase的区别和应用场景。 一、定义 Hive是基于Hadoop的一个数据仓库工具&#xff0c;可以将结构化的数据文件映射为一张数据库表&#xff0c;并提供简单的sql查询功能&am…...

C++之单例模式

目录 1. 请设计一个类&#xff0c;只能在堆上创建对象 2. 请设计一个类&#xff0c;只能在栈上创建对象 3.请设计一个类&#xff0c;不能被拷贝 C98 C11 4. 请设计一个类&#xff0c;不能被继承 C98 C11 5. 请设计一个类&#xff0c;只能创建一个对象(单例模式) 设计…...

Redis十大类型——Set与Zset常见操作

Redis十大类型——Set与Zset常见操作Set命令操作简列基本操作展示删除移动剪切集合运算Zset基本操作简列添加展示反转按分数取值获取分数值删除分数操作下标操作如果我们对Java有所了解&#xff0c;相信大家很容易就明白Set&#xff0c;在Redis中也一样&#xff0c;Set的value值…...

车载雷达实战之Firmware内存优化

内存&#xff08;Memory&#xff09;是计算机中最重要的部件之一&#xff0c;计算机运时的程序以及数据都依赖它进行存储。内存主要分为随机存储器&#xff08;RAM&#xff09;,只读存储器&#xff08;ROM&#xff09;以及高速缓存&#xff08;Cache&#xff09;。仅仅雷达的原…...

【剑指Offer】JZ14--剪绳子

剪绳子详解1.问题描述2.解题思路3.具体实现1.问题描述 2.解题思路 首先想到的思路&#xff1a;因为是求乘积的最大值&#xff0c;所以如果截取剩下的是1&#xff0c;那还是它本身就没有意义。从此出发&#xff0c;考虑绳子长度是2、3、4、5…通过穷举法来找规律。 值–》拆分–…...

raspberry pi播放音视频

文章目录目的QMediaPlayerGStreamerwhat is GStreamer体系框架优势omxplayerwhat is omxplayercommand Linekey bindings运行过程中错误ALSA目的 实现在树莓派下外接扬声器&#xff0c; 播放某段音频&#xff0c; 进行回音测试。 QMediaPlayer 首先我的安装是5.11版本。 优先…...

【电子学会】2022年12月图形化二级 -- 老鹰捉小鸡

老鹰捉小鸡 小鸡正在农场上玩耍&#xff0c;突然从远处飞来一只老鹰&#xff0c;小鸡要快速回到鸡舍中&#xff0c;躲避老鹰的抓捕。 1. 准备工作 &#xff08;1&#xff09;删除默认白色背景&#xff0c;添加背景Farm&#xff1b; &#xff08;2&#xff09;删除默认角色小…...

C++的双端队列

双端队列介绍1.双端队列知识需知2.大试牛刀1.双端队列知识需知 由于队列是一种先进先出&#xff08;FIFO&#xff09;的数据结构&#xff0c;因此无法直接从队列的底部删除元素。如果希望从队列的底部删除元素&#xff0c;可以考虑使用双端队列&#xff08;deque&#xff09;。…...

【独家】华为OD机试 - 拼接 URL(C 语言解题)

最近更新的博客 华为od 2023 | 什么是华为od,od 薪资待遇,od机试题清单华为OD机试真题大全,用 Python 解华为机试题 | 机试宝典【华为OD机试】全流程解析+经验分享,题型分享,防作弊指南)华为od机试,独家整理 已参加机试人员的实战技巧文章目录 最近更新的博客使用说明本期…...

为什么使用Junit单元测试?Junit的详解

Hi I’m Shendi 为什么使用Junit单元测试&#xff1f;Junit的详解 Junit简介 Junit是一个Java语言的单元测试框架。 单元测试是一个对单一实体&#xff08;类或方法&#xff09;的测试 JUnit是由 Erich Gamma 和 Kent Beck 编写的一个回归测试框架&#xff08;regression test…...

怎么学好嵌入式Linux系统和驱动

嵌入式专业是一门实践性非常强的学科&#xff0c;只有多动手&#xff0c;多实践&#xff0c;多编程&#xff0c;多调试&#xff0c;多看书&#xff0c;多思考才能真正掌握好嵌入式开发技术。 现在很多同学也意识到了学校培养模式和社会需求脱节问题&#xff0c;有一部分同学也先…...

Spring Aware总结

概述 Spring中Aware到底是什么意思&#xff1f; 我们在看Spring源码的时候&#xff0c;经常可以看到xxxAwarexxx的身影&#xff0c;通常我会很疑惑&#xff0c;Aware到底是什么意思呢&#xff1f; 比如图片中这些包含Aware关键字的类或者接口。 我对下面3个类或接口进行了解…...

【RocketMQ】源码详解:Broker端消息刷盘流程

消息刷盘 同步入口&#xff1a;org.apache.rocketmq.store.CommitLog.GroupCommitService 异步入口&#xff1a;org.apache.rocketmq.store.CommitLog.FlushRealTimeService 刷盘有同步和异步两种&#xff0c;在实例化Commitlog的时候&#xff0c;会根据配置创建不同的服务 p…...

编码器SIQ-02FVS3驱动

一.简介 此编码器可以是功能非常强大&#xff0c;可以检测左右转动&#xff0c;和按键按下&#xff0c;所以说这一个编码器可以抵三个按键&#xff0c;而且体积非常小&#xff0c;使用起来比三个按键要高大尚&#xff0c;而且驱动也简单。唯一不足的点就是价格有点小贵6-8元才…...

【2021.9.7】记一次exe手动添加shellcode

【2021.9.7】记一次exe手动添加shellcode 文章目录【2021.9.7】记一次exe手动添加shellcode0.大致思路1.获取MessageBox的真实地址VA2.通过OD在代码段添加shellcode3.dump出数据,设置程序OEP4.测试dump出来的exe5.方法总结测试的exe和添加了shellcode的exe&#xff1a;链接&…...