- ubuntu12.04环境下使用kvm ioctl接口实现最简单的虚拟机
- Ubuntu 通过无线网络安装Ubuntu Server启动系统后连接无线网络的方法
- 在Ubuntu上搭建网桥的方法
- ubuntu 虚拟机上网方式及相关配置详解
CFSDN坚持开源创造价值,我们致力于搭建一个资源共享平台,让每一个IT人在这里找到属于你的精彩世界.
这篇CFSDN的博客文章JAVA多线程的使用场景与注意事项总结由作者收集整理,如果你对这篇文章有兴趣,记得点赞哟.
前言 。
我曾经对自己的小弟说,如果你实在搞不清楚什么时候用hashmap,什么时候用concurrenthashmap,那么就用后者,你的代码bug会很少.
他问我:concurrenthashmap是什么? -.- 。
编程不是炫技。大多数情况下,怎么把代码写简单,才是能力.
多线程生来就是复杂的,也是容易出错的。一些难以理解的概念,要规避。本文不讲基础知识,因为你手里就有jdk的源码.
线程 。
thread 。
第一类就是thread类。大家都知道有两种实现方式。第一可以继承thread覆盖它的run方法;第二种是实现runnable接口,实现它的run方法;而第三种创建线程的方法,就是通过线程池.
我们的具体代码实现,就放在run方法中.
我们关注两种情况。一个是线程退出条件,一个是异常处理情况.
线程退出 。
有的run方法执行完成后,线程就会退出。但有的run方法是永远不会结束的。结束一个线程肯定不是通过thread.stop()方法,这个方法已经在java1.2版本就废弃了。所以我们大体有两种方式控制线程.
定义退出标志放在while中 。
代码一般长这样.
1
2
3
4
5
|
private
volatile
boolean
flag=
true
;
public
void
run() {
while
(flag) {
}
}
|
标志一般使用volatile进行修饰,使其读可见,然后通过设置这个值来控制线程的运行,这已经成了约定俗成的套路.
使用interrupt方法终止线程 。
类似这种.
1
|
while
(!isinterrupted()){……}
|
对于interruptedexception,比如thread.sleep所抛出的,我们一般是补获它,然后静悄悄的忽略。中断允许一个可取消任务来清理正在进行的工作,然后通知其他任务它要被取消,最后才终止,在这种情况下,此类异常需要被仔细处理.
interrupt方法不一定会真正”中断”线程,它只是一种协作机制。interrupt方法通常不能中断一些处于阻塞状态的i/o操作。比如写文件,或者socket传输等。这种情况,需要同时调用正在阻塞操作的close方法,才能够正常退出.
interrupt系列使用时候一定要注意,会引入bug,甚至死锁.
异常处理 。
java中会抛出两种异常。一种是必须要捕获的,比如interruptedexception,否则无法通过编译;另外一种是可以处理也可以不处理的,比如nullpointerexception等.
在我们的任务运行中,很有可能抛出这两种异常。对于第一种异常,是必须放在try,catch中的。但第二种异常如果不去处理的话,会影响任务的正常运行.
有很多同学在处理循环的任务时,没有捕获一些隐式的异常,造成任务在遇到异常的情况下,并不能继续执行下去。如果不能确定异常的种类,可以直接捕获exception或者更通用的throwable.
1
2
3
4
5
6
7
|
while
(!isinterrupted()){
try
{
……
}
catch
(exception ex){
……
}
}
|
同步方式 。
java中实现同步的方式有很多,大体分为以下几种.
生产者、消费者是wait、notify最典型的应用场景,这些函数的调用,是必须要放在synchronized代码块里才能够正常运行的。它们同信号量一样,大多数情况下属于炫技,对代码的可读性影响较大,不推荐。关于objectmonitor相关的几个函数,只要搞懂下面的图,就基本ok了.
使用reentrantlock最容易发生错误的就是忘记在finally代码块里关闭锁。大多数同步场景下,使用lock就足够了,而且它还有读写锁的概念进行粒度上的控制。我们一般都使用非公平锁,让任务自由竞争。非公平锁性能高于公平锁性能,非公平锁能更充分的利用cpu的时间片,尽量的减少cpu空闲的状态时间。非公平锁还会造成饿死现象:有些任务一直获取不到锁.
synchronized通过锁升级机制,速度不见得就比lock慢。而且,通过jstack,能够方便的看到其堆栈,使用还是比较广泛.
volatile总是能保证变量的读可见,但它的目标是基本类型和它锁的基本对象。假如是它修饰的是集合类,比如map,那么它保证的读可见是map的引用,而不是map对象,这点一定要注意.
synchronized和volatile都体现在字节码上(monitorenter、monitorexit),主要是加入了内存屏障。而lock,是纯粹的java api.
threadlocal很方便,每个线程一份数据,也很安全,但要注意内存泄露。假如线程存活时间长,我们要保证每次使用完threadlocal,都调用它的remove()方法(具体来说是expungestaleentry),来清除数据.
关于concurrent包 。
concurrent包是在aqs的基础上搭建起来的,aqs提供了一种实现阻塞锁和一系列依赖fifo等待队列的同步器的框架.
线程池 。
最全的线程池大概有7个参数,想要合理使用线程池,肯定不会不会放过这些参数的优化.
线程池参数 。
concurrent包最常用的就是线程池,平常工作建议直接使用线程池,thread类就可以降低优先级了。我们常用的主要有newsinglethreadexecutor、newfixedthreadpool、newcachedthreadpool、调度等,使用executors工厂类创建.
newsinglethreadexecutor可以用于快速创建一个异步线程,非常方便。而newcachedthreadpool永远不要用在高并发的线上环境,它用的是无界队列对任务进行缓冲,可能会挤爆你的内存.
我习惯性自定义threadpoolexecutor,也就是参数最全的那个.
1
2
3
4
5
6
7
|
public
threadpoolexecutor(
int
corepoolsize,
int
maximumpoolsize,
long
keepalivetime,
timeunit unit,
blockingqueue<runnable> workqueue,
threadfactory threadfactory,
rejectedexecutionhandler handler)
|
假如我的任务可以预估,corepoolsize,maximumpoolsize一般都设成一样大的,然后存活时间设的特别的长。可以避免线程频繁创建、关闭的开销。i/o密集型和cpu密集型的应用线程开的大小是不一样的,一般i/o密集型的应用线程就可以开的多一些.
threadfactory我一般也会定义一个,主要是给线程们起一个名字。这样,在使用jstack等一些工具的时候,能够直观的看到我所创建的线程.
监控 。
高并发下的线程池,最好能够监控起来。可以使用日志、存储等方式保存下来,对后续的问题排查帮助很大.
通常,可以通过继承threadpoolexecutor,覆盖beforeexecute、afterexecute、terminated方法,达到对线程行为的控制和监控.
线程池饱和策略 。
最容易被遗忘的可能就是线程的饱和策略了。也就是线程和缓冲队列的空间全部用完了,新加入的任务将如何处置。jdk默认实现了4种策略,默认实现的是abortpolicy,也就是直接抛出异常。下面介绍其他几种.
discardpolicy 比abort更加激进,直接丢掉任务,连异常信息都没有.
callerrunspolicy 由调用的线程来处理这个任务。比如一个web应用中,线程池资源占满后,新进的任务将会在tomcat线程中运行。这种方式能够延缓部分任务的执行压力,但在更多情况下,会直接阻塞主线程的运行.
discardoldestpolicy 丢弃队列最前面的任务,然后重新尝试执行任务(重复此过程).
很多情况下,这些饱和策略可能并不能满足你的需求,你可以自定义自己的策略,比如将任务持久化到一些存储中.
阻塞队列 。
阻塞队列会对当前的线程进行阻塞。当队列中有元素后,被阻塞的线程会自动被唤醒,这极大的提高的编码的灵活性,非常方便。在并发编程中,一般推荐使用阻塞队列,这样实现可以尽量地避免程序出现意外的错误。阻塞队列使用最经典的场景就是socket数据的读取、解析,读数据的线程不断将数据放入队列,解析线程不断从队列取数据进行处理.
arrayblockingqueue对访问者的调用默认是不公平的,我们可以通过设置构造方法参数将其改成公平阻塞队列.
linkedblockingqueue队列的默认最大长度为integer.max_value,这在用做线程池队列的时候,会比较危险.
synchronousqueue是一个不存储元素的阻塞队列。每一个put操作必须等待一个take操作,否则不能继续添加元素。队列本身不存储任何元素,吞吐量非常高。对于提交的任务,如果有空闲线程,则使用空闲线程来处理;否则新建一个线程来处理任务”。它更像是一个管道,在一些通讯框架中(比如rpc),通常用来快速处理某个请求,应用较为广泛.
delayqueue是一个支持延时获取元素的无界阻塞队列。放入delayqueue的对象需要实现delayed接口,主要是提供一个延迟的时间,以及用于延迟队列内部比较排序。这种方式通常能够比大多数非阻塞的while循环更加节省cpu资源.
另外还有priorityblockingqueue和linkedtransferqueue等,根据字面意思就能猜测它的用途。在线程池的构造参数中,我们使用的队列,一定要注意其特性和边界。比如,即使是最简单的newfixedthreadpool,在某些场景下,也是不安全的,因为它使用了无界队列.
countdownlatch 。
假如有一堆接口a-y,每个接口的耗时最大是200ms,最小是100ms.
我的一个服务,需要提供一个接口z,调用a-y接口对结果进行聚合。接口的调用没有顺序需求,接口z如何在300ms内返回这些数据?
此类问题典型的还有赛马问题,只有通过并行计算才能完成问题。归结起来可以分为两类:
在concurrent包出现之前,需要手工的编写这些同步过程,非常复杂。现在就可以使用countdownlatch和cyclicbarrier进行便捷的编码.
countdownlatch是通过一个计数器来实现的,计数器的初始值为线程的数量。每当一个线程完成了自己的任务后,计数器的值就会减1。当计数器值到达0时,它表示所有的线程已经完成了任务,然后在闭锁上等待的线程就可以恢复执行任务.
cyclicbarrier与其类似,可以实现同样的功能。不过在日常的工作中,使用countdownlatch会更频繁一些.
信号量 。
semaphore虽然有一些应用场景,但大部分属于炫技,在编码中应该尽量少用.
信号量可以实现限流的功能,但它只是常用限流方式的一种。其他两种是漏桶算法、令牌桶算法.
hystrix的熔断功能,也有使用信号量进行资源的控制.
lock && condition 。
在java中,对于lock和condition可以理解为对传统的synchronized和wait/notify机制的替代。concurrent包中的许多阻塞队列,就是使用condition实现的.
但这些类和函数对于初中级码农来说,难以理解,容易产生bug,应该在业务代码中严格禁止。但在网络编程、或者一些框架类工程中,这些功能是必须的,万不可将这部分的工作随便分配给某个小弟.
end 。
不管是wait、notify,还是同步关键字或者锁,能不用就不用,因为它们会引发程序的复杂性。最好的方式,是直接使用concurrent包所提供的机制,来规避一些编码方面的问题.
concurrent包中的cas概念,在一定程度上算是无锁的一种实现。更专业的有类似disruptor的无锁队列框架,但它依然是建立在cas的编程模型上的。近些年,类似akka这样的事件驱动模型正在走红,但编程模型简单,不代表实现简单,背后的工作依然需要多线程去协调.
golang引入协程(coroutine)概念以后,对多线程加入了更加轻量级的补充。java中可以通过javaagent技术加载quasar补充一些功能,但我觉得你不会为了这丁点效率去牺牲编码的可读性.
总结 。
以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,谢谢大家对我的支持.
原文链接:http://www.sayhiai.com/index.php/archives/107/ 。
最后此篇关于JAVA多线程的使用场景与注意事项总结的文章就讲到这里了,如果你想了解更多关于JAVA多线程的使用场景与注意事项总结的内容请搜索CFSDN的文章或继续浏览相关文章,希望大家以后支持我的博客! 。
我正在编写一个具有以下签名的 Java 方法。 void Logger(Method method, Object[] args); 如果一个方法(例如 ABC() )调用此方法 Logger,它应该
我是 Java 新手。 我的问题是我的 Java 程序找不到我试图用作的图像文件一个 JButton。 (目前这段代码什么也没做,因为我只是得到了想要的外观第一的)。这是我的主课 代码: packag
好的,今天我在接受采访,我已经编写 Java 代码多年了。采访中说“Java 垃圾收集是一个棘手的问题,我有几个 friend 一直在努力弄清楚。你在这方面做得怎么样?”。她是想骗我吗?还是我的一生都
我的 friend 给了我一个谜语让我解开。它是这样的: There are 100 people. Each one of them, in his turn, does the following
如果我将使用 Java 5 代码的应用程序编译成字节码,生成的 .class 文件是否能够在 Java 1.4 下运行? 如果后者可以工作并且我正在尝试在我的 Java 1.4 应用程序中使用 Jav
有关于why Java doesn't support unsigned types的问题以及一些关于处理无符号类型的问题。我做了一些搜索,似乎 Scala 也不支持无符号数据类型。限制是Java和S
我只是想知道在一个 java 版本中生成的字节码是否可以在其他 java 版本上运行 最佳答案 通常,字节码无需修改即可在 较新 版本的 Java 上运行。它不会在旧版本上运行,除非您使用特殊参数 (
我有一个关于在命令提示符下执行 java 程序的基本问题。 在某些机器上我们需要指定 -cp 。 (类路径)同时执行java程序 (test为java文件名与.class文件存在于同一目录下) jav
我已经阅读 StackOverflow 有一段时间了,现在我才鼓起勇气提出问题。我今年 20 岁,目前在我的家乡(罗马尼亚克卢日-纳波卡)就读 IT 大学。足以介绍:D。 基本上,我有一家提供簿记应用
我有 public JSONObject parseXML(String xml) { JSONObject jsonObject = XML.toJSONObject(xml); r
我已经在 Java 中实现了带有动态类型的简单解释语言。不幸的是我遇到了以下问题。测试时如下代码: def main() { def ks = Map[[1, 2]].keySet()
一直提示输入 1 到 10 的数字 - 结果应将 st、rd、th 和 nd 添加到数字中。编写一个程序,提示用户输入 1 到 10 之间的任意整数,然后以序数形式显示该整数并附加后缀。 public
我有这个 DownloadFile.java 并按预期下载该文件: import java.io.*; import java.net.URL; public class DownloadFile {
我想在 GUI 上添加延迟。我放置了 2 个 for 循环,然后重新绘制了一个标签,但这 2 个 for 循环一个接一个地执行,并且标签被重新绘制到最后一个。 我能做什么? for(int i=0;
我正在对对象 Student 的列表项进行一些测试,但是我更喜欢在 java 类对象中创建硬编码列表,然后从那里提取数据,而不是连接到数据库并在结果集中选择记录。然而,自从我这样做以来已经很长时间了,
我知道对象创建分为三个部分: 声明 实例化 初始化 classA{} classB extends classA{} classA obj = new classB(1,1); 实例化 它必须使用
我有兴趣使用 GPRS 构建车辆跟踪系统。但是,我有一些问题要问以前做过此操作的人: GPRS 是最好的技术吗?人们意识到任何问题吗? 我计划使用 Java/Java EE - 有更好的技术吗? 如果
我可以通过递归方法反转数组,例如:数组={1,2,3,4,5} 数组结果={5,4,3,2,1}但我的结果是相同的数组,我不知道为什么,请帮助我。 public class Recursion { p
有这样的标准方式吗? 包括 Java源代码-测试代码- Ant 或 Maven联合单元持续集成(可能是巡航控制)ClearCase 版本控制工具部署到应用服务器 最后我希望有一个自动构建和集成环境。
我什至不知道这是否可能,我非常怀疑它是否可能,但如果可以,您能告诉我怎么做吗?我只是想知道如何从打印机打印一些文本。 有什么想法吗? 最佳答案 这里有更简单的事情。 import javax.swin
我是一名优秀的程序员,十分优秀!