【Java并发系列01】Thread及ThreadGroup杂谈
一、前言
最近开始学习Java并发编程,把学习过程记录下。估计不是那么系统,主要应该是Java API的介绍(不涉及最基础的概念介绍),想要深入系统学习推荐看一本书《Java Concurrency in Practice 》(建议看英文,也可以看中文译本:《 Java 并发编程实战》)。
并发编程的基础就是线程,所以这一篇对线程做初步了解。
二、Thread和ThredGroup的关系
因为Thread的构造函数中有关于ThradGroup的,所以了解它们之间的关系是有必要的。ThradGroup之间的关系是树的关系,而Thread与ThradGroup的关系就像元素与集合的关系。关系图简单如下:
图片引用突破渴望的博客:http://www.cnblogs.com/hvicen/p/6218981.html,谢谢指正错误。
其中有一点要明确一下:main方法执行后,将自动创建system线程组合main线程组,main方法所在线程存放在main线程组中。
三、Thread API
3.1 基本属性
首先应该了解线程的基本属性:
- name:线程名称,可以重复,若没有指定会自动生成。
- id:线程ID,一个正long值,创建线程时指定,终生不变,线程终结时ID可以复用。
- priority:线程优先级,取值为1到10,线程优先级越高,执行的可能越大,若运行环境不支持优先级分10级,如只支持5级,那么设置5和设置6有可能是一样的。
- state:线程状态,Thread.State枚举类型,有NEW、RUNNABLE、BLOCKED、WAITING、TIMED_WAITING、TERMINATED 5种。
- ThreadGroup:所属线程组,一个线程必然有所属线程组。
- UncaughtExceptionHandler:未捕获异常时的处理器,默认没有,线程出现错误后会立即终止当前线程运行,并打印错误。
3.2 字段摘要
Thread类有三个字段,设置线程优先级时可使用:
- MIN_PRIORITY:1,最低优先级
- NORM_PRIORITY:5,普通优先级
- MAX_PRIORITY:10,最高优先级
3.3 构造方法
现只介绍参数最多的一个:
Thread(ThreadGroup group, Runnable target, String name, long stackSize)
- group:指定当前线程的线程组,未指定时线程组为创建该线程所属的线程组。
- target:指定运行其中的Runnable,一般都需要指定,不指定的线程没有意义,或者可以通过创建Thread的子类并重新run方法。
- name:线程的名称,不指定自动生成。
- stackSize:预期堆栈大小,不指定默认为0,0代表忽略这个属性。与平台相关,不建议使用该属性。
3.4 方法摘要
3.4.1 静态方法
- Thread Thread.currentThread() :获得当前线程的引用。获得当前线程后对其进行操作。
- Thread.UncaughtExceptionHandler getDefaultUncaughtExceptionHandler() :返回线程由于未捕获到异常而突然终止时调用的默认处理程序。
- int Thread.activeCount():当前线程所在线程组中活动线程的数目。
- void dumpStack() :将当前线程的堆栈跟踪打印至标准错误流。
- int enumerate(Thread[] tarray) :将当前线程的线程组及其子组中的每一个活动线程复制到指定的数组中。
- Map<Thread,StackTraceElement[]> getAllStackTraces() :返回所有活动线程的堆栈跟踪的一个映射。
- boolean holdsLock(Object obj) :当且仅当当前线程在指定的对象上保持监视器锁时,才返回 true。
- boolean interrupted() :测试当前线程是否已经中断。
- void setDefaultUncaughtExceptionHandler(Thread.UncaughtExceptionHandler eh) :设置当线程由于未捕获到异常而突然终止,并且没有为该线程定义其他处理程序时所调用的默认处理程序。
- void sleep(long millis) :休眠指定时间
- void sleep(long millis, int nanos) :休眠指定时间
- void yield() :暂停当前正在执行的线程对象,并执行其他线程。意义不太大
3.4.2 普通方法
- void checkAccess() :判定当前运行的线程是否有权修改该线程。
- ClassLoader getContextClassLoader() :返回该线程的上下文 ClassLoader。
- long getId() :返回该线程的标识符。
- String getName() :返回该线程的名称。
- int getPriority() :返回线程的优先级。
- StackTraceElement[] getStackTrace() :返回一个表示该线程堆栈转储的堆栈跟踪元素数组。
- Thread.State getState() :返回该线程的状态。
- ThreadGroup getThreadGroup() :返回该线程所属的线程组。
- Thread.UncaughtExceptionHandler getUncaughtExceptionHandler() :返回该线程由于未捕获到异常而突然终止时调用的处理程序。
- void interrupt() :中断线程。
- boolean isAlive() :测试线程是否处于活动状态。
- boolean isDaemon() :测试该线程是否为守护线程。
- boolean isInterrupted():测试线程是否已经中断。
- void join() :等待该线程终止。
- void join(long millis) :等待该线程终止的时间最长为 millis 毫秒。
- void join(long millis, int nanos) :等待该线程终止的时间最长为 millis 毫秒 + nanos 纳秒。
- void run() :线程启动后执行的方法。
- void setContextClassLoader(ClassLoader cl) :设置该线程的上下文 ClassLoader。
- void setDaemon(boolean on) :将该线程标记为守护线程或用户线程。
- void start():使该线程开始执行;Java 虚拟机调用该线程的 run 方法。
- String toString():返回该线程的字符串表示形式,包括线程名称、优先级和线程组。
3.4.3 作废方法
- int countStackFrames() :没有意义不做解释。
- void destroy() :破坏线程,不释放锁,已经不能再使用,使用会抛出NoSuchMethodError。
- void suspend() :挂起线程,不要使用。
- void resume() :恢复线程,不要使用。
- void stop() :停止线程释放锁,不要使用。
- void stop(Throwable obj) :同上。
3.5 Thread知识方法讲解
3.5.1 setDefaultUncaughtExceptionHandler(Thread.UncaughtExceptionHandler eh)
首先要了解什么是Thread.UncaughtExceptionHandler,默认来说当线程出现未捕获的异常时,会中断并抛出异常,抛出后的动作只有简单的堆栈输出。如:
public class ThreadTest{
public static void main(String[] args) throws Exception{
Thread t1=new Thread(new Runnable(){
public void run(){
int a=1/0;
}
});
t1.start();
}
}
那么代码运行到int a=1/0;就会报错:
Exception in thread "Thread-0" java.lang.ArithmeticException: / by zero
at yiwangzhibujian.ThreadTest$1.run(ThreadTest.java:11)
at java.lang.Thread.run(Thread.java:662)
这时候如果设置了Thread.UncaughtExceptionHandler,那么处理器会将异常进行捕获,捕获后就可以对其进行处理:
public class ThreadTest{
public static void main(String[] args) throws Exception{
Thread t1=new Thread(new Runnable(){
public void run(){
int a=1/0;
}
});
t1.setUncaughtExceptionHandler(new UncaughtExceptionHandler(){
@Override
public void uncaughtException(Thread t,Throwable e){
System.out.println("线程:"+t.getName()+"出现异常,异常信息:"+e);
}
});
t1.start();
}
}
那么当线程抛出异常后就可以对其抓取并进行处理,最终结果如下:
线程:Thread-0出现异常,异常信息:java.lang.ArithmeticException: / by zero
如果自己写线程,那么完全可以在run方法内,将所有代码进行try catch,在catch里做相同的操作。UncaughtExceptionHandler的意义在于不对(或者不能对)原有线程进行修改的情况下,为其增加一个错误处理器。
3.5.2 interrupt() 、interrupted() 、isInterrupted()作用
因为stop()方法已经不建议使用了,下面的3.5.4进行详解,所以如何中断一个线程就成了一个问题,一种简单的办法是设置一个全局变量needStop,如下:
@Override
public void run(){
while(!needStop){
//执行某些任务
}
}
或者需要操作耗时较长的方法内,每一步执行之前进行判断:
@Override
public void run(){
//耗时较长步骤1
if(needStop) return;
//耗时较长步骤2
if(needStop) return;
//耗时较长步骤3
}
这样在其他的地方将此线程停止掉,因为停止是在自己的预料下,所以不会有死锁或者数据异常问题(当然你的程序编写的时候要注意)。
其实Thread类早就有类似的功能,那就是Thread具有中断属性。可以通过调用interrupt()方法对线程中断属性设置为true,这将导致如下两种情况:
- 当线程正常运行时,中断属性设置为true,调用其isInterrupted()方法会返回true。
- 当线程阻塞时(wait,join,sleep方法),会立即抛出InterruptedException异常,并将中断属性设置为false。此时再调用isInterrupted()会返回false。
这样就由程序来决定当检测到中断属性为true时,怎么对线程中断进行处理。因此,上面的代码可以改成:
@Override
public void run(){
while(!Thread.currentThread().isInterrupted()){
//执行某些任务
}
}
---------------------------------------------------------
@Override
public void run(){
//耗时较长步骤1
if(Thread.currentThread().isInterrupted()) return;
//耗时较长步骤2
if(Thread.currentThread().isInterrupted()) return;
//耗时较长步骤3
}
interrupted()的方法名容易给人一种误解,看似和interrupt()方法一样,但是其实际含义是,返回当前中断状态,并将其设置为false。
3.5.3 yield()和sleep(0)
yield()方法的API容易给人一种误解,它的实际含义是停止执行当前线程(立即),让CPU重新选择需要执行的线程,因为具有随机性,所以也有可能重新执行该线程,通过下面例子了解:
public class ThreadTest{
public static void main(String[] args) throws Exception{
Thread t1=new Thread(new Runnable(){
@Override
public void run(){
while(true){
System.out.println(1);
Thread.yield();
}
}
});
Thread t2=new Thread(new Runnable(){
public void run(){
while(true){
System.out.println(2);
Thread.yield();
}
}
});
t1.start();
t2.start();
}
}
程序执行结果并不是121212而是有,有连续的1和连续的2。
经过测试yield()和sleep(0)的效果是一样的,sleep(0)底层要么是和yield()一样,要么被过滤掉了(纯靠猜测),不过sleep(0)没有任何意义。要是真打算让当前线程暂停还是应该使用sleep(long millis,int nanos)这个方法,设置几纳秒表示下诚意,或者找到想要让步的线程,调用它的join方法更实际一些。
3.5.4 stop()、suspend()、resume()为什么不建议使用
stop方法会立即中断线程,虽然会释放持有的锁,但是线程的运行到哪是未知的,假如在具有上下文语义的位置中断了,那么将会导致信息出现错误,比如:
@Override
public void run(){
try{
//处理资源并插入数据库
}catch(Exception e){
//出现异常回滚
}
}
如果在调用stop时,代码运行到捕获异常需要回滚的地方,那么将会因为没有回滚,保存了错误的信息。
而suspend会将当前线程挂起,但是并不会释放所持有的资源,如果恢复线程在调用resume也需要那个资源,那么就会形成死锁。当然可以通过你精湛的编程来避免死锁,但是这个方法具有固有的死锁倾向。所以不建议使用。其他暂停方法为什么可用:
- wait方法会释放锁,所以不会有死锁问题
- sleep方法虽然不释放锁,但是它不需要唤醒,在使用的时候已经指定想要的睡眠时间了。
jdk的文章详细介绍了方法禁用的原因:文章地址,有空可以看一看,如果你足够大胆,也是可以使用的。
四、ThreadGroup API
4.1 基本属性
name:当前线程的名称。
parent:当前线程组的父线程组。
MaxPriority:当前线程组的最高优先级,其中的线程优先级不能高于此。
4.2 构造方法
只介绍一个构造方法:
ThreadGroup(ThreadGroup parent, String name) :
- parent:父线程组,若为指定则是创建该线程组的线程所需的线程组。
- name:线程组的名称,可重复。
4.3 常用方法摘要
API详解(中文,英文)。
- int activeCount():返回此线程组中活动线程的估计数。
- void interrupt():中断此线程组中的所有线程。
- void uncaughtException(Thread t, Throwable e) :设置当前线程组的异常处理器(只对没有异常处理器的线程有效)。
4.4 ThreadGroup作用
这个线程组可以用来管理一组线程,通过activeCount() 来查看活动线程的数量。其他没有什么大的用处。
这篇博客讲解了最基本的线程及线程组,这是并发编程的根基,应该全面了解。
未经许可禁止转载。
【Java并发系列01】Thread及ThreadGroup杂谈的更多相关文章
- Java并发编程:Thread类的使用
Java并发编程:Thread类的使用 在前面2篇文章分别讲到了线程和进程的由来.以及如何在Java中怎么创建线程和进程.今天我们来学习一下Thread类,在学习Thread类之前,先介绍与线程相关知 ...
- Java并发系列[1]----AbstractQueuedSynchronizer源码分析之概要分析
学习Java并发编程不得不去了解一下java.util.concurrent这个包,这个包下面有许多我们经常用到的并发工具类,例如:ReentrantLock, CountDownLatch, Cyc ...
- Java并发系列[2]----AbstractQueuedSynchronizer源码分析之独占模式
在上一篇<Java并发系列[1]----AbstractQueuedSynchronizer源码分析之概要分析>中我们介绍了AbstractQueuedSynchronizer基本的一些概 ...
- Java并发系列[3]----AbstractQueuedSynchronizer源码分析之共享模式
通过上一篇的分析,我们知道了独占模式获取锁有三种方式,分别是不响应线程中断获取,响应线程中断获取,设置超时时间获取.在共享模式下获取锁的方式也是这三种,而且基本上都是大同小异,我们搞清楚了一种就能很快 ...
- Java并发系列[5]----ReentrantLock源码分析
在Java5.0之前,协调对共享对象的访问可以使用的机制只有synchronized和volatile.我们知道synchronized关键字实现了内置锁,而volatile关键字保证了多线程的内存可 ...
- 3、Java并发编程:Thread类的使用
Java并发编程:Thread类的使用 在前面2篇文章分别讲到了线程和进程的由来.以及如何在Java中怎么创建线程和进程.今天我们来学习一下Thread类,在学习Thread类之前,先介绍与线程相关知 ...
- Java 并发系列之一
Java 并发系列之一 简单的总结了一些 Java 常用的集合之后,发现许多集合都针对多线程提供了支持,比如 ConcurrentHashMap 使用分段锁来提高多线程环境下的性能表现与安全表现.所以 ...
- java io系列01之 "目录"
java io 系列目录如下: 01. java io系列01之 "目录" 02. java io系列02之 ByteArrayInputStream的简介,源码分析和示例(包括 ...
- Java 集合系列 01 总体框架
java 集合系列目录: Java 集合系列 01 总体框架 Java 集合系列 02 Collection架构 Java 集合系列 03 ArrayList详细介绍(源码解析)和使用示例 Java ...
随机推荐
- 一个简单的 Web 服务器 [未完成]
最近学习C++,linux和网络编程,想做个小(mini)项目. 就去搜索引擎, 开源中国, Sourceforge上找http server的项目. 好吧,也去了知乎. 知乎上程序员氛围好, ...
- Bubble Cup 8 finals F. Bulbo (575F)
题意: 给定初始位置,查询n次区间,每次查询前可以花费移动距离的代价来移动, 查询时需要花费当前位置到区间内最近的点的距离,求最小代价. 1<=n<=5000,1<=所有位置< ...
- php-fpm启动,重启,终止操作
最近安装了mysqli扩展,重启了nginx后,phpinfo()没有显示出mysqli,后来搞不出原因,直接使用了pdo连接数据库.直到今天安装redis后phpinfo()没有显示redis,内心 ...
- 【Sorting Collection】
排序集锦 各种排序算法,总结一下,一直在遗忘...... [冒泡排序] 就是下面这个鬼啦: c实现代码(升序): #include<stdio.h> void BubbleSort(int ...
- [Algorithm & NLP] 文本深度表示模型——word2vec&doc2vec词向量模型
深度学习掀开了机器学习的新篇章,目前深度学习应用于图像和语音已经产生了突破性的研究进展.深度学习一直被人们推崇为一种类似于人脑结构的人工智能算法,那为什么深度学习在语义分析领域仍然没有实质性的进展呢? ...
- Android handler的使用简单示例
Handler handler = new Handler() { @Override public void handleMessage(Message msg) { super.handleMes ...
- swift与OC之间不得不知道的21点
swift与OC之间不得不知道的21点 自6月的WWDC大会上由苹果的大神Chris Lattner向我们首次展示swift至今已经大半年时间了,虽然绝大部分软件公司代码里还都见不到一丁点swif ...
- 为什么要用base64编码
1.需求 了解为什么要使用base64对数据编码 2.理由 因为传输二进制数据的时候,网络中间的有些路由会把ascii码中的不可见字符删了,导致数据不一致.一般也会对url进行base64编码 Whe ...
- WinForm 进程、线程、TreeView递归加载、发送邮件--2016年12月13日
进程:一个程序就是一个进程,但是也有一个程序需要多个进程来支持的情况 进程要使用的类是:Process它在命名空间:System.Diagnostics; 静态方法Start(); Process.S ...
- linux回退到上次访问目录
cd / cd .. 回到上级目录 cd - 回到上次访问目录