Java高并發(fā)--線程安全策略

Java高并發(fā)--線程安全策略

主要是學(xué)習(xí)慕課網(wǎng)實戰(zhàn)視頻《Java并發(fā)編程入門與高并發(fā)面試》的筆記

不可變對象

發(fā)布不可變對象可保證線程安全。

實現(xiàn)不可變對象有哪些要注意的地方?比如JDK中的String類。

  • 不提供setter方法(包括修改字段、字段引用到的的對象等方法)
  • 將所有字段設(shè)置為final、private
  • 將類修飾為final,不允許子類繼承、重寫方法。可以將構(gòu)造函數(shù)設(shè)為private,通過工廠方法創(chuàng)建。
  • 如果類的字段是對可變對象的引用,不允許修改被引用對象。 1)不提供修改可變對象的方法;2)不共享對可變對象的引用。對于外部傳入的可變對象,不保存該引用。如要保存可以保存其復(fù)制后的副本;對于內(nèi)部可變對象,不要返回對象本身,而是返回其復(fù)制后的副本。

final關(guān)鍵字可以修飾在類、方法、變量:

  • 類:被修飾的類不能被繼承
  • 方法:被修飾的方法不能被重寫
  • 變量:被修飾的是一個基本類型,其值不能被修改;被修飾的是一個對象引用,這里的“不可變”指不允許其再指向其他對象,但是可以修改對象里面的值。

關(guān)于上一條中final修飾對象的引用。以ArrayList為例

final List<Integer> list= new ArrayList<>();
list.add(3);
list.add(4);
list = new ArrayList<>(); // 編譯時報錯,不能再指向其他對象
list.set(0, 2); // 但是可以修改list里面的值

假如我們就是要求諸如List、Map一類的數(shù)據(jù)結(jié)構(gòu)也不能修改其中的元素呢?

JDK中Collections的一些靜態(tài)方法提供了支持,如下,舉一個List的例子

final List<Integer> list= new ArrayList<>();
list.add(3);
list.add(4);
List<Integer> unmodifiableList = Collections.unmodifiableList(list);
System.out.println(unmodifiableList);
unmodifiableList.add(5); // 運行時異常
unmodifiableList.set(0, 2); // 運行時異常

只需要將普通的list傳給Collections.unmodifiableList()作為入?yún)⒓纯伞?/p>

其實現(xiàn)原理也很簡單,將普通list中的數(shù)據(jù)拷貝,然后對于所有添加、修改的操作,直接拋出異常即可,這樣就保證了list不能修改其中的元素。

線程安全的問題就是出在多個線程同時修改共享變量,不可變對象的策略完全規(guī)避了對對象的修改,所以在多線程中使用也不會有任何問題。

線程封閉

  • 堆棧封閉:能使用局部變量的地方就不使用全局變量,多線程下訪問同一個方法時,方法中的局部變量都會拷貝一份到線程的棧中,也就是說每一個線程中都有只屬于本線程的私有變量,因此局部變量不會被多個線程共享。
  • ThreadLocal:特別好的線程封閉方法,其實現(xiàn)原理如下

對于共享變量,一般采取同步的方式保證線程安全。而ThreadLocal是為每一個線程都提供了一個線程內(nèi)的局部變量,每個線程只能訪問到屬于它的副本。

下面是set和get的實現(xiàn)

// set方法
public void set(T value) {
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null)
        map.set(this, value);
    else
        createMap(t, value);
}

// 上面的getMap方法
ThreadLocalMap getMap(Thread t) {
    return t.threadLocals;
}

// get方法
public T get() {
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null) {
        ThreadLocalMap.Entry e = map.getEntry(this);
        if (e != null) {
            @SuppressWarnings("unchecked")
            T result = (T)e.value;
            return result;
        }
    }
    return setInitialValue();
}

從源碼中可以看出:每一個線程擁有一個ThreadLocalMap,這個map存儲了該線程擁有的所有局部變量。

set時先通過Thread.currentThread()獲取當(dāng)前線程,進而獲取到當(dāng)前線程的ThreadLocalMap,然后以ThreadLocal自己為key,要存儲的對象為值,存到當(dāng)前線程的ThreadLocalMap中。

get時也是先獲得當(dāng)前線程的ThreadLocalMap,以ThreadLocal自己為key,取出和該線程的局部變量。

題話外,一個線程內(nèi)可以設(shè)置多個ThreadLocal,這樣該線程就擁有了多個局部變量。比如當(dāng)前線程為t1,在t1內(nèi)創(chuàng)建了兩個ThreadLocal分別是tl1和tl2,那么t1的ThreadLocalMap就有兩個鍵值對。

t1.threadLocals.set(tl1, obj1) // 等價于在t1線程中調(diào)用tl1.set(obj1)
t1.threadLocals.set(tl2, obj2) // 等價于在t1線程中調(diào)用tl2.set(obj1)

t1.threadLocals.getEntry(tl1) // 等價于在t1線程中調(diào)用tl1.get()獲得obj1
t1.threadLocals.getEntry(tl2) // 等價于在t1線程中調(diào)用tl2.get()獲得obj2

以一個角色驗證的例子為例,為每一個請求(線程)保存了當(dāng)前訪問人的角色。比如有g(shù)uest和admin。

public class CurrentUserHolder {
    private static final ThreadLocal<String> holder = new ThreadLocal<>();

    public static void setUserHolder(String user) {
        holder.set(user);
    }

    public static String getUserHolder() {
        return holder.get();
    }
}

在進行某些敏感操作前,需要對當(dāng)前請求下的角色進行驗證。游客是沒有訪問權(quán)限的,只有管理員可以。

import org.springframework.stereotype.Component;
import org.springframework.stereotype.Service;


@Component
public class AuthService {
    public void checkAccess() {
        // 通過ThreadLocal取得當(dāng)前線程(請求)中的角色
        String user = CurrentUserHolder.getUserHolder();
        if (!"admin".equals(user)) {
            throw new RuntimeException("操作不被允許!");
        }
    }
}

操作前的驗證使用AOP過濾

import com.shy.aopdemo.security.AuthService;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

@Aspect
@Component
public class AuthAspect {
    @Autowired
    private AuthService authService;
    @Pointcut("execution(* com.shy.aopdemo.service.ProductService.*(..))")
    public void adminOnly() {}

    @Before("adminOnly()")
    public void checkAccess() {
        authService.checkAccess();
    }
}

測試一下,如果當(dāng)前請求的角色是guest

import com.shy.aopdemo.security.CurrentUserHolder;
import com.shy.aopdemo.service.ProductService;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

@RunWith(SpringRunner.class)
@SpringBootTest
public class AopdemoApplicationTests {
    @Autowired
    private ProductService productService;
    @Test
    public void checkDeleteTest() {
        // 當(dāng)前請求的角色是guest
        CurrentUserHolder.setUserHolder("guest");
        // AOP,在delete之前會先調(diào)用authService.checkAccess();結(jié)果驗證不通過
        productService.delete(1L);
    }
}

總結(jié)

安全共享對象的策略

  • 線程限制:一個被線程限制的對象,由線程獨占;只能由它的線程來修改,例如使用線程內(nèi)的局部變量、ThreadLocal等
  • 共享只讀:只讀對象在沒有額外同步的情況下,可以被多個線程并發(fā)訪問,但是任何線程都無法修改它。例如,使用不可變對象(final關(guān)鍵字修飾)
  • 線程安全的對象:一個線程按安全的對象或容器,通過內(nèi)部的同步機制來保證線程安全。比如StringBuffer、ConcurrentHashMap、AtomicInteger等線程安全對象。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容