Java高併發編程實戰(一)

線程池

線程和進程的區別是什麼?

線程的生命週期

線程池的組成部分

  • 任務隊列
  • 拒絕策略(拋出異常,直接丟棄,阻塞,臨時隊列)
  • 初始大小 init(最少線程個數) min
  • 活躍大小 active
  • 最大線程個數 max

關係:min<=active<=max

自定義線程池


設計模式引出的多線程問題

  • 餓漢式單例設計
public class SingletonObject1 {
    /**
     * can't lazy load
     */
    private static final SingletonObject1 instance = new SingletonObject1();

    private SingletonObject1() {
    }
    public static SingletonObject1 getInstance(){
        return instance;
    }
}
  • 懶加載單例設計模式
public class SingletonObject2 {
    private static SingletonObject2 instance;

    private SingletonObject2() {
    }

    public static SingletonObject2 getInstance() {
        if (instance == null) {
            instance = new SingletonObject2();
        }
        return instance;
    }
}
  • 同步的懶加載單例設計模式
public class SingletonObject3 {
    private static SingletonObject3 instance;

    private SingletonObject3() {
    }

    public synchronized static SingletonObject3 getInstance() {
        if (instance == null) {
            instance = new SingletonObject3();
        }
        return instance;
    }
}
  • DCL(Double Check Lock)
- public class SingletonObject4 {
    private static SingletonObject4 instance;

    private SingletonObject4() {
    }

    public static SingletonObject4 getInstance() {
        if (instance == null) {
            synchronized (SingletonObject4.class){
                if (instance == null){
                    instance = new SingletonObject4();
                }
            }
        }
        return instance;
    }
}

會出現空指針異常的問題,因爲雖然成功的保證了單例原則,當其餘的線程返回這個對象的時候,該類中的實例域可能還沒有初始化完成

  • volatile - DCL
public class SingletonObject5 {
    private static volatile SingletonObject5 instance;

    private SingletonObject5() {
    }

    public static SingletonObject5 getInstance() {
        if (instance == null) {
            synchronized (SingletonObject5.class){
                if (instance == null){
                    instance = new SingletonObject5();
                }
            }
        }
        return instance;
    }
}
  • 靜態內部類-單例
public class SingletonObject6 {
    private static volatile SingletonObject6 instance;

    private SingletonObject6() {
    }

    private static class InstanceHolder {
        private static final SingletonObject6 instance = new SingletonObject6();
    }

    public static SingletonObject6 getInstance() {
        return InstanceHolder.instance;
    }
}
  • 枚舉-單例
   public class SingletonObject7 {
    private SingletonObject7() {
    }

    private enum Singleton {
        INSTANCE;

        private final SingletonObject7 instance;

        Singleton() {
            instance = new SingletonObject7();
        }

        public SingletonObject7 getInstance() {
            return instance;
        }
    }

    public static SingletonObject7 getInstance() {
        return Singleton.INSTANCE.getInstance();
    }

    public static void main(String[] args) {
        Thread[] thread = new Thread[10];
        for (int i = 0; i < 10; i++) {
            thread[i] = new Thread(() -> {
                System.out.println(SingletonObject7.getInstance());
            });
            thread[i].start();
        }
    }
}

wait set

public class WaitSet {
    private static final Object LOCK = new Object();

