java 实例来自from http://ifeve.com/
http://ifeve.com/oracle-java-concurrency-tutorial/
#####定义并启动一个线程
任务:创建线程打印hello world
com.oracle.sec2.thread_objects.HelloThread
定义并启动一个Actor也是很简单的
com.oracle.sec2.thread_objects.HelloAkka
如java线程不同,akka执行完成后是不会自动退出的,需要手动杀进程,一般我们也不需要自动退出【TODO 自动退出程序?】
#####使用Sleep方法暂停一个线程
任务:每四秒打印一个信息
com.oracle.sec2.thread_objects.SleepMessages
在Actor中,启动定时任务也是通过 system.scheduler 实现的,如果要结束定时任务,也需要通过程序触发
com.oracle.sec2.thread_objects.SimpleMessagesInAkka
还有更简化的写法,只用一个Actor
com.oracle.sec2.thread_objects.SimpleMessageLoop2
#####中断(Interrupts)
Actor中没有中断的概念,对Actor来说,所有的动作都是离散的,所以只需要给它一个停止的消息,而该Actor进行相应的响应即可
#####Joins
Join()方法可以让一个线程等待另一个线程执行完成
Actor中没有Join,如果一个任务完成,可以通过消息通知到另一个Actor即可
#####一个简单的线程例子
下面这个简单的例子是对SleepMessages的改进,如果主线程等待一段时间后,打印线程仍未结束,就可以通过中断停止打印
这个例子 将会把这一节的一些概念放到一起演示。
MessageLoop线程将会打印一系列的信息。如果中断在它打印完所有信息前发生,它将会打印一个特定的消息并退出。
com.oracle.sec2.thread_objects.SimpleThreads
Akka的例子
com.oracle.sec2.thread_objects.SimpleThreadsInAkka
在Actor模型中,同一个Actor的消息处理都是单线程的,或者可以认为是同步的
即在处理完这个消息之前,是不会处理下一消息的
所以对这个Counter可以这样改写为Actor
case object Increase case object Decrease case object GetValue
class Counter extends Actor { var c = 0
def receive = { case Increase => c += 1 case Decrease => c -= 1 case GetValue => sender ! c } }
确立 happens-before关系,对Actor来说就是消息顺序
即在处理完这个消息之前,是不会处理下一消息的
Actor模型 都不关注,天然无风险
任务:每一个人被鞠躬之后都要鞠躬回来以示礼节。但是这段代码由于使用了相同的锁,导致了死锁情况的出现
com.oracle.sec4.liveness.Deadlock
Actor 模型中没有使用锁,所以不存在死锁
com.oracle.sec4.liveness.DeadlockInAkka
####饥饿和活锁
归功于Actor模型的无锁特性,这两种情况在Akka中也不存在
多线程之间经常需要协同工作,最常见的方式是使用Guarded Blocks,它循环检查一个条件(通常初始值为true),直到条件发生变化才跳出循环继续执行。
使用notify实现一个简单的生产者消费者模型:
资源是字符串,一个资源槽只能容纳一个资源。 一个生产者隔一秒生成一个字符串需要放到资源槽中,如果槽满就等待,直到被唤醒。最后放入Done标识结束 一个消费者隔一秒从资源槽中获取并消费一个字符串,如果槽空就等待,直到被唤醒。最后收到Done标识结束
com.oracle.sec5.guarded_blocks.ProducerConsumerExample
Actor模型相对简单,一个Master通过消息控制生产者的生产和消费者的消费,达到相同效果
com.oracle.sec5.guarded_blocks.ProducerConsumerExampleInAkka
###Immutable Objects 不可变对象
scala对于不可变很重视,case class的实例就是不可变的对象
可以看到scala中定义一个 ImmutableRGB 比 java 中要简洁很多
com.oracle.sec6.immutable.ImmutableRGBInScala
###High Level Concurrency Objects 高级并发对象
####锁对象
Akka不需要锁
####执行器(Executors)、线程池
Akka底层集成了java的Executors和线程池,所以我们可以不关注这些细节,Akka会自动利用系统资源进行多线程的处理
http://doc.akka.io/docs/akka/2.3.4/scala/dispatchers.html
####Fork/Join
任务:模糊一张图片
com.oracle.sec7.executors.ForkBlur
标准实现中也大量运用了Fork/Join来做,包括java8中 java.util.Arrays类的一系列parallelSort()方法等。
其他采用了fork/join框架的方法还包括java.util.streams包中的一些方法,此包是作为Java SE 8发行版中Project Lambda的一部分。
Akka原生支持 Fork/Join 处理模式,可以在配置中选择该模式 http://doc.akka.io/docs/akka/2.3.4/scala/dispatchers.html
akka版 todo
Nothing new for akka
http://ifeve.com/java-concurrency-thread-directory/
###Java虚拟机并发编程
####第五章 讨喜的隔离可变性
虽然JDK的线程API使我们可以非常容易地创建线程,但如何防止线程冲突和逻辑混乱却又成了大问题。STM虽然可以解决部分问题,但是在一些类似Java这样的语言中,我们仍不得不非常小心谨慎地避免非托管可变变量和事务逻辑中产生某些副作用。而令人惊讶的是,当共享可变性消失的时候,所有那些令人纠结的问题也都随之消失了。
事实证明,在相同数据集上起多个线程互相冲突地执行是行不通的。幸运的是我们有更好的办法——基于事件的消息传递。通过这种方法,我们可以将任务当成是应用程序/JVM内部的轻量进程来对待。同时,我们将不可变消息传递给各个任务,从而避免每个任务都去抢占共享数据。一旦这些异步任务执行完毕,它们会将不可变的执行结果返回给另外的协调线程。
#####讨喜的隔离可变性(一)用角色实现隔离可变性
Java将OOP变成了可变性驱动(mutability-driven)的开发模式[1],而函数式编程则着重强调不可变性,而这两种极端的方式其实都是有问题的。如果每样事物都是可变的,那么我们就需要妥善处理可见性和竞争条件。而在一个真实的应用程序中,也并非所有事物都是不可变的。即使是纯函数式语言也提供了代码限制区,在该区域内允许出现带副作用的逻辑以及按顺序执行这些逻辑的方法。但无论我们倾向于哪种编程模型,避免共享可变性都是毋庸置疑的。
共享可变性——并发问题的根源所在——是指多个线程可以同时更改相同的变量。而隔离可变性——一个可以消除大部分并发问题的不错的折衷方案——是指任意时刻有且只有一个线程(或角色)可以访问某个可变变量。
在OOP中,由于对象的状态都被封装在对象中,所以只有实例函数才能够操作对象的状态。然而不同的线程可以同时调用这些函数,从而导致并发问题的发生。在基于角色(actor)的编程模型中,我们只允许一个角色(actor)操作对象的状态。因此,即使整个应用程序是多线程的,那些角色(actor)本身也都是单线程的,所以也就不会有任何可见性和竞争条件相关的问题。虽然角色(actor)都可以发出操作执行请求,但它们却无法接触到其他角色(actor)托管的可变状态。
在这种设计方法中,我们令每个actor都负责解决问题的一个组成部分,而它们所接收的数据则都当作不可变对象来处理。一旦完成了分配给自己的任务,这些角色(actor)就会把处理结果封装到不可变对象中并返回给调用角色或其他特定的后置处理(post-processing)角色。我们可以将这种方式想象为OOP进化版,即我们让可变且活跃的对象分别运行在属于自己的线程里。
*在这种模式下,对对象进行操作的唯一方法是将消息传递给它们而不是直接调用其成员函数。
#####讨喜的隔离可变性(二)角色的特性
每个角色都有一个内建的消息队列,该队列与手机上所使用的短信队列十分相似。假设Sally和Sean同时给Bob的手机发了短信,则运营商将会把这两条短信都保存起来以便Bob在方便的时候取走。类似地,基于角色的并发库允许多个角色并发地发送消息。默认情况下,消息发送者都是非阻塞的;它们会先把消息发送出去,然后再继续处理自己的业务逻辑。类库一般会让特定的角色顺序地拾取并处理消息队列中消息,只有将当前消息处理完或将消息委派给其他角色并发处理之后,这个角色才能够接收下一个消息。
角色的生命周期如图 8‑1所示。一个角色在被创建出来之后既可以被启动也可以被终止。一旦被启动,角色即已准备就绪,随时可以接收/处理消息。当角色处于活动状态时,则不是在处理消息就是在等待新消息到达。而一旦停止之后,角色就不会再接收任何消息了。就角色的整体生命周期而言,其用于等待和处理消息的耗时比取决于它们所处的应用程序的动态特性。
http://ifeve.com/wp-content/uploads/2013/08/actor1.png 图 8‑1 角色对可变状态进行了隔离,不同角色之间通过传递不可变消息来进行通信
如果角色在程序的设计中起到了举足轻重的作用,那么我们就会期望在程序执行过程中创建足够多可供使用的角色。然而线程是有限的资源,所以我们不能把角色与线程一对一地捆绑在一起。为了避免资源不足的问题,支持角色的类库通常都会将角色与线程解耦。角色与线程之间的关系好比公司食堂和公司雇员。例如,Bob在其公司食堂里是没有专门的座位的(如果他想要这种待遇的话恐怕得另找一份工作了),所以每次他去食堂吃饭都是随便找一个没人占的座位就行了。与此相类似地,当收到一个待处理的消息或待运行的任务时,角色就可以分配到一个可用的线程来执行任务。一个好的角色在不执行任务时是不会占住线程不放的,因为只有这样才能够让更多不同状态下的角色处于活动状态,并将有限的可用线程资源充分地利用起来。虽然在任意时刻都可能有多个角色处于活动状态,但在任何情况下一个角色中只有一个线程是活动的。这样既保证了多个角色之间的并发性,同时又消除了单个角色之内的竞争。
#####讨喜的隔离可变性(五)同时使用多个角色 令待统计区间为[1,1000w]、划分的子区间为100个,则Scala版示例程序的性能如下所示:
Number of primes is 664579 Time taken 3.88375
#####讨喜的隔离可变性(六)多角色协作
我们写了一个计算给定目录下所有文件大小的程序。在那个例子中,我们启动了100个线程,每个线程都负责扫描不同的子目录,并在最后异步地将所有计算结果累加在一起。而本节中我们将看到一些通过AtomicLong和队列来实现上述功能的方法,同时我们还会把这些方法归纳起来以备你以后处理共享可变性问题之用。
#####讨喜的隔离可变性(十)使用Transactor
#####讨喜的隔离可变性(十三)角色的特性
基于角色的并发模型降低了隔离可变性编程的难度,但该模型在适用场景上还是存在一些限制。
由于角色是通过消息来进行彼此间通信的,所以在那些没有强制不可变性的语言中,我们就必须人工来保证消息都是不可变的。传递可变消息将导致线程安全问题并最终使整个应用陷入共享可变性的险境当中,所以当手头的辅助工具还没有发展到可以帮助我们自动查验消息的不可变性之前,保证消息不可变性的重担暂时还是得由我们程序员来肩负。
角色都是各自异步运行的,彼此之前可以通过传递消息来进行协作。但某些角色的意外失败有可能导致其他角色饿死——一个或多个角色可能会一直等待某些关键的协作消息,而这些消息可能由于本应发出该消息的角色失败而永远无法抵达。因此,我们需要在编码时更加谨慎,在角色中加入异常情况的处理逻辑,并将错误消息传播给那些等待响应的角色。
当两个或多个角色互相等待对方发来的消息时,则所有角色都将陷入死锁。我们必须在设计层面谨小慎微,以确保角色在协作过程中不会陷入死锁状态。与此同时,我们应该使用超时来保证程序不会由于协作环节中个别角色失败无法响应而导致其他角色无限期的等待。
角色一次只能处理一个消息请求,所以无论是动作消息还是请求某个响应或状态的消息,在角色内部都是顺序处理的。对于那些只读任务而言,这种做法可能会降低程序整体的并发度。所以我们最好还是用粗粒度的消息来设计应用程序。此外,我们还可以通过设计单向的“发送并遗忘”类型的消息代替双向消息来减少角色之间的相互等待。
并非所有的应用程序都适合用基于角色的模型实现。只有在待解决的任务可以被拆分成多个彼此相互独立的子任务并且子任务之间只有少量交互的情况下,使用角色才是合适的。但如果子任务之间需要频繁交互或者子任务们需要通过形成一个裁决集(quorum)的形式来进行协作,则使用基于角色的模型就是不合适的。而对于这类问题,我们可以尝试将基于角色的模型与其他并发模型混搭或干脆考虑重新设计。
小结 角色是单线程的,彼此间通过传递消息来进行交互。通过本章的学习,我们了解到角色有如下特性:
降低了隔离可变性的使用门槛 从根本上消除了同步问题 提供了高效的单向消息,但同时也提供了不怎么高效的发送并等待功能 可扩展性非常强;同时由于角色都是单线程的,所以我们可以很方便地用线程池来对其进行管理 允许我们发送消息,但同时也通过接口的方式支持类型化版本(在Akka中)。 允许我们通过事务来完成多角色之间的协作 虽然角色为我们提供了一种强大的编程模型,但该模型在某些方面仍有一些限制。例如,如果我们在使用角色进行设计的时候没有考虑周到,则程序可能存在角色饿死或死锁的潜在风险。此外,我们还需要保证消息的不可变性,这一点在没有语言层面的支持的环境中尤为重要。
下一章我们将学习如何在各种JVM支持的语言中使用角色。
感觉这些缺点都不是事?
####第六章 软件事务内存导论
1.1 同步与并发水火不容 同步操作本身就存在一些很基本的缺陷。
如果我们没能处理好或干脆就忘了进行同步,则某个线程所做的更改可能无法被其他线程及时感知。此外,为了同时保证可见性并避免竞争条件,我们还需要通过一些很麻烦的手段来进行同步操作。
不幸的是,当我们执行同步操作的时候,同时也强制了其他竞争相同资源的线程只能等待。由于同步的粒度对并发度是有很大影响的,所以将同步控制的工作完全交由程序员来完将成会大大降低程序整体效率并增加错误发生的几率。
同步操作还可能引发很多活跃度方面的问题。由于某个线程可能吃着碗里的看着锅里的,所以很容易造成程序死锁。此外,同步还很容易造成活锁(livelock)问题,即线程可能会在申请某一把锁的时候不断遭遇失败。
当然,我们可以尝试使用细粒度的锁来提高程序并发度。虽然一般来说这个主意还不错,但是其中最大的风险是程序员可能没在合适的层级进行同步动作,因为这太依赖于程序员的素质和责任心了。更糟的是,同步出问题的时候我们还收不到任何提示。此外,因为需要互斥访问的线程加了锁之后还是会阻塞其他线程的访问请求,所以细粒度的锁只是把线程等待的位置换了个地方而已。
1.2 对象模型的缺陷 1.3 将实体与状态分离
#####软件事务内存导论(二)软件事务内存 1.1 软件事务内存 将实体与状态分离的做法有助于STM(软件事务内存)解决与同步相关的两大主要问题:跨越内存栅栏和避免竞争条件。
1.2 STM中的事务 原子性:STM事务是原子的。即我们在一个事务中所做的变更要么对所有其他外部事务可见,要么完全不可见。更具体一些,就是一个事务中所有ref的变更要么都生效,要么都不生效。
一致性:是指事务应该要么执行完成并令外界看到其造成的变化,要么执行失败并使所有相关数据都保持原状。如果有多个事务同时运行,那么从这些事务之外的角度来进行观察,我们可以看到它们所造成的变化始终是一个接着一个发生的,中间不会有任何交叉。例如,在(对同一个账户——译者注)两个独立且并发的存款和取款事务完成之后,账户余额应该是两个操作所产生的累加效果(取钱是对账户加上一个负数——译者注)。
隔离性:本事务无法看到其他事务的局部变更结果,即事务所造成的变更只能在其成功完成后才对外可见。
Clojure的STM采用了与数据库相似的多版本并发控制技术(MVCC),其并发控制也和数据库中的乐观锁(optimistic locking)很像。当我们启动一个事务的时候,STM会记录一下时间戳,并将事务中将会用到所有ref都拷贝一份。由于状态是不可变的,所以对于ref的拷贝是多快好省的。当对某个不可变状态进行“变更”的时候,我们其实并没有改变它的值(value),而是为其创建了一个含有新值的拷贝。该拷贝是本事务的一个内部状态,并且由于我们使用了持久化的数据结构(见3.6节),这一步也是多快好省的。而如果STM识别出我们操作过的ref已经被别的事务改了的话,它就会中止并重做本事务。当事务成功完成时,所有的变更都会被写入内存,而时间戳也将被更新(见图 6‑1)。
1.3 用STM实现并发 用事务来实现并发自然是极好的,但如果两个事务都试图更改同一实体的话会是什么状况呢?放心,我不会让你等很久的,本节我们将研究几个关于这方面问题的例子。
在进入实例研究之前我有句话要提醒你:由于事务可能被重复执行多次,所以在写代码的时候请务必确保事务是幂等的并且没有任何副作用。这意味着在事务中控制台不能有任何输出、不能打日志、不能发邮件、也不能做任何不可逆操作。如果违背了上述任何一点,我们只能后果自负。一般而言,我们最好是把这些有副作用的操作收拢起来,在事务完成之后再统一执行它们。
多事务竞争更改balance 两个线程分别把这两个心愿添加到表单 处理写偏斜异常(write sskew anomaly)
#####软件事务内存导论(三)用Akka/Multiverse STM实现并发
在任何情况下,Akka都会保证对于ref的更改是原子的、隔离的且一致的,并同时提供了不同等级的协调粒度。
#####软件事务内存导论(四)创建事务
http://ifeve.com/stm-5/ 使用了嵌套事务的Java版转账函数是非常简洁的
线程安全及不可变性
- 引用不是线程安全的!
重要的是要记住,即使一个对象是线程安全的不可变对象,指向这个对象的引用也可能不是线程安全的。
-
线程通信
-
Java中的读/写锁
一个完整可重入的读写锁的实例: http://ifeve.com/read-write-locks/
- 线程池
有一个最简单的线程池的例子,akka也要使用线程池,不过akka的使用者无需关心
- 剖析同步器
可以看到设计和实现一个同步器的时候需要考虑的问题是非常多的。actor模型不用关心这些。
基本不需要关注 ###Java内存模型Cookbook ###Java内存模型FAQ ###同步和Java内存模型
##其它译文方面
###并发基础 ####Java并发结构
当一个线程释放锁的时候,另一个线程可能正等待这个锁(也可能是同一个线程,因为这个线程可能需要进入另一个同步方法)。但是关于哪一个线程能够紧接着获得这个锁以及什么时候,这是没有任何保证的。(也就是,没有任何的公平性保证-见3.4.1.5)另外,没有什么办法能够得到一个给定的锁正被哪个线程拥有着。
正如2.2.7讨论的,除了锁控制之外,同步也会对底层的内存系统带来副作用。
####任务取消(Cancellation)
#####中断(Interruption)
有两种情况线程会保持休眠而无法检测中断状态或接收InterruptedException:在同步块中和在IO中阻塞时。线程在等待同步方法或同步块的锁时不会对中断有响应。但是,如§2.5中讨论的,当需要大幅降低在活动取消期间被卡在锁等待中的几率,可以使用lock工具类。使用lock类的代码阻塞仅是为了访问锁对象本身,而不是这些锁所保护的代码。这些阻塞的耗时天生就很短(尽管时间不能严格保证)。
#####IO和资源撤销(IO and resource revocation)
一些IO支持类(尤其是java.net.Socket及其相关类)提供了在读操作阻塞的时候能够超时的可选途径,在这种情况下就可以在超时后检测中断。java.io中的其它类采用了另一种方式——一种特殊形式的资源撤销。
CancellableReader 可以通过关闭IO对象和中断线程来完成活动取消。 使用interrupt中断io线程
ReaderWithTimeout 一个不好的实践
#####多步取消(Multiphase cancellation)
有时候,即使取消的是普通的代码,损害也比通常的更大。为应付这种可能性,可以建立一个通用的多步取消功能,尽可能尝试以破坏性最小的方式来取消任务,如果稍候还没有终止,再使用一种破坏性较大的方式。
Akka的关闭?TODO
####Java NIO与IO ####JVM运行时数据区 ####happens-before
###The java.util.concurrent Synchronizer Framework 中文翻译版
###Java Fork Join 框架
可以看一下性能的部分
###Doug Lea并发编程文章全部译文
前面有几篇dl的都集中在一起了
###Mechanical Sympathy 译文
blog
cpu缓存
当你在设计一个重要算法时要记住,缓存不命中所导致的延迟,可能会使你失去执行500条指令时间!这还仅是在单插槽(single-socket)系统上,如果是多插槽(multi-socket)系统,由于内存访问需要跨槽交互,可能会导致双倍的性能损失。
####伪共享(False Sharing)
cache line 缓存填充
###聊聊并发系列
####聊聊并发(一)深入分析Volatile的实现原理 ####聊聊并发(二)Java SE1.6中的Synchronized ####聊聊并发(三)Java线程池的分析和使用 跟前面的线程池的内容差不多 ####聊聊并发(四)深入分析ConcurrentHashMap 线程不安全的HashMap 有一个详细分析,TODO ####聊聊并发(五)原子操作的实现原理 ####聊聊并发(六)ConcurrentLinkedQueue的实现原理 ####聊聊并发(七)Java中的阻塞队列 ####聊聊并发(八)Fork/Join框架介绍 reserved for compare ####聊聊并发(九)Java中的CopyOnWrite容器 ####聊聊并发(十)生产者消费者模式 两个例子,待参考
###深入理解java内存模型系列文章
####深入理解java内存模型(一)——基础
#####并发编程模型的分类 在并发编程中,我们需要处理两个关键问题:线程之间如何通信及线程之间如何同步(这里的线程是指并发执行的活动实体)。通信是指线程之间以何种机制来交换信息。在命令式编程中,线程之间的通信机制有两种:共享内存和消息传递。 在共享内存的并发模型里,线程之间共享程序的公共状态,线程之间通过写-读内存中的公共状态来隐式进行通信。在消息传递的并发模型里,线程之间没有公共状态,线程之间必须通过明确的发送消息来显式进行通信。 同步是指程序用于控制不同线程之间操作发生相对顺序的机制。在共享内存并发模型里,同步是显式进行的。程序员必须显式指定某个方法或某段代码需要在线程之间互斥执行。在消息传递的并发模型里,由于消息的发送必须在消息的接收之前,因此同步是隐式进行的。 Java的并发采用的是共享内存模型,Java线程之间的通信总是隐式进行,整个通信过程对程序员完全透明。如果编写多线程程序的Java程序员不理解隐式进行的线程之间通信的工作机制,很可能会遇到各种奇怪的内存可见性问题。
您好,原文简单概括如下: 并发编程要处理两个关键问题:通信和同步。 共享内存模型:显式同步,隐式通信。 消息传递模型:显式通信,隐式同步。 由于java的并发采用共享内存模型,所以java线程之间的通信是隐式进行的。
erlang这种并发语言就是采用消息传递的方式来通信的,其内部最一切都是线程就像java的一切都是对象一样,它的变量一经定义赋值就不可改变,不存在锁这种东西,为并发而存在
如果对java内存模型感兴趣的话,可以从下面三个文献入手: JSR-133: Java Memory Model and Thread Specification The JSR-133 Cookbook for Compiler Writers JSR 133 (Java Memory Model) FAQ
Java的并发采用的是共享内存模型,共享内存模型的线程间通信是隐式进行的。 如果对于这个主题感兴趣的话,可以参阅Michael L. Scott的《Programming Language Pragmatics, Third Edition 》的第12章。
在涉及JSR-133的几个规范中,都不存在本地内存的概念: 《JSR-133: Java Memory Model and Thread Specification》, 《The Java™ Language Specification Third Edition》 《The Java™ Language Specification Java SE 7 Edition》, 《The Java™ Virtual Machine Specification Java SE 7 Edition》
本地内存以及这段话的描述,是从Brian Goetz写的一篇关于JSR-133内存模型的非常著名的文章中借鉴而来: 《Java theory and practice: Fixing the Java Memory Model, Part 2》 URL:www.ibm.com/developerworks/library/j-jtp03304/i...
Brian Goetz通过使用本地内存这个概念来抽象CPU,内存系统和编译器的优化。
####深入理解java内存模型(二)——重排序 ####深入理解java内存模型(三)——顺序一致性 ####深入理解java内存模型(四)——volatile ####深入理解java内存模型(五)——锁 ReentrantLock 代码分析 ####深入理解java内存模型(六)——final ####深入理解java内存模型(七)——总结
###Java视角理解系统结构 ####从Java视角理解系统结构(一)上下文切换 ####从Java视角理解系统结构(二)CPU缓存 L1CacheMiss ####从Java视角理解系统结构(三)伪共享 class FalseSharing
###Java 7 并发编程指南中文版
http://ifeve.com/java-7-concurrency-cookbook/
线程管理(八)在线程里处理不受控制的异常 http://ifeve.com/thread-management-9/