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 线程基础
关键词:
Thread、Runnable、Callable、Future、wait、notify、notifyAll、join、sleep、yeild、线程状态、线程通信
线程简介
什么是进程
简言之,进程可视为一个正在运行的程序。它是系统运行程序的基本单位,因此进程是动态的。进程是具有一定独立功能的程序关于某个数据集合上的一次运行活动。进程是操作系统进行资源分配的基本单位。
什么是线程
线程是操作系统进行调度的基本单位。线程也叫轻量级进程(Light Weight Process),在一个进程里可以创建多个线程,这些线程都拥有各自的计数器、堆栈和局部变量等属性,并且能够访问共享的内存变量。
进程和线程的区别
- 一个程序至少有一个进程,一个进程至少有一个线程。
- 线程比进程划分更细,所以执行开销更小,并发性更高。
- 进程是一个实体,拥有独立的资源;而同一个进程中的多个线程共享进程的资源。
创建线程
创建线程有三种方式:
- 继承
Thread类 - 实现
Runnable接口 - 实现
Callable接口
Thread
通过继承 Thread 类创建线程的步骤:
- 定义
Thread类的子类,并覆写该类的run方法。run方法的方法体就代表了线程要完成的任务,因此把run方法称为执行体。 - 创建
Thread子类的实例,即创建了线程对象。 - 调用线程对象的
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 接口创建线程的步骤:
- 定义
Runnable接口的实现类,并覆写该接口的run方法。该run方法的方法体同样是该线程的线程执行体。 - 创建
Runnable实现类的实例,并以此实例作为Thread的 target 来创建Thread对象,该Thread对象才是真正的线程对象。 - 调用线程对象的
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 接口创建线程的步骤:
- 创建
Callable接口的实现类,并实现call方法。该call方法将作为线程执行体,并且有返回值。 - 创建
Callable实现类的实例,使用FutureTask类来包装Callable对象,该FutureTask对象封装了该Callable对象的call方法的返回值。 - 使用
FutureTask对象作为Thread对象的 target 创建并启动新线程。 - 调用
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状态的线程,接下来它们需要竞争对象锁。
注意:
wait、notify、notifyAll都是Object类中的方法,而非Thread。wait、notify、notifyAll只能用在synchronized方法或者synchronized代码块中使用,否则会在运行时抛出IllegalMonitorStateException。为什么
wait、notify、notifyAll不定义在Thread中?为什么wait、notify、notifyAll要配合synchronized使用?首先,需要了解几个基本知识点:
- 每一个 Java 对象都有一个与之对应的 监视器(monitor)
- 每一个监视器里面都有一个 对象锁 、一个 等待队列、一个 同步队列
了解了以上概念,我们回过头来理解前面两个问题。
为什么这几个方法不定义在
Thread中?由于每个对象都拥有对象锁,让当前线程等待某个对象锁,自然应该基于这个对象(
Object)来操作,而非使用当前线程(Thread)来操作。因为当前线程可能会等待多个线程的锁,如果基于线程(Thread)来操作,就非常复杂了。为什么
wait、notify、notifyAll要配合synchronized使用?如果调用某个对象的
wait方法,当前线程必须拥有这个对象的对象锁,因此调用wait方法必须在synchronized方法和synchronized代码块中。
生产者、消费者模式是 wait、notify、notifyAll 的一个经典使用案例:
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 种具体实现:PipedOutputStream、PipedInputStream、PipedReader 和 PipedWriter,前两种面向字节,而后两种面向字符。
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.unparkLockSupport.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同步块,其他线程仍然不能访问共享数据。
joinjoin方法会 让线程从Running状态转入Waiting状态。- 当调用了
join方法后,当前线程必须等待调用join方法的线程结束后才能继续执行。
为什么 sleep 和 yield 方法是静态的
Thread 类的 sleep 和 yield 方法将处理 Running 状态的线程。
所以在其他处于非 Running 状态的线程上执行这两个方法是没有意义的。这就是为什么这些方法是静态的。它们可以在当前正在执行的线程中工作,并避免程序员错误的认为可以在其他非运行线程调用这些方法。
Java 线程是否按照线程优先级严格执行
即使设置了线程的优先级,也无法保证高优先级的线程一定先执行。
原因在于线程优先级依赖于操作系统的支持,然而,不同的操作系统支持的线程优先级并不相同,不能很好的和 Java 中线程优先级一一对应。
一个线程两次调用 start()方法会怎样
Java 的线程是不允许启动两次的,第二次调用必然会抛出 IllegalThreadStateException,这是一种运行时异常,多次调用 start 被认为是编程错误。
start 和 run 方法有什么区别
run方法是线程的执行体。start方法会启动线程,然后 JVM 会让这个线程去执行run方法。
可以直接调用 Thread 类的 run 方法么
- 可以。但是如果直接调用
Thread的run方法,它的行为就会和普通的方法一样。 - 为了在新的线程中执行我们的代码,必须使用
Thread的start方法。
参考资料
- 进程和线程关系及区别
- 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.队列的运用 问题描述:某运动会设立N个比赛项目,每个运动成员可以参加1~3个项目。试问如何安排比赛日程,既可以使同一运动员参加的项目不…...
华为机试HJ73-计算日期到天数转换
HJ73 计算日期到天数转换 题目描述: 描述 根据输入的日期,计算是这一年的第几天。 保证年份为4位数且日期合法。 进阶:时间复杂度:O(n) ,空间复杂度:O(1) 输入描述: 输入一行,每行…...
【阅读笔记】你不知道的JavaScript--this与对象2
目录this默认绑定隐式绑定隐式丢失显示绑定API 调用上下文new 绑定this 绑定优先级其余绑定例外对象字面量与对象属性描述符迭代器遍历this 默认绑定 默认绑定适配 独立函数调用 默认绑定 this 指向全局对象; 故直接调用函数,该函数内部的 this 即指向全…...
单板TVS接地不当造成辐射骚扰超标问题分析-EMC
【摘要】 某产品EMC辐射骚扰测试超标,通过近远场扫描配合定位分析,逐步找出骚扰源、传播路径,最终通过修改 PCB 走线切断传播路径解决此问题。 1 故障现象 某产品在进行 EMC 研发摸底测试时发现,整机辐射骚扰垂直方向测试超标&a…...
用Python Flask为女朋友做一个简单的网站(附可运行的源码)
🌟所属专栏:献给榕榕🐔作者简介:rchjr——五带信管菜只因一枚😮前言:该专栏系为女友准备的,里面会不定时发一些讨好她的技术作品,感兴趣的小伙伴可以关注一下~👉文章简介…...
vue3+rust个人博客建站日记5-所有界面
没有数据的前端,是没有灵魂的。明明标题是vue3 rust ,但日记撰写至今,似乎只有第一篇提及了Rust,这可不行。是时候一股作气,完成大部分页面绘制工作了! 最后再说一次,时间要加速了。 ——普奇神…...
青少年软件编程C++一级真题(202212)
1、输入一个整数x,输出这个整数加1后的值,即x1的值。 时间限制:1000 内存限制:65536 输入 一个整数x(0 ≤ x ≤ 1000)。 输出 按题目要求输出一个整数。 样例输入 9样例输出 10 #include<iost…...
【Spring】AOP底层原理(动态代理)-》 AOP概念及术语 -》 AOP实现
个人简介:Java领域新星创作者;阿里云技术博主、星级博主、专家博主;正在Java学习的路上摸爬滚打,记录学习的过程~ 个人主页:.29.的博客 学习社区:进去逛一逛~ AOP - 面向切面编程一、简述AOP二、AOP底层原理…...
Java8 新特性 之 lambda 表达 和 函数式接口
—— lambda 表达式 概念 lambda 表达式是一个匿名函数,可以把 lambda 表达式理解为是一段可以传递的代码。更简洁、更灵活,使 Java 的语言表达能力得到了提升lambda 表达式是作为接口的实现类的对象(万事万物皆对象) 使用语法…...
Netty服务端和客户端开发实例
一、Netty服务端开发在开始使用 Netty 开发 TimeServer 之前,先回顾一下使用 NIO 进行服务端开发的步骤。(1)创建ServerSocketChannel,配置它为非阻塞模式;(2)绑定监听,配置TCP 参数,例如 backlog 大小;(3)创建一个独立的I/O线程&…...
linux基本指令和权限
目录 一.shell命令以及运行原理 二.Linux常用指令 1. ls 指令 2. pwd命令 3.cd指令 4. touch指令 5.mkdir指令(重要) 6.rmdir指令 && rm 指令(重要) 7.man指令(重要) 8.cp指令(重要&…...
滚蛋吧,正则表达式!
大家好,我是良许。 不知道大家有没有被正则表达式支配过的恐惧?看着一行火星文一样的表达式,虽然每一个字符都认识,但放在一起直接就让人蒙圈了~ 你是不是也有这样的操作,比如你需要使用「电子邮箱正则表达式」&…...
序列号和反序列化--java--Serializable接口--json序列化普通使用
序列化和反序列化序列化和反序列化作用为什么需要用途Serializable使用serialVersionUID不设置的后果什么时候修改Externalizable序列化的顺序json序列化序列化和反序列化 序列化:把对象转换为字节序列的过程称为对象的序列化。 反序列化:把字节序列恢复为对象的过…...
Java异步任务编排
多线程创建的五种方式: 继承Thread类实现runnable接口。实现Callable接口 FutureTask(可以拿到返回结果,阻塞式等待。)线程池创建。 ExcutorService service Excutors.newFixedThreadPool(10); service.excute(new Runnable01());另外一种创建线程池…...
Hive与HBase的区别及应用场景
当数据量达到一定量级的时候,存储和统计计算查询都会遇到问题,今天了解一下Hive和Hbase的区别和应用场景。 一、定义 Hive是基于Hadoop的一个数据仓库工具,可以将结构化的数据文件映射为一张数据库表,并提供简单的sql查询功能&am…...
C++之单例模式
目录 1. 请设计一个类,只能在堆上创建对象 2. 请设计一个类,只能在栈上创建对象 3.请设计一个类,不能被拷贝 C98 C11 4. 请设计一个类,不能被继承 C98 C11 5. 请设计一个类,只能创建一个对象(单例模式) 设计…...
Redis十大类型——Set与Zset常见操作
Redis十大类型——Set与Zset常见操作Set命令操作简列基本操作展示删除移动剪切集合运算Zset基本操作简列添加展示反转按分数取值获取分数值删除分数操作下标操作如果我们对Java有所了解,相信大家很容易就明白Set,在Redis中也一样,Set的value值…...
车载雷达实战之Firmware内存优化
内存(Memory)是计算机中最重要的部件之一,计算机运时的程序以及数据都依赖它进行存储。内存主要分为随机存储器(RAM),只读存储器(ROM)以及高速缓存(Cache)。仅仅雷达的原…...
【剑指Offer】JZ14--剪绳子
剪绳子详解1.问题描述2.解题思路3.具体实现1.问题描述 2.解题思路 首先想到的思路:因为是求乘积的最大值,所以如果截取剩下的是1,那还是它本身就没有意义。从此出发,考虑绳子长度是2、3、4、5…通过穷举法来找规律。 值–》拆分–…...
XML Group端口详解
在XML数据映射过程中,经常需要对数据进行分组聚合操作。例如,当处理包含多个物料明细的XML文件时,可能需要将相同物料号的明细归为一组,或对相同物料号的数量进行求和计算。传统实现方式通常需要编写脚本代码,增加了开…...
【大模型RAG】拍照搜题技术架构速览:三层管道、两级检索、兜底大模型
摘要 拍照搜题系统采用“三层管道(多模态 OCR → 语义检索 → 答案渲染)、两级检索(倒排 BM25 向量 HNSW)并以大语言模型兜底”的整体框架: 多模态 OCR 层 将题目图片经过超分、去噪、倾斜校正后,分别用…...
Vue3 + Element Plus + TypeScript中el-transfer穿梭框组件使用详解及示例
使用详解 Element Plus 的 el-transfer 组件是一个强大的穿梭框组件,常用于在两个集合之间进行数据转移,如权限分配、数据选择等场景。下面我将详细介绍其用法并提供一个完整示例。 核心特性与用法 基本属性 v-model:绑定右侧列表的值&…...
1688商品列表API与其他数据源的对接思路
将1688商品列表API与其他数据源对接时,需结合业务场景设计数据流转链路,重点关注数据格式兼容性、接口调用频率控制及数据一致性维护。以下是具体对接思路及关键技术点: 一、核心对接场景与目标 商品数据同步 场景:将1688商品信息…...
c++ 面试题(1)-----深度优先搜索(DFS)实现
操作系统:ubuntu22.04 IDE:Visual Studio Code 编程语言:C11 题目描述 地上有一个 m 行 n 列的方格,从坐标 [0,0] 起始。一个机器人可以从某一格移动到上下左右四个格子,但不能进入行坐标和列坐标的数位之和大于 k 的格子。 例…...
高等数学(下)题型笔记(八)空间解析几何与向量代数
目录 0 前言 1 向量的点乘 1.1 基本公式 1.2 例题 2 向量的叉乘 2.1 基础知识 2.2 例题 3 空间平面方程 3.1 基础知识 3.2 例题 4 空间直线方程 4.1 基础知识 4.2 例题 5 旋转曲面及其方程 5.1 基础知识 5.2 例题 6 空间曲面的法线与切平面 6.1 基础知识 6.2…...
【git】把本地更改提交远程新分支feature_g
创建并切换新分支 git checkout -b feature_g 添加并提交更改 git add . git commit -m “实现图片上传功能” 推送到远程 git push -u origin feature_g...
Spring Boot+Neo4j知识图谱实战:3步搭建智能关系网络!
一、引言 在数据驱动的背景下,知识图谱凭借其高效的信息组织能力,正逐步成为各行业应用的关键技术。本文聚焦 Spring Boot与Neo4j图数据库的技术结合,探讨知识图谱开发的实现细节,帮助读者掌握该技术栈在实际项目中的落地方法。 …...
IoT/HCIP实验-3/LiteOS操作系统内核实验(任务、内存、信号量、CMSIS..)
文章目录 概述HelloWorld 工程C/C配置编译器主配置Makefile脚本烧录器主配置运行结果程序调用栈 任务管理实验实验结果osal 系统适配层osal_task_create 其他实验实验源码内存管理实验互斥锁实验信号量实验 CMISIS接口实验还是得JlINKCMSIS 简介LiteOS->CMSIS任务间消息交互…...
【开发技术】.Net使用FFmpeg视频特定帧上绘制内容
目录 一、目的 二、解决方案 2.1 什么是FFmpeg 2.2 FFmpeg主要功能 2.3 使用Xabe.FFmpeg调用FFmpeg功能 2.4 使用 FFmpeg 的 drawbox 滤镜来绘制 ROI 三、总结 一、目的 当前市场上有很多目标检测智能识别的相关算法,当前调用一个医疗行业的AI识别算法后返回…...
