资讯专栏INFORMATION COLUMN

多线程小记

suxier / 1950人阅读

摘要:死亡状态有两个原因会导致线程死亡方法正常退出而自然死亡。一个未捕获的异常终止了方法而使线程猝死。注意,放入的线程不必担心其结束,超过不活动,其会自动被终止。线程间相互干扰描述了当多个线程访问共享数据时可能出现的错误。

线程 进程与线程的区别

线程是指进程内的一个执行单元,也是进程内的可调度实体。
一个程序至少有一个进程,一个进程至少有一个线程。

线程的五大状态

新建状态(New):例如new Thread(r)。

就绪状态(Runnable): 当start()方法返回后,线程就处于就绪状态。

运行状态(Running) :当线程获得CPU时间后,它才进入运行状态,真正开始执行run()方法。

阻塞状态:(Blocked)
线程运行过程中,可能由于各种原因进入阻塞状态:

线程通过调用sleep方法进入睡眠状态。

线程调用一个在I/O上被阻塞的操作,即该操作在输入输出操作完成之前不会返回到它的调用者。

线程试图得到一个锁,而该锁正被其他线程持有。

线程在等待某个触发条件。

死亡状态(Dead)
有两个原因会导致线程死亡:

run方法正常退出而自然死亡。

一个未捕获的异常终止了run方法而使线程猝死。

为了确定线程在当前是否存活着(就是要么是可运行的,要么是被阻塞了),需要使用isAlive方法。如果是可运行或被阻塞,这个方法返回true; 如果线程仍旧是new状态且不是可运行的, 或者线程死亡了,则返回false。

如何创建一个线程

从Java.lang.Thread类派生一个新的线程类,重写它的run()方法

实现Runnalbe接口,重写Runnalbe接口中的run()方法

实现Callable 接口,重写Callable接口中的call()方法

Runnable和Callable的区别

Callable规定的方法是call(),Runnable规定的方法是run()。

Callable的任务执行后可返回值,而Runnable的任务是不能返回值的。

call方法可以抛出异常,run方法不可以。

运行Callable任务可以拿到一个Future对象,Future 表示异步计算的结果。它提供了检查计算是否完成的方法,以等待计算的完成,并获取计算的结果。

如何安全退出一个已启动的线程

使用退出标志:
当run方法执行完后,线程就会退出。但有时run方法是永远不会结束的,如在服务端程序中使用线程进行监听客户端请求,或是其他的需要循环处理的任务。在这种情况下,一般是将这些任务放在一个循环中,如while循环。如果想使while循环在某一特定条件下退出,最直接的方法就是设一个boolean类型的标志,并通过设置这个标志为true或false来控制while循环是否退出。

Sleep和Wait区别

对于sleep()方法,我们首先要知道该方法是属于Thread类中的。而wait()方法,则是属于Object类中的。

sleep()方法导致了程序暂停执行指定的时间,让出cpu该其他线程,但是他的监控状态依然保持者,当指定的时间到了又会自动恢复运行状态。

在调用sleep()方法的过程中,线程不会释放对象锁。而当调用wait()方法的时候,线程会放弃对象锁,进入等待此对象的等待锁定池,只有针对此对象调用notify()方法后本线程才进入对象锁定池准备。

线程池

由于线程的生命周期中包括创建、就绪、运行、阻塞、销毁阶段,当我们待处理的任务数目较小时,我们可以自己创建几个线程来处理相应的任务,但当有大量的任务时,由于创建、销毁线程需要很大的开销,运用线程池这些问题就大大的缓解了。
相关参数:

corePoolSize 核心池的大小

maximumPoolSize 线程池最大线程数

keepAliveTime 表示线程没有任务执行时最多保持多久时间会终止

unit 参数keepAliveTime的时间单位

workQueue 一个阻塞队列,用来存储等待执行的任务

threadFactory 线程工厂,主要用来创建线程

handler 表示当拒绝处理任务时的策略

ThreadPoolExecutor mExecutor = new ThreadPoolExecutor(corePoolSize,// 核心池的大小
        maximumPoolSize, //线程池最大线程数  
        keepAliveTime, // 闲置线程存活时间  
        TimeUnit.MILLISECONDS,// 时间单位  
        new LinkedBlockingDeque(),//一个阻塞队列,用来存储等待执行的任务  
        Executors.defaultThreadFactory(),//线程工厂,主要用来创建线程  
        new AbortPolicy()// 队列已满,而且当前线程数已经超过最大线程数时的异常处理策略  (表示当拒绝处理任务时的策略)
);

当提交的任务数达到coolPoolSize大小后,之后提交的任务会被保存到workQueue中,而不是创建新的线程去执行它们。当workQueue充满后,就会去创建新的线程,但是总的线程数量不会大于maximumPoolSize

当前线程数量大于corePoolSize的时候,如果空闲线程等待的时间超过了keepAliveTime那么这个空闲线程就会被销毁,当然如果当前线程数量没有超过corePoolSize,那么这个keepAliveTime是不起作用的

常用线程池

newCachedThreadPool

缓存型池子,先查看池中有没有以前建立的线程,如果有,就reuse,如果没有,就建立一个新的线程加入池中。

缓存型池子,通常用于执行一些生存周期很短的异步型任务;因此一些面向连接的daemon型server中用得不多。

能reuse的线程,必须是timeout IDLE内的池中线程,缺省timeout是60s,超过这个IDLE时长,线程实例将被终止及移出池。

