Java知识总结---并发篇

线程

线程的状态

Java中线程可以有如下6中状态:

  • NEW 新创建

  • RUNNABLE 可运行

  • BLOCKED 阻塞

  • WAITING 等待

  • TIMED WAITING 计时等待

  • TERMINATED 终止

线程的创建

(1)继承Thread类

public class ExtendsThread extends Thread {     @Override     public void run() {         System.out.println("1......");     }     public static void main(String[] args) {         new ExtendsThread().start();     } }

(2)实现 Runnable 接口

public class ImplementsRunnable implements Runnable {     @Override     public void run() {         System.out.println("2......");     }     public static void main(String[] args) {         ImplementsRunnable runnable = new ImplementsRunnable();         new Thread(runnable).start();     } }

(3)实现 Callable 接口

public class ImplementsCallable implements Callable<String> {     @Override     public String call() throws Exception {         System.out.println("3......");         return "zhuZi";     }     public static void main(String[] args) throws Exception {         ImplementsCallable callable = new ImplementsCallable();         FutureTask<String> futureTask = new FutureTask<>(callable);         new Thread(futureTask).start();         System.out.println(futureTask.get());     } }

(4)使用线程池

ublic class UseExecutorService {     public static void main(String[] args) {         ExecutorService poolA = Executors.newFixedThreadPool(2);         poolA.execute(()->{             System.out.println("4A......");         });         poolA.shutdown();         // 又或者自定义线程池         ThreadPoolExecutor poolB = new ThreadPoolExecutor(2, 3, 0,                 TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>(3),                 Executors.defaultThreadFactory(), new ThreadPoolExecutor.AbortPolicy());         poolB.submit(()->{             System.out.println("4B......");         });         poolB.shutdown();     } }

(5)使用 CompletableFuture 类

public class UseCompletableFuture {     public static void main(String[] args) throws InterruptedException {         CompletableFuture<String> cf = CompletableFuture.supplyAsync(() -> {             System.out.println("5......");             return "zhuZi";         });         // 需要阻塞,否则看不到结果         Thread.sleep(1000);     } }

(6)基于 ThreadGroup 线程组

public class UseThreadGroup {     public static void main(String[] args) {         ThreadGroup group = new ThreadGroup("groupName");         new Thread(group, ()->{             System.out.println("6-T1......");         }, "T1").start();         new Thread(group, ()->{             System.out.println("6-T2......");         }, "T2").start();         new Thread(group, ()->{             System.out.println("6-T3......");         }, "T3").start();     } }

(7) 使用 FutureTask 类

和实现 Callable 接口差不多,只不过匿名形式创建 Callable

public class UseFutureTask {     public static void main(String[] args) {         FutureTask<String> futureTask = new FutureTask<>(() -> {             System.out.println("7......");             return "zhuZi";         });         new Thread(futureTask).start();     } }

因为当你用一个类,继承Thread类时,它内部所有的方法,都会被继承过来,所以当前类可以直接调用start()方法启动,更具体点来说,在Java中,创建线程的方式就只有一种:调用Thread.start()方法!只有这种形式,才能在真正意义上创建一条线程!

而例如ExecutorService线程池、ForkJoin线程池、CompletableFuture类、Timer定时器类、parallelStream并行流……,如果有去看过它们源码的小伙伴应该清楚,它们最终都依赖于Thread.start()方法创建线程。

死锁的四个条件

  • 互斥条件:该资源任意一个时刻只由一个线程占用。

  • 请求与保持条件:一个线程因请求资源而阻塞时,对已获得的资源保持不放。

  • 不剥夺条件:线程已获得的资源在未使用完之前不能被其他线程强行剥夺,只有自己使用完毕后才释放资源。

  • 循环等待条件:若干线程之间形成一种头尾相接的循环等待资源关系


线程的常用方法

  • interrupted:对当前线程的中断标识进行复位。如果该线程已经处于终结状态,即使该线程被中断过,在调用该线程对象的 isInterrupted() 时依旧会返回 false

  • setPriority(int):设置线程的优先级别。可选范围1-10,默认为5, 优先级高代表抢到时间片的概率高。

  • yield():让当前线程直接放弃时间片返回就绪。

  • join():让当前线程邀请调用方法的那个线程优先执行,在被邀请的线程执行结束之前当前线程一直处于阻塞状态,不再继续执行

1、yield() 和 sleep() 的异同

  • 相同点:yield() 方法和 sleep() 方法类似,也不会释放“锁”。

  • 不同点:yield() 方法只是使当前线程重新回到可执行状态,所以执行 yield() 的线程有可能在进入到可执行状态后马上又被执行。 yield() 方法只能使同优先级或者高优先级的线程得到执行机会,这也和 sleep() 方法不同。

2、join() 和 sleep() 的异同

  • 相同点:都可以实现等待

  • 不同点:由于 join 的内部实现是 wait(),所以使用 join() 方法时会释放锁,那么其他线程就可以调用此线程的同步方法了。 sleep() 方法不释放锁,因此线程会一直等待下去,直到任务完成,才会释放锁。

3、sleep() 与 wait() 的异同

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

  • 不同点:

    • (1)两个方法声明的位置不同:Thread 类中声明 sleep() , Object 类中声明 wait()

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

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

    • (4)当调用某一对象的 wait() 方法后,会使当前线程暂停执行,并将当前线程放入对象等待池中,直到调用了 notify() 方法后,将从对象等待池中移出任意一个线程并放入锁标志等待池中,只有锁标志等待池中的线程可以获取锁标志,它们随时准备争夺锁的拥有权。当调用了某个对象的 notifyAll() 方法,会将对象等待池中的所有线程都移动到该对象的锁标志等待池。

synchronized

在 Java 6 之后, synchronized 引入了大量的优化如自旋锁、适应性自旋锁、锁消除、锁粗化、偏向锁、轻量级锁等技术来减少锁操作的开销,这些优化让 synchronized 锁的效率提升了很多。因此, synchronized 还是可以在实际项目中使用的,像 JDK 源码、很多开源框架都大量使用了 synchronized 。

使用方式

(1)对于普通同步方法,锁是当前实例对象

synchronized void method() {     //业务代码 }

(2) 对于静态同步方法,锁是当前类的 class 对象

synchronized static void method() {     //业务代码 }

(3)对于同步方法块,锁是 Synchronized 括号里配置的对象

synchronized(this) {     //业务代码 }

总结:

  • synchronized 关键字加到 static 静态方法和 synchronized(class) 代码块上都是是给 Class 类上锁;

  • synchronized 关键字加到实例方法上是给对象实例上锁;

  • 尽量不要使用 synchronized(String a) 因为 JVM 中,字符串常量池具有缓存功能。

Q:构造方法可以用 synchronized 修饰么?

A:先说结论:构造方法不能使用 synchronized 关键字修饰。

构造方法本身就属于线程安全的,不存在同步的构造方法一说

  • synchronized 同步语句块的实现使用的是 monitorenter 和 monitorexit 指令,其中 monitorenter 指令指向同步代码块的开始位置,monitorexit 指令则指明同步代码块的结束位置。

  • synchronized 修饰的方法并没有 monitorenter 指令和 monitorexit 指令,取得代之的确实是 ACC_SYNCHRONIZED 标识,该标识指明了该方法是一个同步方法。

不过两者的本质都是对对象监视器 monitor 的获取。

ReentrantLook

同步器

重入锁

获取过锁的线程可以再次调用 lock 来获取锁而不堵塞。假设线程 A 获取锁成功了,释放锁之前,A 线程自己是可以重复获取此锁的(state 会累加)。这就是可重入性的体现:一个线程可以多次获取同一个锁而不会被阻塞。但是,这也意味着,一个线程必须释放与获取的次数相同的锁,才能让 state 的值回到 0,也就是让锁恢复到未锁定状态。只有这样,其他等待的线程才能有机会获取该锁


公平锁

如果一个锁是公平的,那么锁的获取顺序就应该符合请求的绝对时间顺序,也就是FIFO。

static final class FairSync extends Sync {         private static final long serialVersionUID = -3000897897090466540L;         /**          * Acquires only if reentrant or queue is empty.          */         final boolean initialTryLock() {             Thread current = Thread.currentThread();             int c = getState();             if (c == 0) {                 if (!hasQueuedThreads() && compareAndSetState(0, 1)) {                     setExclusiveOwnerThread(current);                     return true;                 }             } else if (getExclusiveOwnerThread() == current) { // 这里实现了可重入锁的逻辑                 if (++c < 0) // overflow                     throw new Error("Maximum lock count exceeded");                 setState(c);                 return true;             }             return false;         }         /**          * Acquires only if thread is first waiter or empty          */         protected final boolean tryAcquire(int acquires) {             if (getState() == 0 && !hasQueuedPredecessors() &&                 compareAndSetState(0, acquires)) {                 setExclusiveOwnerThread(Thread.currentThread());                 return true;             }             return false;         }     }

唯一不同的位置为判断条件多了hasQueuedPredecessors()方法,即加入了同步队列中当前节点是否有前驱节点的判断,如果该方法返回 true,则表示有线程比当前线程更早地请求获取锁,因此需要等待前驱线程获取并释放锁之后才能继续获取锁

hasQueuedPredecessors 是公平锁加锁时判断等待队列中是否存在有效节点的方法。如果返回 False,说明当前线程可以争取共享资源;如果返回 True,说明队列中存在有效节点,当前线程必须加入到等待队列中。


非公平锁(默认)

static final class NonfairSync extends Sync {         private static final long serialVersionUID = 7316153563782823691L;         final boolean initialTryLock() {             Thread current = Thread.currentThread();             if (compareAndSetState(0, 1)) { // first attempt is unguarded                 setExclusiveOwnerThread(current);                 return true;             } else if (getExclusiveOwnerThread() == current) { // 这里实现了可重入锁的逻辑                 int c = getState() + 1;                 if (c < 0) // overflow                     throw new Error("Maximum lock count exceeded");                 setState(c);                 return true;             } else                 return false;         }         /**          * Acquire for non-reentrant cases after initialTryLock prescreen          */         protected final boolean tryAcquire(int acquires) {             if (getState() == 0 && compareAndSetState(0, acquires)) {                 setExclusiveOwnerThread(Thread.currentThread());                 return true;             }             return false;         }     }

为什么会出现线程连续获取锁的情况呢?回顾 nonfairTryAcquire(int acquires)方法,当一个线程请求锁时,只要获取了同步状态即成功获取锁。在这个前提下,刚释放锁的线程再次获取同步状态的几率会非常大,使得其他线程只能在同步队列中等待。

非公平性锁可能使线程“饥饿”,为什么它又被设定成默认的实现呢?再次观察上表的结果,如果把每次不同线程获取到锁定义为 1 次切换,公平性锁在测试中进行了10 次切换,而非公平性锁只有 5 次切换,这说明非公平性锁的开销更小。下面运行测试用例(测试环境:ubuntuserver 14.04 i5-34708GB,测试场景:10 个线程,每个线程获取100000 次锁),通过 vmstat 统计测试运行时系统线程上下文切换的次数,运行结果如表 5-7 所示。


参照:

Java AQS 核心数据结构-CLH 锁 - Qunar 技术沙龙open in new window

ReentrantReadWriteLook

锁降级

锁降级指的是写锁降级成为读锁。如果当前线程拥有写锁,然后将其释放,最后再获取读锁,这种分段完成的过程不能称之为锁降级。锁降级是指把持住(当前拥有的)写锁,再获取到读锁,随后释放(先前拥有的)写锁的过程。

RentrantReadWriteLock 不支持锁升级(把持读锁、获取写锁,最后释放读锁的过程)。目的也是保证数据可见性,如果读锁已被多个线程获取,其中任意线程成功获取了写锁并更新了数据,则其更新对其他获取到读锁的线程是不可见的。

并发容器

ConcurrentHashMap

HashTable 容器使用 synchronized 来保证线程安全,但在线程竞争激烈的情况下HashTable 的效率非常低下。因为当一个线程访问 HashTable 的同步方法,其他线程也访问 HashTable 的同步方法时,会进入阻塞或轮询状态。如线程 1 使用put 进行元素添加,线程 2 不但不能使用 put 方法添加元素,也不能使用 get 方法来获取元素,所以竞争越激烈效率越低。

在 JDK1.7 的时候,ConcurrentHashMap 对整个桶数组进行了分割分段(Segment,分段锁),每一把锁只锁容器其中一部分数据(下面有示意图),多线程访问容器里不同数据段的数据,就不会存在锁竞争,提高并发访问率。

到了 JDK1.8 的时候,ConcurrentHashMap 已经摒弃了 Segment 的概念,而是直接用 Node 数组+链表+红黑树的数据结构来实现,并发控制使用 synchronized 和 CAS 来操作。(JDK1.6 以后 synchronized 锁做了很多优化) 整个看起来就像是优化过且线程安全的 HashMap,虽然在 JDK1.8 中还能看到 Segment 的数据结构,但是已经简化了属性,只是为了兼容旧版本。

由于整个扩容操作是同步的,面对较大容量hashmap扩容是一个相当耗时的过程,会阻塞当前线程的其他操作。因此我们在java中使用较大容量的hashmap时,常常需要预估初始化hashmap的初始容量,指定一个2的N次方初始值进行优化。

参照:

ConcurrentHashMap 源码分析 | JavaGuide

ConcurrentLinkedQueue

ConcurrentLinkedQueue 是一个基于链接节点的无界线程安全队列,它采用先进先出的规则对节点进行排序,当我们添加一个元素的时候,它会添加到队列的尾部;当我们获取一个元素时,它会返回队列头部的元素。它采用了“wait-free”算法(即 CAS 算法)来实现,该算法在 Michael&Scott 算法上进行了一些修改

CountDownLatch

CyclicBarrier

通过设置一个屏障数,然后调用 await()方法来增加屏障数并堵塞等待。

CountDownLatch和 CyclicBarrier区别

  • CountDownLatch的计数器只能使用一次,而CyclicBarrier的计数器可以使用 reset()重置。

  • CyclicBarrier还有其他复杂的方法,比如 getNumberWaiting 可以获得Cyclic-Barrier堵塞的线程数量,isBroken()方法用来了解堵塞的线程是否被中断。

Semaphore

控制并发线程数

使用方式:

(1)设置并发数

(2)调用 acquire()方法获取一个许可证

(3)调用release()方法归还许可证

Exchanger

两个线程互相交换值。执行 exchanger 需要成对出现。如果是奇数个,会有一个线程陷入堵塞。

package com.testclass.concurrency; import java.util.concurrent.Exchanger; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; public class ExchangerTest {     private static final Exchanger<String> exgr = new Exchanger<>();     private static ExecutorService threadPool = Executors.newFixedThreadPool(2);     public static void main(String[] args) {         threadPool.execute(new Runnable() {             @Override             public void run() {                 try {                     String A = "银行流水A";                     String other = exgr.exchange(A);                     System.out.println("银行流水exchange value: " + other);                 } catch (InterruptedException e) {                 }             }         });         threadPool.execute(new Runnable() {             @Override             public void run() {                 try {                     String B = "银行流水B";                     String A = exgr.exchange("B");                     System.out.println("A 和 B 数据是否一致" + A.equals(B) + ", A 录入的是:" + A + ", B录入是:" + B);                 } catch (InterruptedException e){                 }             }         });         threadPool.execute(new Runnable() {             @Override             public void run() {                 try {                     String C = "银行流水C";                     String other = exgr.exchange("C");                     System.out.println("银行流水C exchanger value: " + other);                 } catch (InterruptedException e){                 }             }         });         threadPool.execute(new Runnable() {             @Override             public void run() {                 try {                     String D = "银行流水D";                     String other = exgr.exchange("D");                     System.out.println("银行流水D exchanger value: " + other);                 } catch (InterruptedException e){                 }             }         });         threadPool.shutdown();     } }

线程池

ThreadPoolExecutor

实现原理

关键参数

  • corePoolSIze(线程的基本大小)

  • runnableTaskQueue:任务队列。用于保存等待执行的任务的堵塞队列

(1)ArrryBlockingQueue: 基于数组的有界堵塞队列。按FIFO

(2)LinkBlockingQueue:基于链表的堵塞队列(无界)。

(3)SynchronousQueue: 一个不存储元素的堵塞队列。每个插入都需要等待另一个线程移走。

(4)PriorityBlockingQueue: 一个具有优先级的无限堵塞队列。

  • maximumPoolSize:线程池最大数量。线程池允许创建的最大线程数。值得注意的是,如果使用了无界的任务队列这个参数就没什么效果。

  • ThreadFactory:用于设置创建线程的工厂。可以给线程设置有意义的名字。使用开源框架 guava 提供的 ThreadFactoryBuilder 可以快速给线程池里的线程设置有意义的名字,代码如下。new ThreadFactoryBuilder().setNameFormat("XX-task-%d").build();

  • RejectedExecutionHandler:饱和策略。当队列和线程池都满了,对于新提交的任务的处理方式。默认是AbortPolicy

(1)AbortPolicy:直接抛出异常

(2)CallerRunsPolicy:只用调用者所在线程来运行任务

(3)DiscardOldestPolicy:丢弃队列里最近的一个任务,并执行当前任务

(4)DiscardPolicy:不处理,丢弃掉。

也可以根据应用场景需要来实现 RejectedExecutionHandler 接口自定义策略。

  • keepAliveTime:线程活动保持时间,线程池的工作线程空闲后,保持存活的时间。

  • TimeUnit(线程活动保持时间的单位):可选的单位有天(DAYS)、小时(HOURS)、分钟(MINUTES)、毫秒(MILLISECONDS)、微秒(MICROSECONDS,千分之一毫秒)和纳秒(NANOSECONDS,千分之一微秒)

向线程池提交任务

  • execute:提交不需要返回值的任务。提交的任务是一个 Runnable类的实例

  • submit:提交需要返回值的任务。线程池回返回一个 future 类型的对象。通过 future 的 get() 方法获取返回值。

关闭线程池

可以通过调用线程池的 shutdown 或 shutdownNow 方法来关闭线程池。它们的原理是遍历线程池中的工作线程,然后逐个调用线程的 interrupt 方法来中断线程,所以无法响应中断的任务可能永远无法终止。但是它们存在一定的区别,

  • shutdownNow 首先将线程池的状态设置成 STOP,然后尝试停止所有的正在执行或暂停任务的线程,并返回等待执行任务的列表,

  • shutdown 只是将线程池的状态设置成 SHUTDOWN 状态,然后中断所有没有正在执行任务的线程。

通常调用 shutdown 方法来关闭线程池,如果任务不一定要执行完,则可以调用 shutdownNow 方法。

合理设置线程池

要想合理地配置线程池,就必须首先分析任务特性,可以从以下几个角度来分析。

• 任务的性质:CPU 密集型任务、IO 密集型任务和混合型任务。

• 任务的优先级:高、中和低。• 任务的执行时间:长、中和短。

• 任务的依赖性:是否依赖其他系统资源,如数据库连接。

任务的性质:

性质不同的任务可以用不同规模的线程池分开处理。CPU 密集型任务应配置尽可能小的线程,如配置 Ncpu+1 个线程的线程池。由于 IO 密集型任务线程并不是一直在执行任务,则应配置尽可能多的线程,如 2*Ncpu。混合型的任务,如果可以拆分,将其拆分成一个 CPU 密集型任务和一个 IO 密集型任务,只要这两个任务执行的时间相差不是太大,那么分解后执行的吞吐量将高于串行执行的吞吐量。如果这两个任务执行时间相差太大,则没必要进行分解。可以通过Runtime.getRuntime().availableProcessors()方法获得当前设备的 CPU 个数。

任务的优先级

优先级不同的任务可以使用优先级队列 PriorityBlockingQueue 来处理。它可以让优先级高的任务先执行。

注意 如果一直有优先级高的任务提交到队列里,那么优先级低的任务可能永远不能执行。

任务的依赖性

依赖数据库连接池的任务,因为线程提交 SQL 后需要等待数据库返回结果,等待的时间越长,则 CPU 空闲时间就越长,那么线程数应该设置得越大,这样才能更好地利用 CPU。

建议使用有界队列。

线程池监控

taskCount:线程池需要执行的任务数量。

  • completedTaskCount:线程池在运行过程中已完成的任务数量,小于或等于taskCount。

  • largestPoolSize:线程池里曾经创建过的最大线程数量。通过这个数据可以知道线程池是否曾经满过。如该数值等于线程池的最大大小,则表示线程池曾经满过。

  • •getPoolSize:线程池的线程数量。如果线程池不销毁的话,线程池里的线程不会自动销毁,所以这个大小只增不减。

  • getActiveCount:获取活动的线程数。

通过扩展线程池进行监控。可以通过继承线程池来自定义线程池,重写线程池的beforeExecute、afterExecute 和 terminated 方法,也可以在任务执行前、执行后和线程池

Executor框架

Executor框架的结构和成员

Executor框架的结构

  • 任务:包括被执行任务需要实现的接口:Runnable 接口或 Callable 接口

  • 任务的执行:包括任务执行机制的核心接口 Executor, 以及继承自 Executor 的 ExecutorService 接口。主要实现类 ThreadPoolExecutor 和  scheduledThreadPoolExecutor

  • 异步计算的结果:包括接口 Future 和实现 Future 接口的 FutureTask 类

Executor 框架的成员

本节将介绍 Executor 框架的主要成员:ThreadPoolExecutor、ScheduledThreadPoolExecutor、Future 接口、Runnable 接口、Callable 接口和Executors。

(1)ThreadPoolExecutor

  • FixedThreadPool

固定线程数,为了满足资源管理的需求,而需要限制当前线程数量的应用场景,它适用于负载比较重的服务器。

使用无界队列 LinkedBlockingQueue 作为线程池的工作队列(队列的容量为 Integer.MAX_VALUE)。使用无界队列作为工作队列会对线程池带来如下影响。

1)当线程池中的线程数达到 corePoolSize 后,新任务将在无界队列中等待,因此线程池中的线程数不会超过 corePoolSize。

2)由于是无界队列。如果线程的任务执行没那么快,会导致内存OOM

  • SingleThreadExecutor

    • 单个worker线程。适用于需要保证顺序地执行各个任务;并且在任意时间点,不会有多个线程是活动的应用场景。

    • SingleThreadExecutor 的 corePoolSize 和 maximumPoolSize 被设置为 1。其他参数与FixedThreadPool 相同。SingleThreadExecutor 使用无界队列LinkedBlockingQueue 作为线程池的工作队列(队列的容量为Integer.MAX_VALUE)。SingleThreadExecutor 使用无界队列作为工作队列对线程池带来的影响与 FixedThreadPool 相同

  • CachedThreadPool

大小无界的线程池。适用于执行很多的短期异步任务的小程序。

public static ExecutorService newCachedThreadPool() {   return new ThreadPoolExecutor(     0,     Integer.MAX_VALUE,     60L,     TimeUnit.SECONDS,     new SynchronousQueue<Runnable>()   ); }

  1. CachedThreadPool 的 corePoolSize 被设置为 0,即 corePool 为空;maximumPoolSize被设置为Integer.MAX_VALUE,即 maximumPool 是无界的。这里把keepAliveTime 设置为 60L,意味着 CachedThreadPool 中的空闲线程等待新任务的最长时间为 60 秒,空闲线程超过 60 秒后将会被终止。

  2. 如果主线程提交任务的速度高于maximumPool 中线程处理任务的速度时,CachedThreadPool 会不断创建新线程。极端情况下, CachedThreadPool 会因为创建过多线程而耗尽 CPU 和内存资源。

(2)ScheduledThreadPoolExecutor

适用于需要多个后台线程执行周期任务。

DelayQueue 是一个无界队列,所以 ThreadPoolExecutor 的 maximumPoolSize 在Scheduled-ThreadPoolExecutor 中没有什么意义(设置 maximumPoolSize 的大小没有什么效果)

一个任务的执行过程:

  1. 线程 1 从 DelayQueue 中获取已到期的 ScheduledFutureTask(DelayQueue.take())。到期任务是指ScheduledFutureTask 的 time 大于等于当前时间。

  2. 线程 1 执行这个 ScheduledFutureTask。

  3. 线程 1 修改 ScheduledFutureTask 的 time 变量为下次将要被执行的时间。

  4. 线程 1 把这个修改 time 之后的 ScheduledFutureTask 放回 DelayQueue 中(Delay-Queue.add())。

DelayQueue.Add的执行流程:

  • SingleThreadPoolExecutor

适用于需要单个后台线程执行周期任务,同时需要保证顺序地执行各个任务的应用场景。

FutureTask

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/572915.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

后端工程师——C++工程师招聘需求

相比 Java 语言方向&#xff0c;C 入门简单&#xff0c;精通难&#xff0c;找工作竞争压力更小&#xff0c;但 C 依然是近年来招聘的热门岗位之一。本文将从以下三个方面进行详细讲解&#xff0c;帮助你对 C 相关岗位的就业前景、岗位要求、学习路线等有更充分的了解。 C工程师…

48-70V降12V/33V 5A高效同步降压DC-DC——AH1007

AH1007是一款高效率、高压外置MOSFET管降压芯片TEL&#xff1a;186*4884*3702*&#xff0c;芯片典型输入是8V~100V,输出 电压可调&#xff0c;AH1007最大输出电流可支持6A以上&#xff0c;需要注意板子的散热和温升。 AH1007典型开关频率为150KHz。轻载时会自动降低开关频率以…

SSRF—服务器请求伪造 漏洞详解

漏洞简述 SSRF(Server-Side Request Forgery:服务器端请求伪造) 是一种由攻击者构造&#xff0c;由服务端发起请求的一个网络攻击&#xff0c;一般用来在外网探测或攻击内网服务&#xff0c;其影响效果根据服务器用的函数不同&#xff0c;从而造成不同的影响。 SSRF 形成的原因…

H5点击复制功能 兼容安卓、IOS

效果图 HTML代码 <div>链接&#xff1a;<span style"color: #FF8A21" click"CopyUrl" id"copyId"> https://blog.csdn.net/qq_51463650?spm1000.2115.3001.5343</span> </div>复制方法 const CopyUrl () > {let …

水库大坝安全白蚁监测系统解决方案

一、系统背景 白蚁作为河岸生态系统中的重要病害&#xff0c;不仅会导致水库大坝外部环境发生改变&#xff0c;甚至会引发水库大坝破坏&#xff0c;进而导致自身结构失去稳定性&#xff0c;严重影响水库大坝的正常运行。因此&#xff0c;治理水库大坝白蚁是确保水库大坝工程顺利…

chrome 浏览器 f12 如何查看 websocket 消息?

1. 打开目标页面 2. f12--》网络--》WS&#xff0c;然后刷新页面( 如果不刷页面&#xff0c;就会看不到 websocket 请求&#xff0c;因为 websocket 是长连接&#xff0c;页面加载后只发出一次连接请求&#xff0c;不像 http 接口&#xff0c;不用刷新页面&#xff0c;待会儿也…

Linux Docker下载镜像更改默认存储位置/usr/lib/docker

用于解决docker默认存储位置磁盘空间不足&#xff0c;切换存储位置 1、执行下面命令查看 现在docker的存储位置 docker info | grep "Docker Root Dir" 1.2、如果之前已经下载过镜像可以用mv命令把原来的镜像复制到新的地址 mv /var/lib/docker /data/docker 2、…

十堰着力建设城市级供应链安全检测平台,全力打造数字政府安全示范区

4月23日&#xff0c;十堰市互联网信息办公室、十堰政务服务和大数据管理局、十堰数安技术有限公司共同签订了《城市供应链安全检测平台战略合作框架协议》&#xff0c;通过政企深度协作&#xff0c;加强十堰市数字安全体系建设&#xff0c;为推动十堰市乃至湖北省数字经济产业发…

RS1G08XF5规格详情

RS1G08XF5 是一款由润石&#xff08;RUNIC&#xff09;公司生产的电子元器件。根据所提供的信息&#xff0c;这是一款SOT-23-5封装的器件&#xff0c;其主要参数包括最小电源电压为5V&#xff0c;最大电源电压为6V&#xff0c;最小工作温度为-40C。 为了更准确地了解这款器件的…

【开源】I.Mx6uLL的QT项目合集

文章目录 前言仓库11.下载仓库代码2.使用qmake交叉编译3.效果展示 前言 不定期的更新基于I.Mx6uLL的arm架构下的QT项目合集&#xff1a; 项目移植前的准备 【环境安装】 仓库1 1.下载仓库代码 https://github.com/cocowts/QtCustomcomponent git clone https://github.com/…

ssm框架的网上招聘系统的设计与实现,ssm框架,java编程,mysql数据库 myeclipse开发平台

网上招聘是一个功能很复杂的系统&#xff0c;各个部门之间要有一定的协调能力。 要建立一个高效的管理系统的关键问题就是系统内部的各个模块的相互作用&#xff0c;简单的编写一个网站 只用html &#xff0c;css &#xff0c;javascript &#xff0c;xml &#xff0c;xsl技术…

XUbuntu18.04 源码编译Qt4.5.3的过程

由于新公司很多旧的软件都是基于这个版本做的嵌入式开发。 所以想要自己搭一套基于Linux的非嵌入式开发环境&#xff0c;方便用来调试和编译代码。 这样就可以完成在linux下开发&#xff0c;然后直接嵌入式打包&#xff0c;涉及到界面的部分就不需要上机调试看问题了。 所以…

java 当中的流

初识io 代码演示 private final static String URL "E:\\";private final static String READURL "H:\\kj\\202402\\LS0205.00P";Testpublic void testOutputStream() {long start System.currentTimeMillis();try (InputStream in new FileInputStrea…

RC电路延时时间常数在线计算器

RC电路延时时间常数在线计算器: https://www.838dz.com/calculator/1888.html 急用时&#xff0c;找不到。

古籍数字化平台中的OCR:这个平台更精准

在浩瀚的历史长河中&#xff0c;古籍作为中华民族的文化瑰宝&#xff0c;承载着无数先人的智慧与心血。然而&#xff0c;由于岁月侵蚀、保存不当等多种原因&#xff0c;许多珍贵的古籍面临损坏、失传的危机。为了守护这些无价之宝&#xff0c;云聪研发团队倾力打造了一款尖端的…

portaudio 怎么调用获取输出流

PortAudio是一个跨平台的音频I/O库&#xff0c;它允许你访问计算机的音频硬件进行录音和播放。要使用PortAudio获取输出流&#xff08;播放流&#xff09;&#xff0c;你需要遵循以下步骤&#xff1a; 官方下载地址&#xff1a;PortAudio - an Open-Source Cross-Platform Audi…

AIGC技术的探索与展望:跨界融合与科技变革

文章目录 前言一、AIGC技术的现状与特点二、AIGC技术在各个领域的应用三、AIGC技术对未来社会的影响四、AIGC技术的可能发展方向 前言 随着科技的飞速发展&#xff0c;人工智能与大数据的结合日益紧密&#xff0c;AIGC&#xff08;人工智能生成内容&#xff09;技术作为这一领域…

Linux——web基础实验

实验前的安装 [rootwebserver ~]# yum -y install httpd [rootwebserver ~]# systemctl enable --now httpd Created symlink /etc/systemd/system/multi-user.target.wants/httpd.service → /usr/lib/systemd/system/httpd.service. [rootwebserver ~]# echo test for apach…

Linux入门攻坚——20、systemd、(sysvinit、upstart重温)

再一次讲到Linux系统启动流程&#xff1a; POST --> Boot Sequence --> Bootloader(grub) --> kernel initramfs(initrd) --> rootfs --> /sbin/init 对于init&#xff0c;即系统内核加载完毕后&#xff08;加载kernel和切换根文件系统&#xff09;运行…

源代码开发企业的防泄密该怎么做

在源代码开发企业中&#xff0c;保护产品的知识产权和源代码安全是至关重要的。尤其是在面对Java等易被反编译的语言时&#xff0c;加密和保护源代码就显得尤为重要。针对这一挑战&#xff0c;SDC沙盒提供了一系列全面的解决方案&#xff0c;为企业源代码的安全保驾护航。 源代…