5个由浅及深的Java线程使用技巧

Java线程有哪些不太为人所知的技巧与用法?

  从本文中你将会看到线程提供的一些不太常用的方法及技术。不管你是初学者还是高级用户或者是Java专家,希望都能看一下哪些是你已经知道的,而哪些是刚了解的。如果你认为关于线程还有什么值得分享给大家的。

  初学

  1.线程名

  程序中的每个线程都有一个名字,创建线程的时候会给它分配一个简单的Java字符串来作为线程名。默认的名字是”Thread-0″, “Thread-1″, “Thread-2″等等。现在有趣的事情来了——Thread提供了两种方式来设置线程名:

    线程构造函数,下面是最简单的一个实现:

class SuchThread extends Thread {
 
    Public void run() {
        System.out.println ("Hi Mom! " + getName());
    }
 
}
 
SuchThread wow = new SuchThread("much-name");

    线程名setter方法:

wow.setName(“Just another thread name”);

  没错,线程名是可变的。因此我们可以在运行时修改它的名字,而不用在初始化的时候就指定好。name字段其实就是一个简单的字符串对象。也就是说它能达到2³¹-1个字符那么长(Integer.MAX_VALUE)。这足够用了。注意这个名字并不是一个唯一性的标识,因此不同的线程也可以拥有同样的线程名。还有一点就是,不要把null用作线程名,否则会抛出异常(当然了,”null”还是可以的)。

  使用线程名来调试问题

  既然可以设置线程名,那么如果遵循一定的命名规则的话,出了问题的时候排查起来就能更容易一些。“Thread-6″这样的名字看起来就太没心没肺了,肯定有比它更好的名字。在处理用户请求的时候,可以将事务ID追加到线程名后面,这样能显著减少你排查问题的时间。

“pool-1-thread-1″ #17 prio=5 os_prio=31 tid=0x00007f9d620c9800
nid=0x6d03 in Object.wait() [0x000000013ebcc000]

  “pool-1-thread-1″,这也太严肃了吧。我们来看下这是什么情况,给它起一个好点的名字:

Thread.currentThread().setName(Context + TID + Params + current Time, ...);

  现在我们再来运行下jstack,情况便豁然开朗了:

”Queue Processing Thread, MessageID: AB5CAD, type:
AnalyzeGraph, queue: ACTIVE_PROD, Transaction_ID: 5678956,
Start Time: 30/12/2014 17:37″ #17 prio=5 os_prio=31 tid=0x00007f9d620c9800
nid=0x6d03 in Object.wait() [0x000000013ebcc000]

  如果我们能知道线程在做什么,这样当它出问题的时候,至少可以拿到事务ID来开始排查。你可以回溯这个问题,复现它,然后定位问题并搞定它。如果你想知道jstack有什么给力的用法,可以看下这篇文章。

  2. 线程优先级

  线程还有一个有意思的属性就是它的优先级。线程的优先级介于1 (MINPRIORITY)到10 (MAXPRIORITY)之间,主线程默认是5(NORM_PRIORITY)。每个新线程都默认继承父线程的优先级,因此如果你没有设置过的话,所有线程的优先级都是5。这个是通常被忽视的属性,我们可以通过getPriority()与setPriority()方法来获取及修改它的值。线程的构造函数里是没有这个功能的。

  什么地方会用到优先级?

  当然并不是所有的线程都是平等的,有的线程需要立即引起CPU的重视,而有些线程则只是后台任务而已。优先级就是用来把这些告诉给操作系统的线程调度器的。在Takipi中,这是我们开发的一错误跟踪及排查的工具,负责处理用户异常的线程的优先级是MAX_PRIORITY,而那些只是在上报新的部署情况的线程,它们的优先级就要低一些。你可能会觉得优先级高的线程从JVM的线程调度器那得到的时间会多一些。但其实并都是这样的。

  在操作系统层面,每一个新线程都会对应一个本地线程,你所设置的Java线程的优先级会被转化成本地线程的优先级,这个在各个平台上是不一样的。在Linux上,你可以打开“-XX:+UseThreadPriorities”选项来启用这项功能。正如前面所说的,线程优先级只是你所提供的一个建议。和Linux本地的优先级相比,Java线程的优先级并不能覆盖全所有的级别(Linux共有1到99个优先级,线程的优先级在是-20到20之间)。最大的好处就是你所设定的优先级能在每个线程获得的CPU时间上有所体现,不过完全依赖于线程优先级的做法是不推荐的。

  进阶篇

  3.线程本地存储

  这个和前面提到的两个略有不同。ThreadLocal是在Thread类之外实现的一个功能(java.lang.ThreadLocal),但它会为每个线程分别存储一份唯一的数据。正如它的名字所说的,它为线程提供了本地存储,也就是说你所创建出来变量对每个线程实例来说都是唯一的。和线程名,线程优先级类似,你可以自定义出一些属性,就好像它们是存储在Thread线程内部一样,是不是觉得酷?不过先别高兴得太早了,有几句丑话得先说在前头。

  创建ThreadLocal有两种推荐方式:要么是静态变量,要么是单例实例中的属性,这样可以是非静态的。注意,它的作用域是全局的,只不过对访问它的线程而言好像是本地的而已。在下面这个例子中,ThreadLocal里面存储了一个数据结构,这样我们可以很容易地访问到它:

