简介

在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和同步的正确使用的更多相关文章

  1. java安全编码指南之:基础篇

    目录 简介 java平台本身的安全性 安全第一,不要写聪明的代码 在代码设计之初就考虑安全性 避免重复的代码 限制权限 构建可信边界 封装 写文档 简介 作为一个程序员,只是写出好用的代码是不够的,我 ...

  2. java安全编码指南之:死锁dead lock

    目录 简介 不同的加锁顺序 使用private类变量 使用相同的Order 释放掉已占有的锁 简介 java中为了保证共享数据的安全性,我们引入了锁的机制.有了锁就有可能产生死锁. 死锁的原因就是多个 ...

  3. java安全编码指南之:可见性和原子性

    目录 简介 不可变对象的可见性 保证共享变量的复合操作的原子性 保证多个Atomic原子类操作的原子性 保证方法调用链的原子性 读写64bits的值 简介 java类中会定义很多变量,有类变量也有实例 ...

  4. java安全编码指南之:Mutability可变性

    目录 简介 可变对象和不可变对象 创建mutable对象的拷贝 为mutable类创建copy方法 不要相信equals 不要直接暴露可修改的属性 public static fields应该被置位f ...

  5. java安全编码指南之:字符串和编码

    目录 简介 使用变长编码的不完全字符来创建字符串 char不能表示所有的Unicode 注意Locale的使用 文件读写中的编码格式 不要将非字符数据编码为字符串 简介 字符串是我们日常编码过程中使用 ...

  6. java安全编码指南之:输入校验

    目录 简介 在字符串标准化之后进行校验 注意不可信字符串的格式化 小心使用Runtime.exec() 正则表达式的匹配 简介 为了保证java程序的安全,任何外部用户的输入我们都认为是可能有恶意攻击 ...

  7. java安全编码指南之:ThreadPool的使用

    目录 简介 java自带的线程池 提交给线程池的线程要是可以被中断的 正确处理线程池中线程的异常 线程池中使用ThreadLocal一定要注意清理 简介 在java中,除了单个使用Thread之外,我 ...

  8. java安全编码指南之:文件和共享目录的安全性

    目录 简介 linux下的文件基本权限 linux文件的特殊权限 Set UID 和 Set GID Sticky Bit SUID/SGID/SBIT权限设置 文件隐藏属性 特殊文件 java中在共 ...

  9. java安全编码指南之:声明和初始化

    目录 简介 初始化顺序 循环初始化 不要使用java标准库中的类名作为自己的类名 不要在增强的for语句中修改变量值 简介 在java对象和字段的初始化过程中会遇到哪些安全性问题呢?一起来看看吧. 初 ...

随机推荐

  1. 用navicat进行身份验证连接出现cannot connect to Mongodb authentication failed

    用navicat进行身份验证连接出现cannot connect to Mongodb authentication failed. 解决办法: 1.打开mongoDB连接 win+r --cmd-- ...

  2. shell小技巧(3)复制文件到其他主机

    复制一个文件nfsclient.sh到主机172.18.117.162的/tmp目录下 scp -P22 /tmp/nfsclient.sh  root@172.18.117.162:/tmp -P2 ...

  3. docker打包项目

    nginx镜像制作实战 docker容器的主业 docker理念里,容器启动时,应当为它指定主业是什么,如nginx容器主业就是nginx代理服务,tomcat容器就是web服务等等 1.容器创建时, ...

  4. JS实现串行请求

    使用async和await var fn = async function(promiseArr) { for(let i = 0,len = arr.length; i<len; i++) { ...

  5. Java 的各种内部类、Lambda表达式

    内部类 内部类是指在一个外部类的内部再定义一个类.内部类的出现,再次打破了Java单继承的局限性. 内部类可以是静态 static 的,也可用 public,default,protected 和 p ...

  6. 吴恩达《深度学习》-课后测验-第三门课 结构化机器学习项目(Structuring Machine Learning Projects)-Week1 Bird recognition in the city of Peacetopia (case study)( 和平之城中的鸟类识别(案例研究))

    Week1 Bird recognition in the city of Peacetopia (case study)( 和平之城中的鸟类识别(案例研究)) 1.Problem Statement ...

  7. tcp、http 学习小结

    tcp.http 学习小结 前言 最近因为cdn的一个问题,困扰了自己好久.因为需要统计网站访问的成功数,而且要求比较精确.目前的实现不能满足要求,因为没有区别访问成功与否,也没有对超时做处理.期间解 ...

  8. [程序员代码面试指南]递归和动态规划-机器人达到指定位置方法数(一维DP待做)(DP)

    题目描述 一行N个位置1到N,机器人初始位置M,机器人可以往左/右走(只能在位置范围内),规定机器人必须走K步,最终到位置P.输入这四个参数,输出机器人可以走的方法数. 解题思路 DP 方法一:时间复 ...

  9. NOIP2017 Day1 T1 小凯的疑惑

    题目描述 小凯手中有两种面值的金币,两种面值均为正整数且彼此互素.每种金币小凯都有 无数个.在不找零的情况下,仅凭这两种金币,有些物品他是无法准确支付的.现在小凯想知道在无法准确支付的物品中,最贵的价 ...

  10. 使用Excel设计fastreport报表

    最近做项目遇到一些报表设计的问题.项目的报表使用 Fast Report,然而该控件调整报表格式比较繁琐,有时候调整某一栏的宽度,整个标题栏都需要调整,且调整以后 还不一定能对齐.客户的很多报表都是基 ...