java安全编码指南之:lock和同步的正确使用
文章目錄
- 簡(jiǎn)介
- 使用private final object來作為lock對(duì)象
- 不要synchronize可被重用的對(duì)象
- 不要sync Object.getClass()
- 不要sync高級(jí)并發(fā)對(duì)象
- 不要使用Instance lock來保護(hù)static數(shù)據(jù)
- 在持有l(wèi)ock期間,不要做耗時(shí)操作
- 正確釋放鎖
簡(jiǎn)介
在java多線程環(huán)境中,lock和同步是我們一定會(huì)使用到的功能。那么在java中編寫lock和同步相關(guān)的代碼之后,需要注意哪些問題呢?一起來看看吧。
使用private final object來作為lock對(duì)象
一般來說我們?cè)谧龆嗑€程共享對(duì)象的時(shí)候就需要進(jìn)行同步。java中有兩種同步方式,第一種就是方法同步,第二種是同步塊。
如果我們?cè)趯?shí)例方法中使用的是synchronized關(guān)鍵字,或者在同步塊中使用的是synchronized(this),那么會(huì)以該該對(duì)象的實(shí)例作為monitor,我們稱之為intrinsic lock。
如果有惡意代碼惡意獲取該對(duì)象的鎖并且釋放,那么我們的系統(tǒng)將不能及時(shí)響應(yīng)正常的服務(wù),將會(huì)遭受到DOS攻擊。
解決這種問題的方法就是使用private final object來作為lock的對(duì)象。因?yàn)槭莗rivate的,所以惡意對(duì)象無法獲取到該對(duì)象的鎖,從而避免了問題的產(chǎn)生。
如果是在類方法(static)中使用了synchronized關(guān)鍵字,那么將會(huì)以這個(gè)class對(duì)象作為monitor。這種情況下,惡意對(duì)象可以通過該class的子類或者直接獲取到該class,然后通過調(diào)用getClass()獲取到class對(duì)象,從而進(jìn)行加鎖操作,讓正常服務(wù)無法獲取到鎖。
所以,我們推薦使用private final object來作為lock對(duì)象。
下面舉幾個(gè)例子來說明:
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 foreverThread.sleep(10000);}}} }上面代碼可能使我們最常使用的代碼,我們?cè)趯?duì)象中定義了一個(gè)synchronized的doSomething方法。
如果有惡意代碼直接拿到了我們要調(diào)用的SynObject對(duì)象,并且直接對(duì)其進(jìn)行同步,如上例所示,那么這個(gè)對(duì)象的鎖將永遠(yuǎn)無法釋放。最終導(dǎo)致DOS。
我們看第二種寫法:
public Object lock = new Object();public void doSomething2(){synchronized (lock){//do something}}上面的例子中,我們同步了一個(gè)public對(duì)象,但是因?yàn)樵搶?duì)象是public的,所以惡意程序完全可以訪問該public字段,并且永久獲得這個(gè)對(duì)象的monitor,從而產(chǎn)生DOS。
再看下面的一個(gè)例子:
private volatile Object lock2 = new Object();public void doSomething3() {synchronized (lock2) {// do something}}public void setLock2(Object lockValue) {lock2 = lockValue;}上面的例子中,我們定義了一個(gè)private的lock對(duì)象,并且使用它來為doSomething3方法加鎖。
雖然是private的,但是我們提供了一個(gè)public的方法來對(duì)該對(duì)象進(jìn)行修改。所以也是有安全問題的。
正確的做法是使用private final Object:
private final Object lock4= new Object();public void doSomething4() {synchronized (lock4) {// do something}}我們?cè)倏紤]一下靜態(tài)方法的情況:
public static synchronized void doSomething5() {// do something}synchronized (SynObject.class) {while (true) {Thread.sleep(10000); } }上面定義了一個(gè)public static的方法,從而鎖定的是class對(duì)象,惡意代碼可以惡意占有該對(duì)象的鎖,從而導(dǎo)致DOS。
不要synchronize可被重用的對(duì)象
之前我們?cè)谥v表達(dá)式規(guī)則的時(shí)候,提到了封裝類對(duì)象的構(gòu)建原則:
對(duì)于Boolean和Byte來說,如果直接從基礎(chǔ)類值構(gòu)建的話,也是同一個(gè)對(duì)象。
而對(duì)于Character來說,如果值的范圍在\u0000 to \u007f,則屬于同一個(gè)對(duì)象,如果超出了這個(gè)范圍,則是不同的對(duì)象。
對(duì)于Integer和Short來說,如果值的范圍在-128 and 127,則屬于同一個(gè)對(duì)象,如果超出了這個(gè)范圍,則是不同的對(duì)象。
舉個(gè)例子:
Boolean boolA=true;Boolean boolB=true;System.out.println(boolA==boolB);上面從基礎(chǔ)類型構(gòu)建的Boolean對(duì)象其實(shí)是同一個(gè)對(duì)象。
如果我們?cè)诖a中使用下面的Boolean對(duì)象來進(jìn)行同步,則可能會(huì)觸發(fā)安全問題:
private final Boolean booleanLock = Boolean.FALSE;public void doSomething() {synchronized (booleanLock) {// ...} }上面的例子中,我們從Boolean.FALSE構(gòu)建了一個(gè)Boolean對(duì)象,雖然這個(gè)對(duì)象是private的,但是惡意代碼可以通過Boolean.FALSE來構(gòu)建一個(gè)相同的對(duì)象,從而讓private規(guī)則失效。
同樣的問題也可能出現(xiàn)在String中:
private final String lock = "lock";public void doSomething() {synchronized (lock) {// ...} }因?yàn)镾tring對(duì)象有字符串常量池,直接通過字符串來創(chuàng)建的String對(duì)象其實(shí)是同一個(gè)對(duì)象。所以上面的代碼是有安全問題的。
解決辦法就是使用new來新創(chuàng)建一個(gè)對(duì)象。
private final String lock = new String("LOCK");不要sync Object.getClass()
有時(shí)候我們想要同步class類,Object提供了一個(gè)方便的getClass方法來返回當(dāng)前的類。但是如果在父類和子類的情況下,子類的getClass會(huì)返回子類的class類而不是父類的class類,從而產(chǎn)生不一致對(duì)象同步的情況。
看下面的一個(gè)例子:
public class SycClass {public void doSomething(){synchronized (getClass()){//do something}} }在SycClass中,我們定義了一個(gè)doSomething方法,在該方法中,我們sync的是getClass()返回的對(duì)象。
如果SycClass有子類的情況下:
public class SycClassSub extends SycClass{public void doSomethingElse(){synchronized (SycClass.class){doSomething();}} }doSomethingElse方法實(shí)際上獲得了兩個(gè)鎖,一個(gè)是SycClass,一個(gè)是SycClassSub,從而產(chǎn)生了安全隱患。
在sync的時(shí)候,我們需要明確指定要同步的對(duì)象,有兩種方法指定要同步的class:
synchronized (SycClass.class) synchronized (Class.forName("com.flydean.SycClass"))我們可以直接調(diào)用SycClass.class也可以使用Class.forName來獲取。
不要sync高級(jí)并發(fā)對(duì)象
我們把實(shí)現(xiàn)了java.util.concurrent.locks包中的Lock和Condition接口的對(duì)象稱作高級(jí)并發(fā)對(duì)象。比如:ReentrantLock。
這些高級(jí)并發(fā)對(duì)象看起來也是一個(gè)個(gè)的Lock,那么我們可不可以直接sync這些高級(jí)并發(fā)對(duì)象呢?看下面的例子:
public class SyncLock {private final Lock lock = new ReentrantLock();public void doSomething(){synchronized (lock){//do something}} }看起來好像沒問題,但是我們要注意的是,我們自定義的synchronized (lock)和高級(jí)并發(fā)對(duì)象中的Lock實(shí)現(xiàn)是不一樣的,如果我們同時(shí)使用了synchronized (lock)和Lock自帶的lock.lock(),那么就有可能產(chǎn)生安全隱患。
所以,對(duì)于這些高級(jí)并發(fā)對(duì)象,最好的做法就是不要直接sync,而是使用他們自帶的lock機(jī)制,如下:
public void doSomething2(){lock.lock();try{//do something}finally {lock.unlock();}}不要使用Instance lock來保護(hù)static數(shù)據(jù)
一個(gè)class中可以有static類變量,也可以有實(shí)例變量。類變量是和class相關(guān)的,而實(shí)例變量是和class的實(shí)例對(duì)象相關(guān)的。
那么我們?cè)诒Wo(hù)類變量的時(shí)候,一定要注意sync的也必須是類變量,如果sync的是實(shí)例變量,就無法達(dá)到保護(hù)的目的。
看下面的一個(gè)例子:
public class SyncStatic {private static volatile int age;public synchronized void doSomething(){age++;} }我們定義了一個(gè)static變量age,然后在一個(gè)方法中希望對(duì)其累加。之前的文章我們也講過了,++是一個(gè)復(fù)合操作,我們需要對(duì)其進(jìn)行數(shù)據(jù)同步。
但是上面的例子中,我們使用了synchronized關(guān)鍵字,同步的實(shí)際上是SyncStatic的實(shí)例對(duì)象,如果有多個(gè)線程創(chuàng)建多個(gè)實(shí)例對(duì)象同時(shí)調(diào)用doSomething方法,完全是可以并行進(jìn)行的。從而導(dǎo)致++操作出現(xiàn)問題。
同樣的,下面的代碼也是一樣的問題:
private final Object lock = new Object();public void doSomething2(){synchronized (lock) {age++;}}解決辦法就是定義一個(gè)類變量:
private static final Object lock3 = new Object();public void doSomething3(){synchronized (lock3) {age++;}}在持有l(wèi)ock期間,不要做耗時(shí)操作
如果在持有l(wèi)ock期間,我們進(jìn)行了比較耗時(shí)的操作,像I/O操作,那么持有l(wèi)ock的時(shí)間就會(huì)過長(zhǎng),如果是在高并發(fā)的情況下,就有可能出現(xiàn)線程餓死的情況,或者DOS。
所以這種情況我們一定要避免。
正確釋放鎖
在持有鎖之后,一定要注意正確的釋放鎖,即使遇到了異常也不應(yīng)該打斷鎖的釋放。
一般來說鎖放在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/
最通俗的解讀,最深刻的干貨,最簡(jiǎn)潔的教程,眾多你不知道的小技巧等你來發(fā)現(xiàn)!
歡迎關(guān)注我的公眾號(hào):「程序那些事」,懂技術(shù),更懂你!
總結(jié)
以上是生活随笔為你收集整理的java安全编码指南之:lock和同步的正确使用的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 看动画学算法之:递归和递归树
- 下一篇: java安全编码指南之:输入注入inje