new Thread()
//或者
new Thread(new Runnable())
複製代碼
以後用start()來啓動線程。跟代碼會發現start()會執行start0()這個native方法,虛擬機調用run方法。有Runnable就會調用傳入的runnable的run()實現,不然就會執行Thread中的run()方法。java
ThreadFactory factory=new ThreadFactory(){
@Override
public Thread newThread(Runnable r){
return new Thread(r);
}
}
Runnable runnable =new Runnable(){
//```
}
Thread thread=factory.newThread(runnable);
thread.start();
Thread thread1=factory.newThread(runnable);
thread1.start();
複製代碼
Runnable runnable =new Runnable(){
@Override
public void run(){
//```
}
}
Executor executor=Executors.newCachedThreadPool();
executor.executor(runnable);
executor.executor(runnable);
(ExecutorService)executor.shutdown();
複製代碼
至少對於我,看起來是不多用,那爲何說最經常使用呢?
AsyncTask,Cursor,Rxjava其實也是使用Executor進行線程操做的。數據庫
能夠來看一下Executor,只是一個接口來經過execute()來指定線程工做緩存
public interface Executor {
void execute(Runnable command);
}
複製代碼
對於Executor的擴展:ExecutorServive/Executors Executors.newCachedThreadPool()返回的又是一個ExecutorSevice extends Executor,對Executor作了一些擴展,主要關注的是shutdown()(保守型的結束)/shotdownNow()(當即結束),還有Future相關的submit(),這些後面會說。
newCachedThreadPool() 建立了一個帶緩存的線程池,自動的進行線程的建立,緩存,回收操做。
還有幾個別的方法:
newSingleThreadExecutor() 單一線程,用途較少。
newFixedThreadPool() 固定大小的線程池。 好比須要建立批量任務。 newScheduledThreadPool() 指定時間表,作個延時或者指定時間執行。
若是你想自定義線程池的時候就能夠參考這幾個方法在app初始化的時候直接new ThreadPoolExecutor了。 線程完成後結束:就能夠直接添加任務後執行shutdown()。關於線程的結束,寫在了後面的從及基本的啓動/結束開始。安全
ThreadPoolExecutor()的幾個參數bash
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(
0, Integer.MAX_VALUE,60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
public ThreadPoolExecutor( int corePoolSize,//初始線程池的大小/建立線程執行結束後回收到多少線程後再也不回收 int maximumPoolSize,//線程上線 long keepAliveTime,//保持線程不被回收的等待時間 TimeUnit unit, BlockingQueue<Runnable> workQueue,//阻塞隊列 ThreadFactory threadFactory, RejectedExecutionHandler handler) {
//```
}
複製代碼
寫到這裏的時候,maximumPoolSize這個參數,想起以前在本身寫一些圖片加載、緩存的時候,開的線程老是會用CPU核心數來限制一下,好比2*CPU_CORE,之前不懂,會以爲大概是每一個核分一個? 如今學到的:首先確定不是爲了一個核心來一個線程,畢竟一個cpu跑N多個線程,哪能就那麼剛恰好一個核心一個。
大概是能夠保證代碼在不一樣的機器上的CPU調度積極性差很少,好比單核的,就建立兩個線程,8核心的,就是16個線程。否則寫8個線程,單核心機器運行可能就會比較卡,8核心機器運行又會太少。網絡
Callable callable = new Callable<String>() {
@Override
public String call() throws Exception {
try {
Thread.sleep(1500);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "找瓶子";
}
};
ExecutorService executor = Executors.newCachedThreadPool();
Future<String> future = executor.submit(callable);
try {
String result = future.get();
} catch (ExecutionException | InterruptedException e) {
e.printStackTrace();
}
複製代碼
是否是看起來也不是很麻煩?甚至還有一丟丟好用? 可是這個Future會阻塞線程,若是在主線程中使用的話,就須要不停地來查看後臺是否執行結束多線程
while (true) {
if (future.isDone()){
try {
String result = future.get();
} catch (ExecutionException | InterruptedException e) {
e.printStackTrace();
}
try{
Thread.sleep(1000);//模擬主線程的任務,過一秒來看一看
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
複製代碼
ok,線程的常見使用方式就基本上說完了。使用的時候咱們會常常遇到多個線程操做同一個資源的問題,好比A給B轉帳的同時,B,C又同時給A轉帳,那麼應該怎麼處理呢?這時候就要說到第二節的線程安全問題了。app
操做系統對於cpu使用的時間片機制,致使某段代碼某個線程在執行中會被暫停,其餘線程繼續執行同一段代碼/操做同一個數據(資源)的時候,可能帶來的數據錯誤。
eg:ide
class Test{
private int x=0;
private int y=0;
private void ifEquals(int val){
x=val;
y=val if(x!=y){
System.out.println("x: "+x+",y: "+y);
}
}
public void testThread(){
new Thread(){
public void run(){
for(int i=0;i<1_000_000_000;i++){
ifEquals(i);
}
}
}.start();
new Thread(){
public void run(){
for(int i=0;i<1_000_000_000;i++){
ifEquals(i);
}
}
}.start();
}
}
複製代碼
這還能不相等?
產生的緣由其實簡單,上面也簡述過。就是在執行testThread的時候,兩個線程同時操做ifEquals方法:性能
- 線程1在操做到i=10,當前x=val=10時候被切換線程,此時y=val還未被線程1進行賦值
- 線程2進行執行代碼,當進行到x=100,y=100,而後線程再次切換
- 線程1執行y=10,此時x的值已是線程2修改過的100了,就會致使x!=y。
那麼知道了緣由,解決的思路就變得簡單,x,y這兩步操做應該是變成一步操做便可。或者說一次ifEquals方法變成一步操做。因此JAVA提供了synchronized關鍵字,把這個關鍵字加在ifEquals這個方法,就使得其變成原子操做。 會對這個方法添加一個監視器Monitor這樣在線程1未執行完畢的時候,monitor不會被釋放,即便線程切換,線程2訪問到這個方法的時候,因爲monitor未被釋放就會進入排隊等待,不會執行這個方法。
關於Synchronized關鍵字:如今給ifEquals增長Synchronized關鍵之後,上述代碼再增長下面這個delVal()方法在線程中調用,x,y的值會出現問題嗎?依然會。
因此,看起來是對於方法的保護,其實是對資源的保護,好比上面的例子,咱們但願的其實並非保護ifEquals方法,而是x,y的資源。
private void delVal(int val){
x=val-1;
y=val-1;
}
String name;
private Synchronized void setName(String val){
name=val;
}
複製代碼
另外一個問題就是在保護x/y的時候,同時也須要保護name的時候,如上對於兩個方法都加上Synchronized的時候也不方便,此時會致使當一個線程只是訪問到ifEquals方法的時候,另外一個線程不能訪問setName。這就與咱們一個線程操做x、y,另外一個線程同步操做name的預期不符。緣由是對方法添加synchronized會把整個Test類對象當作Monitor進行監視,也就是這些方法都會被同一個monitor進行監視。
那爲何兩個方法操做的不是同一個資源,還會被保護呢?由於monitor不會對方法進行檢查,實際上咱們synchronize方法的緣由也不是爲了讓方法不能被另外一個線程調用,而是爲了保護資源。這時候,synchronize方法就不符合咱們多線程操做的預期,就須要咱們本身手動來進行操做。因此就須要引入Synchronized代碼塊。
final Object numMonitor=new Object();
final Object nameMonitor=new Object();
//代碼塊
private void ifEquals(int val){
synchronize(this){
x=val;
y=val if(x!=y){
System.out.println("x: "+x+",y: "+y);
}
}
}
//代碼塊
private void delVal(int val){
synchronize(this){
x=val-1;
y=val-1;
}
}
//指定monitor
private Synchronized void setName(String val){
synchronize(nameMonitor){
name=val;
}
}
public void testThread(){
//``````
}
複製代碼
synchronize(Object object),容許你指定object做爲monitor來進行監視,好比咱們能夠把上面的this,換成numMonitor,這個時候,name和x、y就是兩個monitor進行,互不影響了。
synchronize的另外一個做用:線程之間對於監視資源的數據同步
1.給方法加,默認的monitor就是當前的Test.Class,這個類,不是這個對象 2.代碼塊內部沒法使用synchronize(this),應爲這是靜態方法並不存在這個this。可使用synchronize(Test.Class)
private volatile int a;
AtomicInteger a=new ActomicInteger(0);
a.getAndIncrement();
複製代碼
先解釋下java虛擬機下面的數據操做:好比ifEquals這個方法,x、y讀到內存中,並非cpu直接操做內存中的數據,而是由cpu單獨給線程一個存儲空間進行操做。咱們都知道,如今用的內存條速度,比起cpu的操做速度有着極大的速度差,就像硬盤和內存的巨大速度差同樣,若是代碼都是從硬盤執行,而後操做數據再寫回硬盤,確定沒法忍受。對於cpu也是同樣,ram的讀寫實在是太慢了,這時候就像使用內存來彌補速度差同樣,使用cpu的高速cache,來彌補內存和cpu總線之間的速度差。 基於以上的描述
開始操做的時候,
- x=0,y=0;
- thread1進行:x=5,y=5結束(在線程的cpu cache中),尚未寫入內存的時候,
- thread2進行數據讀取,x=0,y=0;
synchronize關鍵字,就會保證cpu讀取賦值之後,再寫回內存中。來保證數據的正確
複製代碼
可是帶來的結果就是,若是沒有cpu緩存操做,這個x=5,y=5的操做會變得很慢。其實從上面的代碼運行時間也能很明顯的看出區別,testThread()方法的執行時間,在有沒有對方法添加synchronize時會相差很是明顯。因此雖然會帶來線程之間的數據同步問題,當前的cache仍是頗有必要的。
不少時候咱們在說安全,線程安全,網絡安全,數據安全,可是其實這是不同的「安全」:
Safety 保證不會被改錯,改壞的安全,好比Thread Safety
Security 不被侵犯安全 https 的S
複製代碼
死鎖是咱們最常遇到,或者是最常聽到的一種鎖。其實產生的緣由也很簡單,就是互相持有(對方的「鑰匙」)致使互相等待:
private Synchronized void setName(String val){
synchronize(nameMonitor){
name=val;
synchronize(numMonitor){
x=val;
y=val if(x!=y){
System.out.println("x: "+x+",y: "+y);
}
}
}
}
}
private void ifEquals(int val){
synchronize(numMonitor){
x=val;
y=val if(x!=y){
System.out.println("x: "+x+",y: "+y);
synchronize(nameMonitor){
name="haha";
}
}
}
}
複製代碼
Thread1 執行 ifEquals,numMonitor,而後cpu進行線程進行切換到Thread2 執行setName,持有nameMonitor,而後往下執行的時候,發現numMonitor被持有,Thread2進行等待,切換回Thread1,Thread1發現繼續執行,可是nameMonitor被持有,進入等待,這樣兩個線程就變成了互相持有對方須要的monitor(鑰匙)進入互相等待,也就是死鎖。
跟線程安全不是很相關的鎖,更多的是,數據庫相關,並非線程相關。
好比數據庫進行數據修改,須要先取出數據進行操做,再往裏寫,就會出現A操做寫數據,B操做也寫同一個數據,好比小明給我轉帳100,A操做出個人餘額X+100,正要寫入數據庫:餘額X+100,小王給我轉帳1而且先一步寫入了X+1,此時若是A操做繼續寫入餘額X+100就很明顯是錯誤的了。
解決這個問題的方式兩種:
好比Test中,增長一個方法
private Lock lock=new ReentrantLock();
private void reset(){
lock.lock();
//```
lock.unlock();
}
複製代碼
可是若是在中間的方法中,出現了異常,後面的lock.unlock()沒法執行,那就會致使一直被鎖(Monitor遇到異常會自動解開),因此須要手動處理:
private void reset(){
lock.lock();
try{
//```
}finally{
lock.unlock();
}
}
複製代碼
看起來功能跟synchronized差很少?可是這麼麻煩用你幹啥?可是其實想一下,以前說線程同步的時候,都是在寫數據的時候出現問題,單純的讀取數據並不會出現問題,只有在寫入的時候,別人讀寫會致使出現問題,若是線程1讀取數據中切換線程,線程2也不能讀取,就會有性能的浪費,讀寫鎖就能夠解決這個問題:
private ReentrantReadWriteLock lock=new ReentrantReadWriteLock();
private ReentrantReadWriteLock.ReadLock readLock=lock.readLock();
private ReentrantReadWriteLock.WriteLock writeLock=lock.writeLock();
private void ifEquals(int val){
writeLock.lock();
try{
x=val;
y=val;
}finally{
writeLock.unlock()
}
}
private readData(){
readLock.lock();
try{
System.out.println("x: "+x+",y: "+y);
}finally{
readLock.unlock();
}
}
複製代碼
這樣,有線程在調用ifEquals()的時候,別人不能讀也不能寫,readData()的時候,別人能跟我一塊兒讀取數據。就有利於性能的提高。
Thread thread = new Thread() {
@Override
public void run() {
for (int i = 0; i < 1_000_000_000; i++) {// 模擬一段耗時操做
Log.d("========>", "找湯圓");
}
}
};
thread.start();
Thread.sleep(100);
thread.stop();
複製代碼
這樣就在主線程完成了子線程的開始和終止,就基本的交互就是這樣。可是用的時候會發現,stop()。喵喵喵?不是很好用嗎?爲何劃線不建議用了呢?
由於不可控,Thread.stop會直接結束,而無論你內部正在進行什麼操做(實際上主線程也確實不知道子線程在作什麼),這樣就帶來了不可控性。
可是好比我明知道進行A操做之後,後面的線程作的工做已經無心義了,須要節省資源終止線程要怎麼作呢? 用thread.interrupt(),可是這個interrupt只是作了一個標記,若是僅僅使用interrupt是沒有任何做用的,還須要子線程本身根據這個中斷狀態進行操做:
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 1_000_000_000; i++) {
if (isInterrupted()) {//不改變interrupt狀態
// if(Thread.interrupted()) //這個方法會在使用以後重置interrupt的狀態
//作線程結束的收尾工做
return;
}
Log.d("========>", "找湯圓"+i);
}
}
});
thread.start();
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
thread.interrupt();
複製代碼
看到這個interrupt是否是會以爲這個詞在線程操做中有點熟悉?哎(二聲),這不是恰好是上面兩行sleep的時候要catch的InterruptedException麼?爲何我就想讓線程sleep一下,要加入個try/catch呢?
緣由有兩個:
//對於Thread.interrupt()方法的部分註釋
* If this thread is blocked in an invocation of the wait() or join() or sleep(),
* methods of this class, then its interrupt status will be cleared and it
* will receive an {@link InterruptedException}.
複製代碼
因此中斷線程的時候須要考慮一下進行處理:
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 1_000_000_000; i++) {
if (Thread.interrupted()) {//false,100毫秒後纔會變成ture
//進行本身的interrupt處理
return;
}
try {
Thread.sleep(2000);//睡得時候被執行interrupt,會直接喚醒進入中斷異常,
//若是不在下面catch進行處理,interrupt的值又會被重置,致使外部調用的interrupt至關於沒有發生
} catch (InterruptedException e) {
e.printStackTrace();
//收尾工做
return;
}
Log.d("========>", "找湯圓"+i);
}
}
});
thread.start();
try {
Thread.sleep(100);//這裏是主線程睡了,
//跟上面的子線程的sleep不同哦,只是爲了模擬start()和interrupt之間中間有個時間差
} catch (InterruptedException e) {
e.printStackTrace();
}
thread.interrupt();
複製代碼
那因此安卓中我只是想單純的讓線程sleep一下,不須要外部interrupt時候提供支持,不想寫try/catch不行嗎?
SystemClock.sleep(100);//小哥哥,瞭解一下這個
複製代碼
String name=null;
private synchronized void setName(){
name="湯圓";
}
private synchronized String getName(){
return name;
}
private void main(){
new Thread() {
@Override
public void run() {
//一些操做
setName();
}
}.start();
new Thread() {
@Override
public void run() {
//一些操做
getname();
}
}.start();
}
複製代碼
因爲兩個Thread不知道誰先執行完,因此可能出現getName先執行,可是getName獲取空的話又無法進行操做,這時候怎麼辦呢?
private synchronized String getName(){
while(name==null){}//一直乾等着,直到name不爲空
return name;
}
複製代碼
可是這是個synchronized方法又會持有跟setname同樣的monitor,setName也被鎖住了成了死鎖,那怎麼作?
private synchronized String getName(){
while(name==null){//使用wait的標準配套就是while判斷,而不是if,由於wait會被interrupt喚醒
try{
wait();//object方法
}catch(InterruptedException e){
//
e.printStackTrace();
}
}//乾等着,直到不爲空
return name;
}
private synchronized void setName(){
name="湯圓";
notifyAll();//object方法,把monitor上的全部在等待的線程所有喚醒去看一下是否知足執行條件了
}
複製代碼
或者是進入頁面,須要請求多個接口,根據接口的數據來設置頁面設置數據,也是相似的狀況。不過實際上一個Rxjava的zip操做就能解決大多數問題了。 實際上寫了這麼多,這種需求Rxjava的zip操做就解決了...
private void main(){
new Thread() {
@Override
public void run() {
//一些操做
try{
thread0.join();//至關於自動notify的wait
//Thread.yield();
}catch(InterruptedException e){
e.printStackTrace();
}
//一些操做
getname();
}
};
}
複製代碼
感謝&參考:扔物線