java安全编码指南之:Thread API调用规则
简介
java中多线程的开发中少不了使用Thread,我们在使用Thread中提供的API过程中,应该注意些什么规则呢?
一起来看一看吧。
start一个Thread
Thread中有两个方法,一个是start方法,一个是run方法,两个都可以调用,那么两个有什么区别呢?
先看一下start方法:
public synchronized void start() {
if (threadStatus != 0)
throw new IllegalThreadStateException();
group.add(this);
boolean started = false;
try {
start0();
started = true;
} finally {
try {
if (!started) {
group.threadStartFailed(this);
}
} catch (Throwable ignore) {
}
}
}
private native void start0();
start()是一个synchronized的方法,通过它会去调用native的start0方法,而最终将会调用Thread的run()方法。
我们知道,创建一个Thread有两种方式,一种是传入一个Runnable,一个是继承Thread,并重写run()方法。
如果我们直接调用Thread的run()方法会发生什么事情呢?
先看一下run方法的定义:
public void run() {
if (target != null) {
target.run();
}
}
默认情况下, 这个target就是一个Runnable对象,如果Thread是通过Runnable来构建的话,调用Thread.run()会在当前线程中运行run方法中的内容。
如果Thread是以其形式构建,并且没有重新run()方法,那么直接调用Thread.run()将什么都不会做。
public void wrongStart(){
Runnable runnable= ()-> System.out.println("in thread running!");
Thread thread= new Thread(runnable);
thread.run();
}
public void correctStart(){
Runnable runnable= ()-> System.out.println("in thread running!");
Thread thread= new Thread(runnable);
thread.start();
}
所以,上面两种调用方式,只有第二种是正确的。
不要使用ThreadGroup
Thread中有个字段类型是java.lang.ThreadGroup,这个主要是用来给Thread进行分组,我们看下Thread的这个构造函数:
public Thread(ThreadGroup group, Runnable target) {
this(group, target, "Thread-" + nextThreadNum(), 0);
}
上面的构造函数可以在传入runnable的同时传递一个ThreadGroup对Thread进行分组。
如果没有指定ThreadGroup,那么将会为其分配一个默认的default group。
ThreadGroup是做什么的呢?ThreadGroup是java 1.0引入的方法,主要是一次性的对一组thread进行操作。我们可以调用ThreadGroup.interrupt()来一次性的对整个Group的Thread进行interrupts操作。
虽然ThreadGroup提供了很多有用的方法,但是其中很多方法都被废弃了,比如:allowThreadSuspension(), resume(), stop(), 和 suspend(),并且ThreadGroup中还有很多方法是非线程安全的:
- ThreadGroup.activeCount()
这个方法主要是用来统计一个ThreadGroup中活动的线程个数,这个方法会统计还未启动的线程,同时也会受系统线程的影响,所以是不准确的。
- ThreadGroup.enumerate()
这个方法是将ThreadGroup和子group的线程拷贝到一个数组中,但是如果数组太小了,多余的线程是会被自动忽略的。
ThreadGroup本身有一个 stop() 方法用来停止所有的线程,但是stop是不安全的,已经被废弃了。
那么我们该怎么去安全的停止很多个线程呢?
使用executor.shutdown()就可以了。
不要使用stop()方法
刚刚讲了ThreadGroup中不要调用stop()方法,因为stop是不安全的。
调用stop方法会立马释放线程持有的所有的锁,并且会抛出ThreadDeath异常。
因为会释放所有的锁,所以可能会造成受这些锁保护的对象的状态发生不一致的情况。
替代的方法有两种,一种是使用volatile flag变量,来控制线程的循环执行:
private volatile boolean done = false;
public void shutDown(){
this.done= true;
}
public void stopWithFlag(){
Runnable runnable= ()->{
while(!done){
System.out.println("in Runnable");
}
};
Thread thread= new Thread(runnable);
thread.start();
shutDown();
}
另外一种方法就是调用interrupt(), 这里我们要注意interrupt()的使用要点:
如果当前线程实例在调用Object类的wait(),wait(long)或wait(long,int)方法或join(),join(long),join(long,int)方法,或者在该实例中调用了Thread.sleep(long)或Thread.sleep(long,int)方法,并且正在阻塞状态中时,则其中断状态将被清除,并将收到InterruptedException。
如果此线程在InterruptibleChannel上的I/O操作中处于被阻塞状态,则该channel将被关闭,该线程的中断状态将被设置为true,并且该线程将收到java.nio.channels.ClosedByInterruptException异常。
如果此线程在java.nio.channels.Selector中处于被被阻塞状态,则将设置该线程的中断状态为true,并且它将立即从select操作中返回。
如果上面的情况都不成立,则设置中断状态为true。
先看下面的例子:
public static void main(String[] args) {
Runnable runnable= ()->{
while (!Thread.interrupted()) {
System.out.println("in thread");
}
};
Thread thread= new Thread(runnable);
thread.start();
Thread.sleep(5000);
thread.interrupt();
}
我们在while循环中调用了Thread.interrupted()方法用来判断线程是否被设置了中断位,然后在main方法中调用了thread.interrupt()来设置中断,最终可以正确的停止Thread。
注意,这里运行的Thread并没有被阻塞,所以并不满足我们上面提到的第一个条件。
下面我们再看一个例子:
public static void main(String[] args) {
Runnable runnable= ()->{
while (!Thread.interrupted()) {
System.out.println("in thread");
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
Thread thread= new Thread(runnable);
thread.start();
thread.interrupt();
}
这个例子和上面的例子不同之处就是在于,Thread中调用了sleep方法,导致Thread被阻塞了,最终满足了第一个条件,从而不会设置终端位,只会抛出InterruptedException,所以这个例子中线程是不会被停止的,大家一定要注意。
wait 和 await 需要放在循环中调用
为什么要放在循环中呢?因为我们希望wait不是被错误的被唤醒,所以我们需要在wait被唤醒之后,重新检测一遍条件。
错误的调用是放在if语句中:
synchronized (object) {
if (<condition does not hold>) {
object.wait();
}
// Proceed when condition holds
}
正确的方法是放在while循环中:
synchronized (object) {
while (<condition does not hold>) {
object.wait();
}
// Proceed when condition holds
}
本文的代码:
learn-java-base-9-to-20/tree/master/security
本文已收录于 http://www.flydean.com/java-security-code-line-thread/
最通俗的解读,最深刻的干货,最简洁的教程,众多你不知道的小技巧等你来发现!
欢迎关注我的公众号:「程序那些事」,懂技术,更懂你!
java安全编码指南之:Thread API调用规则的更多相关文章
- java安全编码指南之:基础篇
目录 简介 java平台本身的安全性 安全第一,不要写聪明的代码 在代码设计之初就考虑安全性 避免重复的代码 限制权限 构建可信边界 封装 写文档 简介 作为一个程序员,只是写出好用的代码是不够的,我 ...
- java安全编码指南之:输入校验
目录 简介 在字符串标准化之后进行校验 注意不可信字符串的格式化 小心使用Runtime.exec() 正则表达式的匹配 简介 为了保证java程序的安全,任何外部用户的输入我们都认为是可能有恶意攻击 ...
- java安全编码指南之:可见性和原子性
目录 简介 不可变对象的可见性 保证共享变量的复合操作的原子性 保证多个Atomic原子类操作的原子性 保证方法调用链的原子性 读写64bits的值 简介 java类中会定义很多变量,有类变量也有实例 ...
- java安全编码指南之:异常处理
目录 简介 异常简介 不要忽略checked exceptions 不要在异常中暴露敏感信息 在处理捕获的异常时,需要恢复对象的初始状态 不要手动完成finally block 不要捕获NullPoi ...
- java安全编码指南之:死锁dead lock
目录 简介 不同的加锁顺序 使用private类变量 使用相同的Order 释放掉已占有的锁 简介 java中为了保证共享数据的安全性,我们引入了锁的机制.有了锁就有可能产生死锁. 死锁的原因就是多个 ...
- java安全编码指南之:lock和同步的正确使用
目录 简介 使用private final object来作为lock对象 不要synchronize可被重用的对象 不要sync Object.getClass() 不要sync高级并发对象 不要使 ...
- java安全编码指南之:ThreadPool的使用
目录 简介 java自带的线程池 提交给线程池的线程要是可以被中断的 正确处理线程池中线程的异常 线程池中使用ThreadLocal一定要注意清理 简介 在java中,除了单个使用Thread之外,我 ...
- java安全编码指南之:线程安全规则
目录 简介 注意线程安全方法的重写 构造函数中this的溢出 不要在类初始化的时候使用后台线程 简介 如果我们在多线程中引入了共享变量,那么我们就需要考虑一下多线程下线程安全的问题了.那么我们在编写代 ...
- java安全编码指南之:文件IO操作
目录 简介 创建文件的时候指定合适的权限 注意检查文件操作的返回值 删除使用过后的临时文件 释放不再被使用的资源 注意Buffer的安全性 注意 Process 的标准输入输出 InputStream ...
随机推荐
- Istio 运维实战系列(2):让人头大的『无头服务』-上
本系列文章将介绍用户从 Spring Cloud,Dubbo 等传统微服务框架迁移到 Istio 服务网格时的一些经验,以及在使用 Istio 过程中可能遇到的一些常见问题的解决方法. 什么是『无头服 ...
- v-charts 绘制柱状图、条形图、水球图、雷达图、折线图+柱状图,附官网地址
v-charts 官网地址:https://v-charts.js.org/#/ 柱状图: <template> <ve-histogram :data="chartDat ...
- Shell学习(五)Shell输出命令
一.echo命令 #1.直接显示字符串 echo "It is a test" #输出:It is a test #2.显示转义字符 echo "\"It is ...
- msf学习笔记
metasploit frame 这是一个渗透测试框架,围绕这个框架,我们就可以完成最终的目标,即渗透测试. 渗透测试者困扰:需要掌握数百个工具,上千条命令参数,很难记住. 新出现的漏洞PoC/EXP ...
- vue学习08 v-bind指令
目录 vue学习08 v-bind指令 v-bind指令的作用是为元素绑定属性 完整写法是v-bind:属性名,可简写为:属性名 练习代码为: 运行效果为: vue学习08 v-bind指令 v-bi ...
- 提效工具-python解析xmind文件及xmind用例统计
现状 每个公司都有一个维护测试case的系统,有自研的也有买的,比如QC, 禅道等等,QA往往习惯使用xmind等思维导图工具来编写测试用例,因为思路清晰,编写方便,那么这就有一个问题,大多公司要求所 ...
- UnityShader学习笔记- Stencil Buffer
模板测试(Stencil Test)是现代渲染流水线的一环,其中涉及到的就是模板缓冲(Stencil Buffer),模板缓冲可以用来制作物体的遮罩.轮廓描边.阴影.遮挡显示等等效果 目录 Stenc ...
- 点、像素、分辨率、PPI、DPI等
屏幕尺寸 屏幕尺寸是屏幕的对角线的长度,单位是英寸,1英寸=2.54厘米. pixel 像素,它是组成图片的最小单元,代表红绿蓝等各种颜色. dot 点,它是屏幕发光.cmos感光的最小物理单元,水平 ...
- Centos-文本过滤-grep
grep 文本过滤,对文本的每一行进行关键字搜索,如果找到则输出 相关选项 -A 除了列出符合关键字的行外,还输出符合关键字行后多少行内容 -c 只显示符号条件的行号 -f 批量搜索,把关键字写入到 ...
- heap是堆,stack是栈
1.栈是用来存放基本类型的变量和引用类型的变量,堆用来存放new出来的对象和数组. 2.栈的存取速度快,但不灵活.堆的存取速度慢,但是存取灵活,空间动态分配. 3.栈在建立在连续的物理位置上,而堆只需 ...