一般来说我们在做多线程共享对象的时候就需要进行同步。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(){ }
public static void main(String[] args) throws InterruptedException { SynObject synObject= new SynObject(); synchronized (synObject){ while (true){ Thread.sleep(10000); } } }}
上面代码可能使我们最常使用的代码,我们在对象中定义了一个synchronized的doSomething方法。
如果有恶意代码直接拿到了我们要调用的SynObject对象,并且直接对其进行同步,如上例所示,那么这个对象的锁将永远无法释放。最终导致DOS。
我们看第二种写法:
public Object lock = new Object();
public void doSomething2(){ synchronized (lock){ } }
上面的例子中,我们同步了一个public对象,但是因为该对象是public的,所以恶意程序完全可以访问该public字段,并且永久获得这个对象的monitor,从而产生DOS。
再看下面的一个例子:
private volatile Object lock2 = new Object();
public void doSomething3() { synchronized (lock2) { } }
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) { } }
我们再考虑一下静态方法的情况:
public static synchronized void doSomething5() { }synchronized (SynObject.class) { while (true) { Thread.sleep(10000); }}
上面定义了一个public static的方法,从而锁定的是class对象,恶意代码可以恶意占有该对象的锁,从而导致DOS。