乐趣区

关于java: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/

最艰深的解读,最粗浅的干货,最简洁的教程,泛滥你不晓得的小技巧等你来发现!

欢送关注我的公众号:「程序那些事」, 懂技术,更懂你!

退出移动版