public static class CriticalData
{
    public int transactionId;
    public int username;
}
 
public static final ThreadLocal<CriticalData> globalData =
    new ThreadLocal<CriticalData>();

  一旦获取到了ThreadLocal对象,就可以通过 globalData.set()和globalData.get()方法来对它进行操作了。

  全局变量?这不是什么好事

  也尽然。ThreadLocal可以用来存储事务ID。如果代码中出现未捕获异常的时候它就相当有用了。最佳实践是设置一个UncaughtExceptionHandler,这个是Thread类本身就支持的,但是你得自己去实现一下这个接口。一旦执行到了UncaughtExceptionHandler里,就几乎没有任何线索能够知道到底发生了什么事情了。这会儿你能获取到的就只有Thread对象,之前导致异常发生的所有变量都无法再访问了,因为那些栈帧都已经被弹出了。一旦到了UncaughtExceptionHandler里,这个线程就只剩下最后一口气了,唯一能抓住的最后一根稻草就是ThreadLocal。

  我们来试下这么做:

System.err.println("Transaction ID " + globalData.get().transactionId);

  我们可以将一些与错误相关的有价值的上下文信息给存储到里面添。ThreadLocal还有一个更有创意的用法,就是用它来分配一块特定的内存,这样工作线程可以把它当作缓存来不停地使用。当然了,这有没有用得看你在CPU和内存之间是怎么权衡的了。没错,ThreadLocal需要注意的就是会造成内存空间的浪费。只要线程还活着,那么它就会一直存在,除非你主动释放否则它是不会被回收的。因此如果使用它的话你最好注意一下,尽量保持简单。

  4. 用户线程及守护线程

  我们再回到Thread类。程序中的每个线程都会有一个状态,要么是用户状态,要么是守护状态。换句话说,要么是前台线程要么是后台线程。主线程默认是用户线程,每个新线程都会从创建它的线程中继承线程状态。因此如果你把一个线程设置成守护线程,那么它所创建的所有线程都会被标记成守护线程。如果程序中的所有线程都是守护线程的话,那么这个进程便会终止。我们可以通过Boolean .setDaemon(true)和.isDaemon()方法来查看及设置线程状态。

  什么时候会用到守护线程?

  如果进程不必等到某个线程结束才能终止,那么这个线程就可以设置成守护线程。这省掉了正常关闭线程的那些麻烦事,可以立即将线程结束掉。换个角度来说,如果一个正在执行某个操作的线程必须要正确地关闭掉否则就会出现不好的后果的话,那么这个线程就应该是用户线程。通常都是些关键的事务,比方说,数据库录入或者更新,这些操作都是不能中断的。

  专家级

  5. 处理器亲和性(Processor Affinity)

  这里要讲的会更靠近硬件,也就是说,当软件遇上了硬件。处理器亲和性使得你能够将线程或者进程绑定到特定的CPU核上。这意味着只要是某个特定的线程,它就肯定只会在某个特定的CPU核上执行。通常来讲如何绑定是由操作系统的线程调度器根据它自己的逻辑来决定的,它很可能会将我们前面提到的线程优先级也一并考虑进来。

  这么做的好处在于CPU缓存。如果某个线程只会在某个核上运行,那么它的数据恰好在缓存里的概率就大大提高了。如果数据正好就在CPU缓存里,那么就没有必要重新再从内存里加载了。你所节省的这几毫秒时间就能用在刀刃上,在这段时间里代码可以马上开始执行,也就能更好地利用所分配给它的CPU时间。当然了,操作系统层面可能会存在某种优化,硬件架构当然也是个很重要的因素,但利用了处理器的亲和性至少能够减小线程切换CPU的机率。

  由于这里掺杂着多种因素,处理器亲和性到底对吞吐量有多大的影响,最好还是通过测试的方式来进行证明。也许这个方法并不是总能显著地提升性能,但至少有一个好处就是吞吐量会相对稳定。亲和策略可以细化到非常细的粒度上,这取决于你具体想要什么。高频交易行业便是这一策略最能大显身手的场景之一。

  处理器亲和性的测试

  Java对处理器的亲和性并没有原生的支持,当然了,故事也还没有就此结束。在Linux上,我们可以通过taskset命令来设置进程的亲和性。假设我们现在有一个Java进程在运行,而我们希望将它绑定到某个特定的CPU上:

