線程池
線程的生命週期
線程池的組成部分
- 任務隊列
- 拒絕策略(拋出異常,直接丟棄,阻塞,臨時隊列)
- 初始大小 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---
1在2之前
- 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的卸載也是不可控的