    /**
     * 1、所有的對象都會有一個wait set,用來存放調用了該對象wait方法之後進入BLOCK狀態的線程
     * 2、線程被notify之後,不一定立即得到執行
     * 3、線程從wait set中被喚醒順序不一定是 FIFO
     * @param args
     */
    public static void main(String[] args) throws InterruptedException {
        IntStream.rangeClosed(1, 10).forEach(i -> new Thread(String.valueOf(i)) {

            @Override
            public void run() {
                synchronized (LOCK) {
                    try {
                        Optional.of(Thread.currentThread().getName() + " will come to wait set.").ifPresent(System.out::println);
                        LOCK.wait();
                        Optional.of(Thread.currentThread().getName() + " will leave to wait set.").ifPresent(System.out::println);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }.start());

        Thread.sleep(3000);

        IntStream.rangeClosed(1, 10).forEach(i -> {
            synchronized (LOCK) {
                LOCK.notify();
            }
        });
    }
}

併發編程中三個比較重要的概念

  • 原子性 Atom
    保證一個操作或者多個操作要麼都成功,要麼都失敗,中間不能由於任何的因素中斷

  • 可見性

  • 有序性(順序性)
    重排序只要求最終一致性
    在這裏插入圖片描述

JMM怎麼保證原子性

對基本數據類型的變量讀取和賦值是保證了原子性的(long,double在32位的操作系統時可能不能保證原子性)

a =10 ->原子性
b=a->不滿足:1.read a; 2.assign b;
c++,c=c+1;->不滿足:1.read c; 2.add; 3.assign to c;

JMM怎麼保證可見性

使用volatile關鍵字保證可見性

JMM怎麼保證有序性

happens-before relationship

  • 代碼的執行順序,編寫在前面的發生在編寫在後面的(多線程不能保證->指令重排序)
  • unlock必鬚髮生在lock之後
try{
	lock.lock();
} catch(Exception e){
	lock.u nlock();	
}
synchronized(obj){ ---1---
}

--unlock--2---
12之前
  • volatile修飾的變量,對一個變量的寫操作先行發生於對這個變量的讀操作
  • 傳遞規則,操作A先於B,B先於C,那麼A肯定先於C
  • 線程啓動規則,start方法肯定先於線程run()
  • 線程中斷規則,interrupt這個動作,必鬚髮生在捕獲該動作之後
  • 對象銷燬規則,初始化必鬚髮生在finalize之前
  • 線程終結規則,所有操作必鬚髮生在線程死亡之前

volatile關鍵字

  • 一旦一個共享變量被volatile修飾,具備兩層語義:
    1、保證了不同線程間的可見性(線程不經過本地內存,都從主內存中讀寫值)
    2、禁止對其進行重排序,也就是保證了有序性

3、並未保證原子性

public class VolatileTest2 {
    private static int INIT_VALUE = 0;

    private final static int MAX_LIMIT = 50;

    public static void main(String[] args) {
        new Thread(() -> {
            while (INIT_VALUE < MAX_LIMIT) {
                System.out.println("T1->" + (++INIT_VALUE));
                try {
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "ADDER-1").start();

        new Thread(() -> {
            while (INIT_VALUE < MAX_LIMIT) {
                System.out.println("T2->" + (++INIT_VALUE));
                try {
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "ADDER-2").start();
    }
}

Volatile關鍵字
1.保證重排序的不會把後面的之類放到屏障的前面,也不會把前面的放到後面
2.強制對緩存的修改操作立刻寫出主內存
3.如果是寫操作,它會導致其他CPU中的緩存失效

Volatile的使用場景

  • 狀態欄標記
  • 屏障前後的一致性
  • 緩存一致性協議
  • 指令重排序
  • happen-before規則
  • 併發編程的三要素
  • volatile關鍵字的作用

不可變對象

  • 不可變對象一定是線程安全的:裏面的任何屬性或者引用類型的屬性都不能被修改
  • 可變對象可能是安全的:StringBuffer

Future 設計模式

  • Future -> 代表的是未來的一個憑據
  • FutureTask -> 將你的調用邏輯進行了隔離
  • FutureService -> 橋接Future和FutureTask

ThreadLocal

用於保存線程私有的數據,不同線程之間看不到彼此的ThreadLocal中保存的數據,始終以當前線程(Thread.currentThread())作爲key

  • ThreadLocal沒放值進去時get出來爲null
    ThreadLocal賦初值,重寫ThreadLocal的initialValue()方法;

模擬ThreadLocal的工作原理

public class ThreadLocalSimulator<T> {

    private final Map<Thread, T> storage = new HashMap<>();

    public void set(T t) {
        synchronized (this) {
            Thread key = Thread.currentThread();
            storage.put(key, t);
        }
    }

    public T get() {
        Thread key = Thread.currentThread();
        T value = storage.get(key);
        return null == value ? initialValue() : value;
    }

    private T initialValue() {
        return null;
    }
}

ThreadLocal應用-上下文設計模式

public class Context {
    private String name;

    protected String cardId;

    public void setName(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

    public void setCardId(String cardId) {
        this.cardId = cardId;
    }

    public String getCardId() {
        return cardId;
    }
}


public class ActionContext {
    private static final ThreadLocal<Context> threadLocal = ThreadLocal.withInitial(Context::new);

    private static class InnerActionContext {
        private static ActionContext actionContext = new ActionContext();
    }

    public static ActionContext getActionContext() {
        return InnerActionContext.actionContext;
    }

    public Context getContext() {
        return threadLocal.get();
    }
}

public class ExecutionTask implements Runnable {

    private QueryFromDBAction queryAction = new QueryFromDBAction();

    private QueryFromHttpAction httpAction = new QueryFromHttpAction();

    @Override
    public void run() {
        queryAction.execute();
        httpAction.execute();

        Context context = ActionContext.getActionContext().getContext();
        System.out.println("The Name is " + context.getName() + " and CardId is " + context.getCardId());
    }
}

public class QueryFromDBAction {

    public void execute() {
        try {
            Thread.sleep(1000L);
            String name = "Alex" + Thread.currentThread().getName();
            ActionContext.getActionContext().getContext().setName(name);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

public class QueryFromHttpAction {
    public void execute() {
        Context context = ActionContext.getActionContext().getContext();
        String cardId = getCardId(context.getName());
        context.setCardId(cardId);
    }

    private String getCardId(String name) {
        try {
            Thread.sleep(1000L);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return "23456789" + name;
    }
}

public class ContextTest {
    public static void main(String[] args) {
        IntStream.range(0, 100).forEach(i -> {
            new Thread(new ExecutionTask()).start();
        });
    }
}

生產者-消費者設計模式

Count-Down設計模式

自定義ClassLoader

  • 類加載器的委託是優先給 父類加載器先去嘗試加載
  • 父加載器和子加載器其實是一種包裝關係或包含關係

總結:
在這裏插入圖片描述
定義類加載:加載類的加載器
初始類加載器:委託過程中所設計的類加載器
打破雙親委託機制,重寫loadClass方法,改變類加載的順序

import java.io.*;

/**
 * @author LiSheng
 * @date 2020/7/1 12:35
 */
public class MyClassLoader extends ClassLoader {
    private final static String DEFAULT_DIR = "F:\\app\\classloader1";

    private String dir = DEFAULT_DIR;

    private String classLoaderName;

    public MyClassLoader() {
        super();
    }

    public MyClassLoader(String classLoaderName) {
        this.classLoaderName = classLoaderName;
    }

    public MyClassLoader(String classLoaderName, ClassLoader parent) {
        super(parent);
        this.classLoaderName = classLoaderName;
    }

    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        String classPath = name.replace(".", "/");
        File classFile = new File(dir, classPath + ".class");
        if (!classFile.exists()) {
            throw new ClassNotFoundException("The class " + name + " not found.");
        }

        byte[] classBytes = loadClassBytes(classFile);
        if (null == classBytes || classBytes.length == 0) {
            throw new ClassNotFoundException("load the class " + name + "failed");
        }
        return this.defineClass(name, classBytes, 0, classBytes.length);
    }

    //破壞雙親委託機制,重寫loadclass方法,改變類加載的順序
    @Override
    protected Class<?> loadClass(String name, boolean resolve)
            throws ClassNotFoundException {
        Class<?> clazz = null;

        if (name.startsWith("java.")) {
            try {
                ClassLoader system = ClassLoader.getSystemClassLoader();
                clazz = system.loadClass(name);
                if (clazz != null) {
                    if (resolve) {
                        resolveClass(clazz);
                        return clazz;
                    }
                }
            } catch (Exception e) {
                //ignore
            }
        }

        try {
            clazz = findClass(name);
        } catch (Exception e) {

        }
        if (clazz == null && getParent() != null) {
            getParent().loadClass(name);
        }
        return clazz;
    }


    private byte[] loadClassBytes(File classFile) {
        try (ByteArrayOutputStream baos = new ByteArrayOutputStream();
             FileInputStream fis = new FileInputStream(classFile)) {
            byte[] buffer = new byte[1024];
            int len = 0;
            while ((len = fis.read(buffer)) != -1) {
                baos.write(buffer, 0, len);
            }
            baos.flush();
            return baos.toByteArray();
        } catch (IOException e) {
            e.printStackTrace();
            return null;
        }
    }

    public static String getDefaultDir() {
        return DEFAULT_DIR;
    }

    public String getDir() {
        return dir;
    }

    public String getClassLoaderName() {
        return classLoaderName;
    }

    public void setDir(String dir) {
        this.dir = dir;
    }

    public void setClassLoaderName(String classLoaderName) {
        this.classLoaderName = classLoaderName;
    }
}

類加載器的命名空間

  • 每個類的加載器都有子的命名空間,命名空間由該加載器及其父加載器所加載的類組成
  • 在同一個命名空間中,不會出現完整的名字

運行時包

  • 父加載器看不到子加載器加載的類
  • 不同命名空間下的類加載器之間的類互相不可訪問

類的卸載以及classLoader的卸載

  • 該類的所有實例已經被GC
  • 加載該類的ClassLoader實例已經被GC
  • 該類的java.lang.Class對象沒有在任何地方被引用
  • GC的時機我們是不可控的,那麼同樣的我們對於Class的卸載也是不可控的
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章