taskset -c 1 “<span style="width: auto; height: auto; float: none;" id="4_nwp"><a style="text-decoration: none;" mpid="4" target="_blank" href="http://cpro.baidu.com/cpro/ui/uijs.php?c=news&cf=1001&ch=0&di=128&fv=15&jk=a4cdcab9727499ec&k=java&k0=java&kdi0=0&luki=10&n=10&p=baidu&q=06011078_cpr&rb=0&rs=1&seller_id=1&sid=ec997472b9cacda4&ssp2=1&stid=0&t=tpclicked3_hc&tu=u1922429&u=http%3A%2F%2Fwww%2Eadmin10000%2Ecom%2Fdocument%2F5854%2Ehtml&urlid=0" id="4_nwl"><span style="color:#0000ff;font-size:14px;width:auto;height:auto;float:none;">java</span></a></span> AboutToBePinned”

  如果是一个已经在运行了的进程:

taskset -c 1 <PID>

  要想深入到线程级别还得再加些代码才行。所幸的是,有一个开源库能完成这样的功能:Java-Thread-Affinity。这个库是由OpenHFT的Peter Lawrey开发的,实现这一功能最简单直接的方式应该就是使用这个库了。我们通过一个例子来快速看下如何绑定某个线程,关于该库的更多细节请参考它在Github上的文档:

AffinityLock al = AffinityLock.acquireLock();

  这样就可以了。关于获取锁的一些更高级的选项——比如说根据不同的策略来选择CPU——在Github上都有详细的说明。

  结论

  本文我们介绍了关于线程的5点知识:线程名,线程本地存储,优先级,守护线程以及处理器亲和性。希望这能为你日常工作中所用到的内容打开一扇新的窗户,期待你们的反馈!还有什么有关线程处理的方法可以分享给大家的吗,请不吝赐教。

时间: 2024-10-03 20:41:57

5个由浅及深的Java线程使用技巧的相关文章

由浅到深了解JavaScript类_javascript技巧

时间:2006-3-6 作者:泣红亭 简介: 原文出处:www.51js.com 说明:2004年6月15日 翻译: 最近在无忧脚本混了一阵子,回复了一些贴子,自己却没有做出什么东东让大家看看,心里有些不安,于是写了下边的一点东西,本来应该发在类封装区的,考虑到那里比较冷,而这篇文章我希望能够帮助到更多的朋友,因此放到这里来了.  类是什么? 许多刚接触编程的朋友都可能理解不了类,其实类是对我们这个现实世界的模拟,把它说成"类别"或者"类型"可能会更容易理解一些.比

Angular浅入深出系列 - 写在前面

本系列目录: 写在前面 基础知识 控制器(Controller) 作用域(Scope) 集合(Collection) 模块(Module) 依赖注入(Dependency Injection) 服务(Service) 过滤器(Filter) 指令(Directive) 指令作用域(Directive Scope) 路由(Routing) HTTP 总结 很久之前就开始着手准备Angular教程,但苦于一直没有找到合适的切入点.前一段时间,和朋友聚餐时,听几位前端开发Leader朋友都在抱怨,"A

