java安全编码指南之:lock和同步的正确使用
简介
在java多线程环境中,lock和同步是我们一定会使用到的功能。那么在java中编写lock和同步相关的代码之后,需要注意哪些问题呢?一起来看看吧。
使用private final object来作为lock对象
一般来说我们在做多线程共享对象的时候就需要进行同步。java中有两种同步方式,第一种就是方法同步,第二种是同步块。
如果我们在实例方法中使用的是synchronized关键字,或者在同步块中使用的是synchronized(this),那么会以该该对象的实例作为monitor,我们称之为intrinsic lock。
如果有恶意代码恶意获取该对象的锁并且释放,那么我们的系统将不能及时响应正常的服务,将会遭受到DOS攻击。
解决这种问题的方法就是使用private final object来作为lock的对象。因为是private的,所以恶意对象无法获取到该对象的锁,从而避免了问题的产生。
如果是在类方法(static)中使用了synchronized关键字,那么将会以这个class对象作为monitor。这种情况下,恶意对象可以通过该class的子类或者直接获取到该class,然后通过调用getClass()获取到class对象,从而进行加锁操作,让正常服务无法获取到锁。
所以,我们推荐使用private final object来作为lock对象。
下面举几个例子来说明:
public class SynObject {
public synchronized void doSomething(){
//do something
}
public static void main(String[] args) throws InterruptedException {
SynObject synObject= new SynObject();
synchronized (synObject){
while (true){
//loop forever
Thread.sleep(10000);
}
}
}
}
上面代码可能使我们最常使用的代码,我们在对象中定义了一个synchronized的doSomething方法。
如果有恶意代码直接拿到了我们要调用的SynObject对象,并且直接对其进行同步,如上例所示,那么这个对象的锁将永远无法释放。最终导致DOS。
我们看第二种写法:
public Object lock = new Object();
public void doSomething2(){
synchronized (lock){
//do something
}
}
上面的例子中,我们同步了一个public对象,但是因为该对象是public的,所以恶意程序完全可以访问该public字段,并且永久获得这个对象的monitor,从而产生DOS。
再看下面的一个例子:
private volatile Object lock2 = new Object();
public void doSomething3() {
synchronized (lock2) {
// do something
}
}
public void setLock2(Object lockValue) {
lock2 = lockValue;
}
上面的例子中,我们定义了一个private的lock对象,并且使用它来为doSomething3方法加锁。
虽然是private的,但是我们提供了一个public的方法来对该对象进行修改。所以也是有安全问题的。
正确的做法是使用private final Object:
private final Object lock4= new Object();
public void doSomething4() {
synchronized (lock4) {
// do something
}
}
我们再考虑一下静态方法的情况:
public static synchronized void doSomething5() {
// do something
}
synchronized (SynObject.class) {
while (true) {
Thread.sleep(10000);
}
}
上面定义了一个public static的方法,从而锁定的是class对象,恶意代码可以恶意占有该对象的锁,从而导致DOS。
不要synchronize可被重用的对象
之前我们在讲表达式规则的时候,提到了封装类对象的构建原则:
对于Boolean和Byte来说,如果直接从基础类值构建的话,也是同一个对象。
而对于Character来说,如果值的范围在\u0000 to \u007f,则属于同一个对象,如果超出了这个范围,则是不同的对象。
对于Integer和Short来说,如果值的范围在-128 and 127,则属于同一个对象,如果超出了这个范围,则是不同的对象。
举个例子:
Boolean boolA=true;
Boolean boolB=true;
System.out.println(boolA==boolB);
上面从基础类型构建的Boolean对象其实是同一个对象。
如果我们在代码中使用下面的Boolean对象来进行同步,则可能会触发安全问题:
private final Boolean booleanLock = Boolean.FALSE;
public void doSomething() {
synchronized (booleanLock) {
// ...
}
}
上面的例子中,我们从Boolean.FALSE构建了一个Boolean对象,虽然这个对象是private的,但是恶意代码可以通过Boolean.FALSE来构建一个相同的对象,从而让private规则失效。
同样的问题也可能出现在String中:
private final String lock = "lock";
public void doSomething() {
synchronized (lock) {
// ...
}
}
因为String对象有字符串常量池,直接通过字符串来创建的String对象其实是同一个对象。所以上面的代码是有安全问题的。
解决办法就是使用new来新创建一个对象。
private final String lock = new String("LOCK");
不要sync Object.getClass()
有时候我们想要同步class类,Object提供了一个方便的getClass方法来返回当前的类。但是如果在父类和子类的情况下,子类的getClass会返回子类的class类而不是父类的class类,从而产生不一致对象同步的情况。
看下面的一个例子:
public class SycClass {
public void doSomething(){
synchronized (getClass()){
//do something
}
}
}
在SycClass中,我们定义了一个doSomething方法,在该方法中,我们sync的是getClass()返回的对象。
如果SycClass有子类的情况下:
public class SycClassSub extends SycClass{
public void doSomethingElse(){
synchronized (SycClass.class){
doSomething();
}
}
}
doSomethingElse方法实际上获得了两个锁,一个是SycClass,一个是SycClassSub,从而产生了安全隐患。
在sync的时候,我们需要明确指定要同步的对象,有两种方法指定要同步的class:
synchronized (SycClass.class)
synchronized (Class.forName("com.flydean.SycClass"))
我们可以直接调用SycClass.class也可以使用Class.forName来获取。
不要sync高级并发对象
我们把实现了java.util.concurrent.locks包中的Lock和Condition接口的对象称作高级并发对象。比如:ReentrantLock。
这些高级并发对象看起来也是一个个的Lock,那么我们可不可以直接sync这些高级并发对象呢?看下面的例子:
public class SyncLock {
private final Lock lock = new ReentrantLock();
public void doSomething(){
synchronized (lock){
//do something
}
}
}
看起来好像没问题,但是我们要注意的是,我们自定义的synchronized (lock)和高级并发对象中的Lock实现是不一样的,如果我们同时使用了synchronized (lock)和Lock自带的lock.lock(),那么就有可能产生安全隐患。
所以,对于这些高级并发对象,最好的做法就是不要直接sync,而是使用他们自带的lock机制,如下:
public void doSomething2(){
lock.lock();
try{
//do something
}finally {
lock.unlock();
}
}
不要使用Instance lock来保护static数据
一个class中可以有static类变量,也可以有实例变量。类变量是和class相关的,而实例变量是和class的实例对象相关的。
那么我们在保护类变量的时候,一定要注意sync的也必须是类变量,如果sync的是实例变量,就无法达到保护的目的。
看下面的一个例子:
public class SyncStatic {
private static volatile int age;
public synchronized void doSomething(){
age++;
}
}
我们定义了一个static变量age,然后在一个方法中希望对其累加。之前的文章我们也讲过了,++是一个复合操作,我们需要对其进行数据同步。
但是上面的例子中,我们使用了synchronized关键字,同步的实际上是SyncStatic的实例对象,如果有多个线程创建多个实例对象同时调用doSomething方法,完全是可以并行进行的。从而导致++操作出现问题。
同样的,下面的代码也是一样的问题:
private final Object lock = new Object();
public void doSomething2(){
synchronized (lock) {
age++;
}
}
解决办法就是定义一个类变量:
private static final Object lock3 = new Object();
public void doSomething3(){
synchronized (lock3) {
age++;
}
}
在持有lock期间,不要做耗时操作
如果在持有lock期间,我们进行了比较耗时的操作,像I/O操作,那么持有lock的时间就会过长,如果是在高并发的情况下,就有可能出现线程饿死的情况,或者DOS。
所以这种情况我们一定要避免。
正确释放锁
在持有锁之后,一定要注意正确的释放锁,即使遇到了异常也不应该打断锁的释放。
一般来说锁放在finally{}中释放最好。
public void doSomething(){
lock.lock();
try{
//do something
}finally {
lock.unlock();
}
}
本文的代码:
learn-java-base-9-to-20/tree/master/security
本文已收录于 http://www.flydean.com/java-security-code-line-lock/
最通俗的解读,最深刻的干货,最简洁的教程,众多你不知道的小技巧等你来发现!
欢迎关注我的公众号:「程序那些事」,懂技术,更懂你!
java安全编码指南之:lock和同步的正确使用的更多相关文章
- java安全编码指南之:基础篇
目录 简介 java平台本身的安全性 安全第一,不要写聪明的代码 在代码设计之初就考虑安全性 避免重复的代码 限制权限 构建可信边界 封装 写文档 简介 作为一个程序员,只是写出好用的代码是不够的,我 ...
- java安全编码指南之:死锁dead lock
目录 简介 不同的加锁顺序 使用private类变量 使用相同的Order 释放掉已占有的锁 简介 java中为了保证共享数据的安全性,我们引入了锁的机制.有了锁就有可能产生死锁. 死锁的原因就是多个 ...
- java安全编码指南之:可见性和原子性
目录 简介 不可变对象的可见性 保证共享变量的复合操作的原子性 保证多个Atomic原子类操作的原子性 保证方法调用链的原子性 读写64bits的值 简介 java类中会定义很多变量,有类变量也有实例 ...
- java安全编码指南之:Mutability可变性
目录 简介 可变对象和不可变对象 创建mutable对象的拷贝 为mutable类创建copy方法 不要相信equals 不要直接暴露可修改的属性 public static fields应该被置位f ...
- java安全编码指南之:字符串和编码
目录 简介 使用变长编码的不完全字符来创建字符串 char不能表示所有的Unicode 注意Locale的使用 文件读写中的编码格式 不要将非字符数据编码为字符串 简介 字符串是我们日常编码过程中使用 ...
- java安全编码指南之:输入校验
目录 简介 在字符串标准化之后进行校验 注意不可信字符串的格式化 小心使用Runtime.exec() 正则表达式的匹配 简介 为了保证java程序的安全,任何外部用户的输入我们都认为是可能有恶意攻击 ...
- java安全编码指南之:ThreadPool的使用
目录 简介 java自带的线程池 提交给线程池的线程要是可以被中断的 正确处理线程池中线程的异常 线程池中使用ThreadLocal一定要注意清理 简介 在java中,除了单个使用Thread之外,我 ...
- java安全编码指南之:文件和共享目录的安全性
目录 简介 linux下的文件基本权限 linux文件的特殊权限 Set UID 和 Set GID Sticky Bit SUID/SGID/SBIT权限设置 文件隐藏属性 特殊文件 java中在共 ...
- java安全编码指南之:声明和初始化
目录 简介 初始化顺序 循环初始化 不要使用java标准库中的类名作为自己的类名 不要在增强的for语句中修改变量值 简介 在java对象和字段的初始化过程中会遇到哪些安全性问题呢?一起来看看吧. 初 ...
随机推荐
- node.js的安装及其相关环境变量的配置
笔者最近一直重置电脑,本来想换台mac,想了想还是加下配置吧. 于是慢慢的一直会去安装node 接下来进入教程环节 一.NodeJS下载 1.下载NodeJS安装包下载地址:NodeJS下载 2.开始 ...
- vue <compent> 结合is实现动态切换子组件
这里就很自然的产生一个新问题:如何给相应的子组件传props?
- Oracle12C创建scott账户
Oracle12C创建scott账户 具体步骤: 打开SqlPlus 输入用户名和密码用户名:sys口令:sys as sysdba 创建c##scott用户CREATE USER c##scott ...
- 基础Html重点——防健忘
一.head标签重点 <head> <meta charset="utf-8"> <title>第二天课</title> <! ...
- selenium常用api之切换:table切换、alert弹框切换、iframe框架切换
10.查看浏览器打开了多少个table和当前页面在哪个table 测试:打开了浏览器后,打开了一个新的标签页之后,显示此时有2个table,浏览器中当前页面展示的是第2个页面,但是代码打印显示的仍然是 ...
- PHP7性能提升原因
1.存储变量的结构体变小,尽量使结构体里成员共用内存空间,减少引用,这样内存占用降低,变量的操作速度得到提升 2.字符串结构体的改变,字符串信息和数据本身原来是分成两个独立内存块存放,php7尽量将它 ...
- [LeetCode]152. 乘积最大子序列(DP)
题目 给定一个整数数组 nums ,找出一个序列中乘积最大的连续子序列(该序列至少包含一个数). 示例 1: 输入: [2,3,-2,4] 输出: 6 解释: 子数组 [2,3] 有最大乘积 6. 示 ...
- vue | vue实现列表同时展开与单独展开
需求:每个li标签在点击的时候,都同时展开. 但是碰见几个问题: 1.如果点第一个li 所有li都会展开: 2.点击第一个li,第一个li展开,点击第二个li,第一个li闭合,第二个li展开 这两种情 ...
- Shell编程(3)
1. shell流程控制 2. for语句 3. while语句 4. break和continue语句 5. case语句 6. shell编程高级实战 shell流程控制 流程控制是改变程序运行顺 ...
- 关于JSON的零碎小知识
1.ali的fastjson在将实体类转成jsonString的时候,一些首字母大写的字段会自动修改为小字母,这种字段加 @JsonProperty(value = "DL_id" ...