多線程學習筆記:
synchronized關鍵字:既能夠保證原子性也能夠保證可見性。 被synchronized關鍵字修飾的對象會被加上一個互斥鎖。
synchronized是一個非公平鎖,如果多個線程處於等待狀態,那麼這些線程是隨機獲取鎖的。
什麼是互斥鎖:同一時間只能被一個線程持有(注意:只有加了synchronized關鍵字同時會使用到這個對象的線程會呈現等待狀態)
synchronized鎖定一個靜態對象時是直接鎖定該對象的CLASS對象
synchronized所獲得的鎖是可以重入的:同一個class對象裏面多個方法可以重複獲得一個鎖。
子類的同步方法可以調用父類的同步方法,允許這樣。
線程出現異常的時候會直接釋放鎖,這時候在多線程情況下可能會讀出錯誤數據
volatile關鍵字:線程之間可見性,禁止指jmm對計算機指令的重排序
一般來說線程讀取某個對象都是將其放在自己的緩衝區,待對該對象操作完畢之後會將緩衝區更新後的數據寫入堆裏。
如果是加了volatile關鍵字,當某個線程對該對象做了修改之後會提示其他線程,從而更新其他線程緩衝區引用對象的值。
volatile關鍵字的效率比synchronized要高,volatile關鍵字是無鎖同步的方式,但是不能夠保證原子性(數據一致性)
atoMic類裏面能夠保證原子性,但是隻能單一使用atoMic裏面的方法是才具有原子性。多個方法使用時可能會出現問題。
synchronized: 細粒度的鎖效率比粗粒度的鎖效率高
new object() 是新建一個對象,所以不會存在鎖被佔用的問題。最好不要使用string 常量做synchronized的對象
wait() 將加鎖的對象的當前使用線程置爲等待狀態,同時釋放鎖,此時其他線程可以調用此對象。喚醒線程的方法是notify和notifyall
注意: notify 和 notifyAll 不會釋放鎖
注意:notify 是喚醒沉睡的線程和對象無關。 wait是使當前線程沉睡,!!!!。雖然都是對象的方法,但一定注意的是它操作的是線程!!!
countdownlunch:門閂。不需要加鎖也可實現沉睡和喚醒機制。
latch.await() 等待
latch.countdownl() 喚醒
reentratlock:手動鎖可重入鎖,使用完畢後必須手動釋放。
// 獲取reentratLock 實例
Lock lock = new reentratlock();
// 嘗試獲取鎖
lock.tryLock(5,TimeUnit.SECCOND); 嘗試獲取鎖(指定時間,指定時間單位)
// 設置線程可以被打斷 , 設置了這個屬性,主線程可以對該線程進行interrupt()打斷
lock.lockInterruptibly();
// reentratLock可以設置公平鎖,這樣能夠保證等待時間最久的線程獲取線程
例子:
public class ReentratDemo extends Thread{
// 設置公平鎖
private Lock lock = new ReentrantLock(true);
public void run(){
for (int i = 0; i < 100;i++){
try {
// 加上可重入鎖
lock.lock();
System.out.println(Thread.currentThread().getName() + "獲得鎖");
}catch (Exception e){
System.out.println(e);
}finally {
// 釋放鎖:注意 reentratLock必須設置手動釋放鎖
lock.unlock();
}
}
}
public static void main(String[] args) {
ReentratDemo r = new ReentratDemo();
Thread t1 = new Thread(r);
Thread t2 = new Thread(r);
t1.start();
t2.start();
}
}
object.wait()方法通常是個while一起使用的;
while(size == max){ if(size == max){
try{ try{
this.wait() this.wait()
}catch(InterruntException e){ }catch(InterruntException e){
sout(e); sout(e);
} }
} }
forEach() forEach()
這裏如果線程是被重新喚醒,還是會繼續執行while中的判斷 這裏線程如果被喚醒,會直接執行wait()後面的代碼,而不會再次去判斷size == max,容易出現錯誤
synchronized 鎖對象,可重入的互斥鎖。拋出異常的時候會自動釋放鎖。
lock:reentratLock: 手動釋放的可重入互斥鎖。tryLck可以嘗試獲取鎖,lockInterruntaby 設置可以被打斷 new 的時候可以指定reentratLock爲公平鎖
wait() 被鎖住的對象釋放鎖,並且使當前線程處於等待狀態
notify/notifyAll 喚醒等待線程/喚醒等待全部的線程
Condotion condition = reenstratLock.newCondition(); condition可以精確地指定某些或者說某一個線程的沉睡和喚醒。
threadLocal:每一個線程設置的對象只能夠提供給自己的線程使用。線程之間不恭喜。
session裏面的設置就使用了threadLocal
單例模式(singleton):無論如何,線程池裏面只有一個對象。分爲懶漢模式(用到時才new),和餓漢模式(直接new)
concurrent包下的類:
concurrentQueue: 線程安全隊列 add() 如果超過容器容量的最大值會拋出異常。
offer() 也是往容器裏面加,有返回值boolean類型的,達到最大值的時候它便不能夠往裏面加了。
concurrentHashMap 和 HashTable 效率問題:concurrentHashMap效率更高。hashTable是鎖住整個容器
concurrentHashMap1.7 鎖的將容器分成一塊一塊的segament中的其中一個segament稱之爲分段鎖。
concurrentHashMap1.8 鎖的是其中的一個node,採用cas算法來插入或者修改數據。
在你put數據進入concurrentHashMap時,會使用cas算法在內存中計算出一個hash值,如果和你put的對象計算出的hash值一致,則允許寫入,保證了原子性。
concurrentSkipListMap :線程安全的有序的map
copyOnWriteList() 寫的時候會複製,寫的少讀得多的情況下使用
linkedBlockingQueue: 同步阻塞容器。 put() 當容器滿了之後,線程會自動等待。 無界容器。
ArrayBlockingQueue:同步阻塞隊列 。有界隊列,默認有大小,自己也可以設置大小。
delayQueue:可以指定時間,等待時間長的最先執行。
transferQueue: 提供者直接將消息發送給對應的消費者,這樣能夠提高效率。但是隻有transfer()方法會使消息阻塞。
runnable():運行的是run()方法,沒有返回值、不能夠拋出異常。
callable():運行的是call()方法,有返回值,可以拋出異常。
executors工具類可以創建線程池和task
executorService.submit() 只是將這些任務(task)放入線程池中,但還未真正的運行
// 創建線程池
ExecuteService service = Executors.newXXXthreadPool();
// 將創建好的的任務添加到線程池中,得到一個future類
Future future = service.submit(callable or implements);
// future.get() 執行指定線程
future.get()
線程池demo:
/**
* 計算1 - 200000 的質數
*/
public class Executor {
static int myCoreCpu = 4;
/**
* 自定義任務
*/
static class myTask implements Callable{
int begin;
int end;
myTask(int begin,int end){
this.begin = begin;
this.end = end;
}
@Override
public List<Integer> call() throws Exception {
return getPrise(begin,end);
}
}
static List<Integer> getPrise(int from,int end){
List<Integer> priseList = new ArrayList<>();
for(int i = from ; i <= end ;i ++){
boolean prise = isPrise(i);
if(prise) priseList.add(i);
}
return priseList;
}
static boolean isPrise(int num){
for(int i = 2; i < num/2; i++){
if(num % i == 0){
return false;
}
}
return true;
}
public static void main(String[] args) throws ExecutionException,InterruptedException{
List<Integer> l1 = new ArrayList<>();
List<Integer> l2 = new ArrayList<>();
/**
* 不使用線程池做任務調度
*/
long time = new Date().getTime();
for(int i = 1;i <= 200000;i++){
boolean prise = isPrise(i);
if (prise) l1.add(i);
}
long end = new Date().getTime();
System.out.println("數組大小:" + l1.size() + "-----用時:" + (end - time));
/**
* 線程池實現
*/
ExecutorService service = Executors.newFixedThreadPool(myCoreCpu);
myTask m1 = new myTask(1,80000);
myTask m2 = new myTask(80001,130000);
myTask m3 = new myTask(130001,170000);
myTask m4 = new myTask(170001,200000);
/**
* 將任務提交到線程池
*/
Future f1 = service.submit(m1);
Future f2 = service.submit(m2);
Future f3 = service.submit(m3);
Future f4 = service.submit(m4);
long startTime = new Date().getTime();
/**
* 啓動線程
*/
l2.addAll ((List<Integer>)f1.get());
l2.addAll ((List<Integer>)f2.get());
l2.addAll ((List<Integer>)f3.get());
l2.addAll ((List<Integer>)f4.get());
long endTime = new Date().getTime();
System.out.println("數組大小:" + l2.size() + "------用時:" + (endTime - startTime));
}
/**
* 關閉service
*/
service.shutdown();
}
ExecutorService.submit --> Future --> Future.get();
catchThredPool --> 可以設置線程存活時間aliveTime:默認爲60s
Executors工具類裏面有很多種類型的threadPool,分別適用於不同的場景,詳細的需要看api。
Executors的創建的線程池的底層是 return new ThreadPoolExecutor(int corePoolSise,
int maxPoolsize,long keepAliveTime,TimeUnit unit,WorkQueue linkedBlockingQueue ThreadFactory tf)
根據裏面參數的值可以分爲不同類型的線程池:
1.指定核心線程數線程池FixedThread:core = max = 指定參數,keepAliveTime = 0
特點:使用shoutDown()方法可以線程執行完畢之後自動關閉線程池。
public static ExecutorService newFixedThreadPool(int nThreads, ThreadFactory threadFactory) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>(),
threadFactory);
}
2.單例線程數singletonThreadPool:core = max = 1,keepAliveTime = 0;
特點:線程池中只能有一條線程在執行,其他的線程在workQueue裏面等待,能保證線程執行的順序。
public static ExecutorService newSingleThreadExecutor(ThreadFactory threadFactory) {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>(),
threadFactory));
}
3.緩存線程池,cacheThreadPool: core = 0,max = integer.maxValue(),keepAliveTime = 60L(默認,可自己設定),TimeUnit.SECCOND ,workQueue = SynchronousQueue
SynchronousQueue 是線程安全的queue不經過線程池,直接被調用,如果沒人調用就一直處於阻塞狀態
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
4.定時線程:ScheduledThreadPoolExecutor 第三個參數爲開啓時間,workQueue = delayedWorkQueue;(優先級隊列,先進先出)
public ScheduledThreadPoolExecutor(int corePoolSize) {
super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
new DelayedWorkQueue());
}
mybatis爲什麼我們在dao層使用interface就可以用
動態代理cglib 和 jdk動態代理
mysqld.exe –install MySql –defaults-file='C:\Program Files\MySQL\mysql5.7\my-default.ini'