javascript控制Div层透明属性由浅变深由深变浅逐渐显示_javascript技巧

搬运的留着以后自己看! 复制下面蓝色的代码保存为html格式的文件,使用网页浏览器浏览效果 代码支持IE6.7.8/firefox/Chrome浏览器 复制代码 代码如下: <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="

浅谈java线程中生产者与消费者的问题_java

一.概念 生产者与消费者问题是一个金典的多线程协作的问题.生产者负责生产产品,并将产品存放到仓库:消费者从仓库中获取产品并消费.当仓库满时,生产者必须停止生产,直到仓库有位置存放产品:当仓库空时,消费者必须停止消费,直到仓库中有产品. 解决生产者/消费者问题主要用到如下几个技术:1.用线程模拟生产者,在run方法中不断地往仓库中存放产品.2.用线程模拟消费者,在run方法中不断地从仓库中获取产品.3  . 仓库类保存产品,当产品数量为0时,调用wait方法,使得当前消费者线程进入等待状态,当有新

浅谈Java线程的生命周期

创建线程 在 Java 程序中创建线程有几种方法.每个 Java 程序至少包含一个线程:主线程.其它线程都是通过 Thread 构造器或实例化继承类 Thread 的类来创建的. Java 线程可以通过直接实例化 Thread 对象或实例化继承 Thread 的对象来创建其它线程.在线程基础中的示例(其中,我们在十秒钟之内计算尽量多的素数)中,我们通过实例化 CalculatePrimes 类型的对象(它继承了 Thread),创建了一个线程. 当我们讨论 Java 程序中的线程时,也许会提到两

Java 线程池的原理与实现

这几天主要是狂看源程序,在弥补了一些以前知识空白的同时,也学会了不少新的知识(比如 NIO),或者称为新技术吧.线程池就是其中之一,一提到线程,我们会想到以前<操作系统>的生产者与消费者,信号量,同步控制等等.一提到池,我们会想到数据库连接池,但是线程池又如何呢? 建议:在阅读本文前,先理一理同步的知识,特别是syncronized同步关键字的用法.关于我对同步的认识,要缘于大三年的一本书,书名好像是 Java 实战,这本书写得实在太妙了,真正的从理论到实践,从截图分析到.class字节码分析

关于java线程

问题描述 怎么样才能慢慢由浅到深的去理解呢? 解决方案 解决方案二:ConcurrentProgramminginJava解决方案三:多看多操作解决方案四:去看些书吧,然后自己动手做做,先做一些简单的,然后做一些难一点的!比如你感觉差不多理解了以后,可以做一个练习打字游戏之类的程序(字母由上到下,然后根据字母在键盘上输入相应的,正确加分,如果在规定时间里,没有消除字母,扣分)!我那时候就是这么做的!也不是很难,呵呵!解决方案五:首先看书是毫无疑问的,可以先看看corejava和Thinkingi

浅谈Javascript数组的使用_javascript技巧

上一篇说了数组的索引,这一篇说下数组的使用. 数组的大小 js的数组可以动态调整大小,更确切点说,它没有数组越界的概念,a[a.length]没什么问题.比如声明一个数组a = [1, 3, 5],现在的数组大小是3,最后一个元素的索引是2,但是你依然可以使用a[3],访问a[3]返回的是undefined,给a[3]赋值:a[3] = 7,是给数组a添加了一个元素,现在数组a的长度是4了.你可以试试把下面这段代码放到浏览器里运行下: var a = []; for(int i = 0; i <

java线程安全总结

最近想将java基础的一些东西都整理整理,写下来,这是对知识的总结,也是一种乐趣.已经拟好了提纲,大概分为这几个主题: java线程安全,java垃圾收集,java并发包详细介绍,java profile和jvm性能调优 .慢慢写吧.本人jameswxx原创文章,转载请注明出处,我费了很多心血,多谢了.关于java线 程安全,网上有很多资料,我只想从自己的角度总结对这方面的考虑,有时候写东西是很痛苦的,知道一些东西,但想用文字说清楚,却不是那么容易.我认为要认 识java线程安全,必须了解两个主