阻塞队列
队列写入元素的时候:如果队列满了,就必须阻塞等待
队列获取元素的时候:如果队列是空的,必须阻塞等待生产
由以上类结构图可以看出,BlockingQueue不是新的东西
使用阻塞队列的场景:多线程并发处理,线程池!
-
SynchronousQueue:是一个特殊的阻塞队列,它并不保存任何元素,每次插入操作必须等待另一个线程的移除操作,每次移除操作必须等待另一个线程的插入操作,因此它可以用于两个线程之间进行数据交换。
-
LinkedBlockingQueue:是一个无界(元素无上限,Integer.Max_Value)的阻塞队列,底层是由链表实现的,可以存储任意数量的元素。
-
ArrayBlockingQueue:是一个有界的阻塞队列,底层是由数组实现的,
-
DelayQueue:队列内元素必须实现Delayed接口,这就意味着你传进去的任务必须先实现Delayed接口。这个队列接收到任务时,首先先入队,只有达到了指定的延时时间,才会执行任务
-
PriorityBlockingQueue:是一个支持优先级的阻塞队列,底层是由堆实现的,可以根据元素的优先级顺序进行排序。当添加元素时,会根据元素的优先级自动排序,获取元素时会返回当前队列中优先级最高的元素。当队列为空时,获取元素的操作将会阻塞,直到队列中有元素可用。
ArrayBlockingQueue 阻塞队列
package com.demo.juc.bq;
import java.util.Collection;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.TimeUnit;
public class TestBq {
public static void main(String[] args) throws InterruptedException {
// List Set Collection
// BlockingQueue 不是新的东西
// test1();
// test2();
// test3();
test4();
}
/**
* 队列满了还添加/队列空了还取出
*
* 抛出异常
*/
public static void test1(){
// 队列的大小
ArrayBlockingQueue arrayBlockingQueue = new ArrayBlockingQueue<>(3);
// 都是t
System.out.println(arrayBlockingQueue.add("a"));
System.out.println(arrayBlockingQueue.add("b"));
System.out.println(arrayBlockingQueue.add("c"));
// java.lang.IllegalStateException: Queue full 抛出异常!
// System.out.println(arrayBlockingQueue.add("d"));
// 先进先出 abc
System.out.println(arrayBlockingQueue.remove());
System.out.println(arrayBlockingQueue.remove());
System.out.println(arrayBlockingQueue.remove());
// java.util.NoSuchElementException
// System.out.println(arrayBlockingQueue.remove());
}
/**
* 队列满了还添加,会返回false
*
* 队列空了还取出,会返回null
*
* 两种都不抛出异常,有返回值
*/
public static void test2(){
ArrayBlockingQueue<Object> arrayBlockingQueue = new ArrayBlockingQueue<>(3);
// t t t f
System.out.println(arrayBlockingQueue.offer("a"));
System.out.println(arrayBlockingQueue.offer("b"));
System.out.println(arrayBlockingQueue.offer("c"));
System.out.println(arrayBlockingQueue.offer("d"));
// 查看队首元素是谁,a
// 两个方法都是同样的作用
System.out.println(arrayBlockingQueue.element());
System.out.println(arrayBlockingQueue.peek());
// a b c null
System.out.println(arrayBlockingQueue.poll());
System.out.println(arrayBlockingQueue.poll());
System.out.println(arrayBlockingQueue.poll());
System.out.println(arrayBlockingQueue.poll());
}
/**
* 等待,阻塞(一直)
*/
public static void test3() throws InterruptedException {
ArrayBlockingQueue<Object> blockingQueue = new ArrayBlockingQueue<>(3);
blockingQueue.put("a");
blockingQueue.put("b");
blockingQueue.put("c");
// 队列没有位置了,就会一直阻塞
// blockingQueue.put("d");
System.out.println(blockingQueue.take());
System.out.println(blockingQueue.take());
System.out.println(blockingQueue.take());
// 没有这个元素,一直阻塞
System.out.println(blockingQueue.take());
}
/**
* 等待,阻塞(等待超时)
*/
public static void test4() throws InterruptedException {
ArrayBlockingQueue<Object> blockingQueue = new ArrayBlockingQueue<>(3);
blockingQueue.offer("a");
blockingQueue.offer("b");
blockingQueue.offer("c");
// 等待2s,如果还没有位置就超时退出
// blockingQueue.offer("d",2, TimeUnit.SECONDS);
blockingQueue.poll();
blockingQueue.poll();
blockingQueue.poll();
// 等待2s,如果还没有元素可以取出就退出
blockingQueue.poll(2,TimeUnit.SECONDS);
}
}
SynchronousQueue 同步队列
没有容量,进去一个元素,必须等待取出来之后,才能再往里面放一个元素。
相当于该队列容量只有1。
package com.demo.juc.bq;
import java.util.concurrent.SynchronousQueue;
import java.util.concurrent.TimeUnit;
/**
* 同步队列
*
* 和其他的BlockingQueue不一样,SynchronousQueue不存储元素
* put了一个元素,必须先从里面take出来,否则不能再put进去值
*/
public class SynchronousQueueDemo {
public static void main(String[] args) {
SynchronousQueue<String> synchronousQueue = new SynchronousQueue<>(); // 同步队列
new Thread(()->{
try {
System.out.println(Thread.currentThread().getName() + "put 1");
synchronousQueue.put("1");
System.out.println(Thread.currentThread().getName() + "put 2");
synchronousQueue.put("2");
System.out.println(Thread.currentThread().getName() + "put 3");
synchronousQueue.put("3");
} catch (InterruptedException e) {
e.printStackTrace();
}
},"t1").start();
new Thread(()->{
try {
TimeUnit.SECONDS.sleep(2);
System.out.println(Thread.currentThread().getName()+synchronousQueue.take());
TimeUnit.SECONDS.sleep(2);
System.out.println(Thread.currentThread().getName()+synchronousQueue.take());
TimeUnit.SECONDS.sleep(2);
System.out.println(Thread.currentThread().getName()+synchronousQueue.take());
} catch (InterruptedException e) {
e.printStackTrace();
}
},"t2").start();
/**
* t1put 1
* t21
* t1put 2
* t22
* t1put 3
* t23
*
*
* 如果是阻塞队列就是全放进去,然后再全取出来
*/
}
}
线程池
线程池就是创建若干个可执行的线程放入一个池(容器)中,有任务需要处理时,会提交到线程池中的任务队列,处理完之后线程并不会被销毁,而是仍然在线程池中等待下一个任务。
线程池常问问题:三大方法、7大参数、4种拒绝策略
程序的运行,本质:占用系统的资源!优化资源的使用!
线程池、连接池、内存池、对象池。。。。。。创建和销毁十分浪费资源。
池化技术:开启和关闭线程是非常消耗资源的,我们事先准备好一些资源,有人要用,就来拿,用完之后再还回去。
线程池的好处:
-
降低资源的消耗
通过重复利用已经创建好的线程降低线程的创建和销毁带来的损耗。 -
提高响应的速度
重复利用线程池中线程,不需要每次都创建(使用线程池就能很好地避免频繁创建和销毁。) -
方便管理
线程对服务器来说是稀缺资源,如果无限制的去创建,肯定会导致系统的访问速度下降。可以根据系统的承受能力,调整线程池中工作线线程的数目,防止因为消耗过多的内存,而把服务器累趴下(每个线程需要大约1MB内存,线程开的越多,消耗的内存也就越大,最后死机)。
总结
:线程复用、可以控制最大并发、管理线程。
这就类似于公司只要有一个新任务就招一个员工
,最终会将公司的资源耗尽,这和我们的业务一样,最终分配给我们的堆空间或者栈空间,内存都是有限的,
如果在我们高并发系统里,比如这个业务非常大,要进行100W的异步任务查询,现在有100W个请求进来,假设一个请求就要开启10个异步任务,直接就会new 1000W个Thread,肯定会导致我们资源耗尽而最终的系统崩溃
我们以后在业务代码中,继承Thread类,实现Runnable接口,实现Callable接口,以上三种启动线程的方式都不用
,将所有的多线程异步任务都交给线程池执行
这就好比我们公司中50个员工,有任务的话就交给其中一个,如果都有活儿,就等处理完了再处理这个任务,这样做的好处就是我们达到了资源控制
我们只有这50个人,消耗的资源也就是这50个人的资源
线程池api
Java里面线程池的顶级接口是java.util.concurrent.Executor
,但是严格意义上讲 Executor 并不是一个线程池,而只是一个执行线程的工具。
真正的线程池接口是 java.util.concurrent.ExecutorService
。 要配置一个线程池是比较复杂的,尤其是对于线程池的原理不是很清楚的情况下,很有可能配置的线程池不是较优的,因此在 java.util.concurrent.Executors 线程工厂类里面提供了一些静态工厂
,生成一些常用的线程池。官方建议使用Executors(但阿里不推荐,听阿里的)
工程类来创建线程池对象。 Executors
类中有个创建线程池的方法如下:
// ExecurtorService:真正的线程池接口。常见子类ThreadPoolExecutors
// 执行线程任务,没返回值
void execute(Runnable command);
// 执行线程任务,有返回值
<T> Future<T> submit(Callable<T> task);
// 关闭连接池
void shutdown();
Executors:工具类、线程池的工厂类,用于创建并返回不同类型的线程池
三大方法
package com.demo.juc.pool;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
// 用Executors或new ThreadPoolExecutor()创建线程池实际上都是实现ExecutorService
// newFixedThreadPool 一个固定数量的线程池
// 这个线程池不应该是每一个异步任务都创建一个线程池
// 而是应该整个系统一个线程池,大家都把自己的任务交给这个池里执行
// 当前系统中池只有一两个,可能有核心业务的或者非核心业务的
// 每个异步任务,直接提交给线程池,让它自己去执行
// Executors 工具类、3大方法
// 使用了线程池之后,使用线程池来创建线程
public class Demo01 {
public static void main(String[] args) {
// ExecutorService threadPool = Executors.newSingleThreadExecutor();// 单个线程
// ExecutorService threadPool = Executors.newFixedThreadPool(5); // 创建一个固定的线程池大小
ExecutorService threadPool = Executors.newCachedThreadPool(); // 可伸缩的线程池
try {
for (int i = 0; i < 10; i++) {
// submit可以传入Runnable接口和Callable接口,可以获取到任务的返回值
// execute 可以只能传入Runnable接口,不能获取到任务的返回值
threadPool.execute(()->{
System.out.println(Thread.currentThread().getName()+"ok");
});
}
} catch (Exception e) {
e.printStackTrace();
} finally {
// 线程池用完,程序结束,关闭线程池
threadPool.shutdown();
}
/**
*
* 以上三个不同的线程池操作线程的结果分别是
*
* pool-1-thread-1ok
* pool-1-thread-1ok
* pool-1-thread-1ok
* pool-1-thread-1ok
* pool-1-thread-1ok
* pool-1-thread-1ok
* pool-1-thread-1ok
* pool-1-thread-1ok
* pool-1-thread-1ok
* pool-1-thread-1ok
*
* Process finished with exit code 0
* 同一个线程
*
*
*
*
*
*
*pool-1-thread-1ok
* pool-1-thread-3ok
* pool-1-thread-2ok
* pool-1-thread-5ok
* pool-1-thread-4ok
* pool-1-thread-3ok
* pool-1-thread-1ok
* pool-1-thread-4ok
* pool-1-thread-5ok
* pool-1-thread-2ok
*
* Process finished with exit code 0
*
*
*
*
*
*
* newCachedThreadPool()
*
* 如果同时执行10个任务,它创建的就有10个线程
*
* 测试如果是100个任务,它创建的是三十多个线程(这个数量没有逻辑)
*
*
*/
}
}
源码分析
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
// 发现三大方法的本质是ThreadPoolExecutor()
public ThreadPoolExecutor(int corePoolSize, // 核心线程池大小【一直存在,除非设置allowCoreThreadTimeOut】
int maximumPoolSize, // 最大核心线程池大小【最大线程数量】
long keepAliveTime, // 存活时间【超时了,没有人调用就会被释放,释放空闲的线程(非核心线程)】
TimeUnit unit, // 超时时间单位
BlockingQueue<Runnable> workQueue, // 阻塞队列
// 如果任务有很多,就会将目前多的任务放在队列里面。
// 只要有线程空闲,就会去队列里面取出新的任务继续执行。
ThreadFactory threadFactory, // 线程工厂:创建线程的,一般不用动
// 工厂都是默认的,也可以自定义,
// 比如每次来创建线程的时候,线程的名字有自己的约束,我们就可以写自己的创建工厂
RejectedExecutionHandler handler // 拒绝策略
// 如果队列满了,按照我们指定的拒绝策略拒绝执行任务 ) {
if (corePoolSize < 0 ||
maximumPoolSize <= 0 ||
maximumPoolSize < corePoolSize ||
keepAliveTime < 0)
throw new IllegalArgumentException();
if (workQueue == null || threadFactory == null || handler == null)
throw new NullPointerException();
this.acc = System.getSecurityManager() == null ?
null :
AccessController.getContext();
this.corePoolSize = corePoolSize;
this.maximumPoolSize = maximumPoolSize;
this.workQueue = workQueue;
this.keepAliveTime = unit.toNanos(keepAliveTime);
this.threadFactory = threadFactory;
this.handler = handler;
}
/** 工作顺序:
*
* 1. 线程池创建,准备好 core 数量的核心线程,准备接受任务
* 2. 新的任务进来,用 core 准备好的空闲线程执行。
* 3. core 满了,就将再进来的任务放入阻塞队列中。空闲的 core 就会自己去阻塞队列获取任务执行
* 4. 阻塞队列满了,就直接开新线程执行,最大只能开到 max 指定的数量
* max 都执行好了。Max-core 数量空闲的线程会在 keepAliveTime 指定的时间后自动销毁。
* 最终保持到 core 大小
*
* new LinkedBlockingQueue<>();默认是Integer的最大值。内存不够
* 5. 如果线程数开到了 max 的数量,还有新任务进来,就会使用 reject 指定的拒绝策略进行处理
* 6. 所有的线程创建都是由指定的 factory 创建的
*
*
* 面试题
*
* 一个线程池,core 7,max 20,queue 50,100并发进来怎么分配的?
*
* 7个会立即得到执行,50个会进入队列,再开13个进行执行。剩下的30个就使用拒绝策略。
*/
七大参数和四种拒绝策略
package com.atlinxi.gulimall.springdemo.juc.pool;
import java.util.concurrent.Executors;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
/**
拒绝策略,我觉得目的可能是避免发生拒绝的,如果出现了拒绝,那就是服务器承载不了那么多的线程并发,需要减少并发或者提升服务器的性能了,
所以它主要目的应该是临时处理和记录,以便后续的处理,一般来说肯定是不想抛弃该任务而做的补偿措施。
线程池工作中,如果任务量很大,超过系统实际承载能力时,如果不予理睬,接着可能系统就崩溃了,
所以jdk内置提供了线程池的4种拒绝策略,合理的解决这种问题。
*/
/**
* ThreadPoolExecutor.CallerRunsPolicy() 哪来的去哪里
* 不启动线程池线程,直接调用run方法,这样的话实际上就是同步调用了。
* 它会让调用线程池的线程自己来运行任务。
* 当任务被拒绝时,由提交任务的线程来执行该任务。
* 第一,新提交的任务不会被抛弃,不会造成业务损失。
第二,由于谁提交任务谁负责任务,提交任务的路线必须负责任务,
执行任务需要时间,在此期间,提交任务的路线被占有,不提交新任务,任务提交速度变慢,相当于负面反馈。
在此期间,线程池的线程也可以充分利用这个时间执行一部分任务,腾出一定的空间,相当于给线程池一定的缓冲期。
*
* ThreadPoolExecutor.AbortPolicy() 超过最大线程直接报错;
* 丢弃任务并抛出RejectedExecutionException异常,默认策略
*
* ThreadPoolExecutor.DiscardPolicy() 队列满了,丢掉任务,不会抛出异常
*
* ThreadPoolExecutor.DiscardOldestPolicy() 当任务被拒绝时,丢弃队列中最老(最早)的一个任务,并尝试再次提交被拒绝的任务。
*
* 同时jdk也为我们提供了RejectedExecutionHandler接口,可以根据实际应用场景去自定义策略
实际开发中我们会使用自定义拒绝策略,因为在自定义拒绝策略灵活好控制,
可以在自定义拒绝策略中发送一条通知给消息中心,让消息中心发送告警信息给开发者,
这样可以实时的监控线程池的运行状况,并能及时发现和排查问题。
*/
public class PoolTest {
public static void main(String[] args) {
// 自定义线程池,工作中创建线程池只会用这种方式
// 最大线程到底该如何定义
// 1. cpu密集型 如果服务器是12核的,就能支持12条线程同时执行,
// 几核就定义为几,可以保证cpu效率最高
// 获取cpu的核数
// 这就是cpu密集型的逻辑
sout(Runtime.getRuntime().availableProcessors());
// 2. io密集型。判断程序中十分耗io的线程,大于这个数即可,通常会设置两倍,
// 除了这15个,还会剩下15个执行别的线程,就不会造成系统的阻塞
// 程序中有15个大型任务,io十分占用资源
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
2,
5,
3,
TimeUnit.SECONDS,
new LinkedBlockingQueue<>(3),
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.DiscardOldestPolicy());// 最大线程数和队列都满了,但是还有人进来,不处理这个线程,并抛出异常
try {
// 最大承载:队列 + 最大线程数
for (int i = 0; i < 9; i++) {
threadPoolExecutor.execute(()->{
System.out.println(Thread.currentThread().getName()+"ok");
});
}
} catch (Exception e) {
e.printStackTrace();
} finally {
threadPoolExecutor.shutdown();
}
/**
* 线程池的线程使用顺序
*
* 核心线程数 -》 队列 -》 最大线程数 -》 拒绝策略
*
* 6个任务以内2个线程执行
* 6个任务3个线程执行
* 7个任务4个线程执行
* 8个任务5个线程执行
* 9个任务由于超过了最大线程数,我们的拒绝策略又是AbortPolicy(),所以报java.util.concurrent.RejectedExecutionException
*
*/
}
}
自定义拒绝策略
dubbo
直接继承的 AbortPolicy
- 输出了一条警告级别的日志,日志内容为线程池的详细设置参数,以及线程池当前的状态,还有当前拒绝任务的一些详细信息。可以说,这条日志,使用dubbo的有过生产运维经验的或多或少是见过的,这个日志简直就是日志打印的典范,其他的日志打印的典范还有spring。得益于这么详细的日志,可以很容易定位到问题所在
- 输出当前线程堆栈详情,这个太有用了,当你通过上面的日志信息还不能定位问题时,案发现场的dump线程上下文信息就是你发现问题的救命稻草,这个可以参考
- 继续抛出拒绝执行异常,使本次任务失败,这个继承了JDK默认拒绝策略的特性
// 通过实现RejectedExecutionHandler接口,自定义一个拒绝策略类,重写它的rejectedExecution()方法:
public class CustomRejectionHandler implements RejectedExecutionHandler {
@Override
public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
System.out.println(r.toString() + "被拒绝了,执行入库操作,之后手动补偿");
}
}
如何判断线程池中的任务是否执行完成?
判断线程池任务执行是否完成,有以下几种方法:
// isTerminated()
// 线程池提供了一个原生函数isTerminated()来判断线程池中的任务是否全部完成。
// 当线程池中所有任务都执行完成之后,线程池就会进入终止状态,此时调用isTerminated()方法返回的结果就是true
ThreadPoolExecutor executor = (ThreadPoolExecutor) Executors.newFixedThreadPool(5);
// 提交任务
executor.execute(() -> {
// 任务代码
});
// 关闭线程池
executor.shutdown();
// 判断任务是否完成
if (executor.isTerminated()) {
System.out.println("所有任务已完成");
// getCompletedTaskCount():我们可以通过判断线程池中计划执行任务和已完成任务,
// 来判断线程池是否已经执行完成。如果相等就代表已经执行完成了
ThreadPoolExecutor executor = (ThreadPoolExecutor) Executors.newFixedThreadPool(5);
// 提交任务
executor.execute(() -> {
// 任务代码
});
// 判断任务是否完成
if (executor.getTaskCount() == executor.getCompletedTaskCount()) {
System.out.println("所有任务已完成");
}
// FutureTask():当你提交一个Callable或者RRunnable任务到达线程池的时候,
// 你可以将它包装到一个FutureTask对象中。然后调用FutureTask的get()方法来获取任务的结果,
// 如果任务没有完成,get()方法将会阻塞,直到任务完成并且结果可用。所以,只要get()方法有返回结果,就直到任务是否完成
// 创建一个 Callable 任务
Callable<Integer> task = new Callable<Integer>() {
@Override
public Integer call() throws Exception {
// 执行任务,返回结果
return doSomeWork();
}
};
// 创建一个 FutureTask 对象来包装任务
FutureTask<Integer> futureTask = new FutureTask<>(task);
// 提交任务到线程池
ExecutorService executor = Executors.newFixedThreadPool(1);
executor.execute(futureTask);
// 获取任务结果
try {
Integer result = futureTask.get(); // 这里会阻塞,直到任务完成
System.out.println("任务已完成,结果是:" + result);
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
线程池的执行原理
Java线程池的执行原理主要包括以下几个步骤:
-
线程池创建:通过ThreadPoolExecutor类创建线程池,并设置核心线程数、最大线程数、队列容量、保持存活时间等参数。
-
任务提交:调用execute或submit方法提交任务。
-
任务执行:如果当前运行的线程数小于核心线程数,则创建一个新线程来执行任务;如果大于或等于核心线程数且任务队列未满,则将任务加入到任务队列中等待执行;如果队列也满了,并且当前运行的线程数小于最大线程数,则创建新的非核心线程来执行任务;如果达到最大线程数,则拒绝策略处理无法执行的任务。
-
任务执行结束:执行完毕的线程会回到线程池中等待下一个任务,而非核心线程在保持存活时间内会保持存活。
线程池非核心线程怎么设置暂停
在 Java 中,通常没有直接的方法来暂停线程池中的非核心线程。但是可以通过一些间接的方式来实现类似的效果。
一种方法是通过控制任务的提交来间接影响非核心线程的行为。比如,可以设置一个标志位,当需要暂停非核心线程时,停止向线程池提交新任务,这样非核心线程在完成当前任务后就会进入等待状态,因为没有新任务可执行。
以下是一个简单的示例代码:
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
public class ThreadPoolPauseExample {
private static boolean pauseFlag = false;
private static ExecutorService executorService = Executors.newFixedThreadPool(5);
public static void main(String[] args) throws InterruptedException {
for (int i = 0; i < 10; i++) {
executorService.execute(() -> {
while (true) {
if (pauseFlag) {
try {
// 模拟暂停,等待恢复信号
Thread.sleep(100);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
} else {
System.out.println(Thread.currentThread().getName() + " is working.");
try {
Thread.sleep(500);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
});
}
// 运行一段时间后暂停非核心线程
TimeUnit.SECONDS.sleep(5);
pauseFlag = true;
// 恢复非核心线程
TimeUnit.SECONDS.sleep(5);
pauseFlag = false;
// 关闭线程池
executorService.shutdown();
while (!executorService.isTerminated()) {
}
System.out.println("Shutdown complete.");
}
}
在这个示例中,通过一个标志位pauseFlag
来控制任务的执行。当pauseFlag
为true时,任务中的线程进入等待状态。当需要恢复时,将标志位设置为false。
需要注意的是,这种方法并不是真正意义上的暂停非核心线程,只是通过控制任务的执行来达到类似的效果。并且在实际应用中,需要谨慎使用这种方法,确保不会引起死锁或其他并发问题。
第二,在人才成长过程中,我们要创造一个宽容的环境,让大家愿意畅所欲言,互相启发,把思想炸开。一是,喝咖啡是一种形式,网聊也是“喝咖啡”,比如2012实验室在群里的讨论就很激烈,关于软件突围方向在心声社区的回帖有1500多条,别看这一片骂声,这就是贵人指点、高僧开光、西汉张良在桥头获得的天书……。对新员工是多么好的指引。我为什么不愿意网聊,愿意喝咖啡呢?我认为讲话互动比文字互动效率高、表述更清楚。二是,高级专家要用更多精力来读文献,而不是埋头做事。比如,把一半时间用来读文献,写写感想。三是,我们“黄大年茶思屋”应该号召所有员工把他认为好的文献转帖过来,如果涉及知识产权问题,就把索引贴上来就行,大家再去那个网站上查阅,这也是一个沟通方式。
今天听了汇报,你们工作的基本思路是清晰的。历史滚滚向前就会有新陈代谢,有人选择离开就会有继任上来,但是我们要形成一个良好机制,让优秀人才涌现,英雄倍出。
任正非