注意,放入CachedThreadPool的线程不必担心其结束,超过TIMEOUT不活动,其会自动被终止。

newFixedThreadPool

newFixedThreadPool与cacheThreadPool差不多,也是能reuse就用,但不能随时建新的线程。

其独特之处:任意时间点,最多只能有固定数目的活动线程存在,此时如果有新的线程要建立,只能放在另外的队列中等待,直到当前的线程中某个线程终止直接被移出池子。

和cacheThreadPool不同,FixedThreadPool没有IDLE机制(可能也有,但既然文档没提,肯定非常长,类似依赖上层的TCP或UDP IDLE机制之类的),所以FixedThreadPool多数针对一些很稳定很固定的正规并发线程,多用于服务器。

从方法的源代码看,cache池和fixed 池调用的是同一个底层池,只不过参数不同:fixed池线程数固定,并且是0秒IDLE(无IDLE),cache池线程数支持0-Integer.MAX_VALUE(显然完全没考虑主机的资源承受能力),60秒IDLE 。

ScheduledThreadPool

调度型线程池。

这个池子里的线程可以按schedule依次delay执行,或周期执行。

SingleThreadExecutor

单例线程,任意时间池中只能有一个线程。

用的是和cache池和fixed池相同的底层池,但线程数目是1-1,0秒IDLE(无IDLE)。

多线程 多线程问题生成的原因

线程之间的共享变量存储在主内存(main memory)中,每个线程都有一个私有的本地内存(local memory),本地内存中存储了该线程以读/写共享变量的副本

对于一个简单的 i++ 操作,会发生如下的步骤:

read:作用于主内存中,把主内存中一个变量的值传输到 工作内存 中。

load:作用于工作内存,把从read 操作从主内存中得到的值放入到工作内存的副本中。

use:把工作内存中的该副本值传递给执行引擎(也就是操作数栈中)。

assign:作用于工作内存,把执行引擎执行后的新值传递给该工作内存的变量。

store:作用于工作内存,把工作内存中该变量的值传送到 主内存中去。

write:作用于主内存的变量,把store 操作 得到的值写入到 主内存的该变量中。

所以说,一个 i++ 操作并不是原子性的。这上述的这些步骤中,可能会有其他线程对主内存的变量进行操作,从而导致出现多线程问题。

什么是线程安全

线程安全就是说多线程访问同一代码,不会产生不确定的结果。编写线程安全的代码是低依靠线程同步。

线程同步

线程间的通讯首要的方式就是对字段及其字段所引用的对象的共享访问。这种通信方式是及其高效的,但是也是导致了可能的错误:线程间相互干涉和内存一致性的问题。避免出现这两种错误的方法就是同步。
线程间相互干扰描述了当多个线程访问共享数据时可能出现的错误。
内存一致性错误描述的了共享内存可能导致的错误。
同步方法(Synchronized method)描述了一种简单的可以有效防止线程间相互干扰及其内存一致性错误的方法。
明锁及同步描述了一种更加通用的同步方法,以及同步是如何基于明锁而实现的。
原子性描述了不能被其它线程干扰的操作。

Lock和synchronized的选择

  1. Lock是一个接口,而synchronized是Java中的关键字,synchronized是内置的语言实现。
  2. synchronized在发生异常时,会自动释放线程占有的锁,因此不会导致死锁现象发生;而Lock在发生异常时,如果没有主动通过unLock()去释放锁,则很可能造成死锁现象,因此使用Lock时需要在finally块中释放锁。
  3. Lock可以让等待锁的线程响应中断,而synchronized却不行,使用synchronized时,等待的线程会一直等待下去,不能够响应中断。
  4. 通过Lock可以知道有没有成功获取锁,而synchronized却无法办到。
  5. Lock可以提高多个线程进行读操作的效率。

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/70076.html

相关文章

  • 集合小记

    摘要:解決沖突开放定址法拉链法表解決沖突开放定址法再哈希法链地址法建立公共溢出区并发包中的线程安全的集合容器线程安全的,不允许为,默认个的数组,每个中实现就是了,通过定位。基于数组,线程安全的集合类,容量可以限制。 List   List 元素是有序的、可重复,实现List接口的集合主要有:ArrayList、LinkedList、Vector、Stack。   ArrayList:动态数组...

    alaege 评论0 收藏0
  • java基础小记

    摘要:看到的只是,而由泛型附加的类型信息对来说是不可见的。然后再加载执行类的静态变量以及静态语句块。接口中基本数据类型为而抽类象不是的。本地方法接口主要是调用或实现的本地方法及返回结果。用户自定义类加载器,在程序运行期间,通过的子类动态加载。 编译机制  编译主要是把 .Java文件转换为 .class 文件。其中转换后的 .class 文件就包含了元数据,方法信息等一些信息。比如说元数据就...

    ruicbAndroid 评论0 收藏0
  • 【开发小记】 Java 线程池 之 被“吃掉”的线程异常(附源码分析和解决方法)

    摘要:接下来就是会把任务提交到队列中给线程池调度处理因为主要关心的是这个线程怎么执行,异常的抛出和处理,所以我们暂时不解析多余的逻辑。 前言 今天遇到了一个bug,现象是,一个任务放入线程池中,似乎没有被执行,日志也没有打。 经过本地代码调试之后,发现在任务逻辑的前半段,抛出了NPE,但是代码外层没有try-catch,导致这个异常被吃掉。 这个问题解决起来是很简单的,外层加个try-cat...

    Soarkey 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<