1.建立子類,繼承自Thread並且重寫run方法:
class MyThread extends Thread { @Override public void run() { System.out.println("hello thread"); } } public class Demo1 { public static void main(String[] args) { // 最基本的创建线程的办法. Thread t = new MyThread(); //调用了start方法才是真正的在系统中创建了线程,执行run方法 t.start(); } }
2.建立一個類,實作Runnable介面再建立Runnable是實例傳給Thread
class MyRunnable implements Runnable{ @Override public void run() { System.out.println("hello"); } } public class Demo3 { public static void main(String[] args) { Thread t = new Thread(new MyRunnable()); t.start(); } }
3.匿名內部類別
#建立了一個匿名內部類,繼承自Thread類,同時重寫run方法,再new出匿名內部類的實例
public class Demo4 { public static void main(String[] args) { Thread t = new Thread(){ @Override public void run() { System.out.println("hello"); } }; t.start(); } }
#new的Runnable,針對這個創建的匿名內部類,同時new出的Runnable實例傳給Thread的建構方法
public class Demo5 { public static void main(String[] args) { Thread t = new Thread(new Runnable() { @Override public void run() { System.out.println("hello"); } }); t.start(); } }
4.lambda表達式lambda取代Runnable
public class Demo6 { public static void main(String[] args) { Thread t = new Thread(() ->{ System.out.println("hello"); }); t.start(); } }
1.isDaemon();
是否後台執行緒後台執行緒不影響行程退出,不是後台執行緒會影響行程退出2.isAlive();
是否存活在呼叫start前系統中是沒有對應執行緒的,run方法執行完後執行緒就銷毀了,t物件可能還存在3.isinterrupted();
是否被中斷
run單純的只是一個普通方法描述了任務的內容start則是一個特殊的方法,內部會在系統中建立執行緒
執行緒停下來的關鍵是要讓對應run方法執行完,對於main執行緒來說main方法執行完了才會終止
1.手動設定標誌位元
在執行緒控制這個標誌位元就能影響到這個執行緒結束,但此處多個執行緒共用一片虛擬空間,因此main執行緒修改的isQuit和t執行緒判斷的isQuit是同一個值
public class Demo10 { // 通过这个变量来控制线程是否结束. private static boolean isQuit = false; public static void main(String[] args) { Thread t = new Thread(() -> { while (!isQuit) { System.out.println("hello thread"); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } }); t.start(); // 就可以在 main 线程中通过修改 isQuit 的值, 来影响到线程是否退出 try { Thread.sleep(5000); } catch (InterruptedException e) { e.printStackTrace(); } // main 线程在 5s 之后, 修改 isQuit 的状态. isQuit = true; } }
2.使用Thread中內建的一個標誌位來判定 Thread.interruted()這是一個靜態方法Thread.currentThread().isInterrupted()這是一個實例方法,其中currentThread能夠取得到目前執行緒的實例
public class Demo7 { public static void main(String[] args) { Thread t = new Thread(() -> { while(!Thread.currentThread().isInterrupted()){ System.out.println("hello"); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); // 当触发异常之后, 立即就退出循环~ System.out.println("这是收尾工作"); break; } } }); t.start(); try{ Thread.sleep(5000); }catch (InterruptedException e){ e.printStackTrace(); } // 在主线程中, 调用 interrupt 方法, 来中断这个线程. // t.interrupt 的意思就是让 t 线程被中断!! t.interrupt(); } }
#要注意的是呼叫這個方法t.interrupt()可能會產生兩種情況:
#1)如果t執行緒處在就緒就設定執行緒的標誌位元為true
2 )如果t執行緒處在阻塞狀態(sleep),就會觸發一個InterruptExeception
多個執行緒之間調度順序是不確定的,有時候我們需要控制執行緒之間的順序,執行緒等待就是一種控制執行緒執行順序的手段,此處的執行緒等待只要是控制執行緒結束的先後順序。
哪個執行緒中的join,哪個執行緒就會阻塞等待直到對應的執行緒執行完畢為止。
t.join();
呼叫這個方法的執行緒是main線程,而針對t這個物件呼叫的此時就是讓main等待t。
程式碼執行到join這一行就停下了,讓t先結束然後main繼續。
t.join(10000);
join提供了另一個版本為帶一個參數的,參數為等待時間10s之後join直接回傳不再等待
Thread.currentThread()
#能夠取得目前執行緒的應用,哪個執行緒呼叫的currentThread就取得哪個執行緒的實例對比this如下:
對於這個程式碼來說,透過繼承Thread的方法來創建線程。此時run方法中直接透過this拿到的就是目前Thread的實例
public class Demo4 { public static void main(String[] args) { Thread t = new Thread(){ @Override public void run() { System.out.println(Thread.currentThread().getName()); System.out.println(this.getName()); } }; t.start(); } }
#然而此處this不是指向Thread類型,而是指向Runnable,Runnable只是一個單純的任務沒有name屬性,要拿到執行緒名字只能透過Thread.currentThread()
public class Demo5 { public static void main(String[] args) { Thread t = new Thread(new Runnable() { @Override public void run() { //err //System.out.println(this.getName()); //right System.out.println(Thread.currentThread().getName()); } }); t.start(); } }
針對系統層面:
#java中Thread類別進一步細化:
NEW:把Thread物件建立好了但是還沒有呼叫start
TERMINATED:作業系統中的執行緒已執行完畢銷毀,但是Thread物件還在取得到的狀態
#RUNNABLE:就緒狀態,處在該狀態的執行緒就是在就緒佇列中,隨時可以調度到CPU上
TIME_WAITING:呼叫了sleep就會進入到該狀態,join(超時時間) BLOCKED:當前執行緒在等待鎖導致了阻塞
WAITING:目前執行緒在等待喚醒
定义:操作系统中线程调度是随机的,导致程序的执行可能会出现一些bug。如果因为调度随机性引入了bug线程就是不安全的,反之则是安全的。
解决方法:加锁,给方法直接加上synchronized关键字,此时进入方法就会自动加锁,离开方法就会自动解锁。当一个线程加锁成功的时候,其他线程尝试加锁就会触发阻塞等待,阻塞会一直持续到占用锁的线程把锁释放为止。
synchronized public void increase() { count++; }
线程不安全产生的原因:
1.线程是抢占式执行,线程间的调度充满随机性。
2.多个线程对同一个变量进行修改操作
3.针对变量的操作不是原子的
4.内存可见性也会影响线程安全(针对同一个变量t1线程循环进行多次读操作,t2线程少次修改操作,t1就不会从内存读数据了而是从寄存器里读)
5.指令重排序,也是编译器优化的一种操作,保证逻辑不变的情况下调整顺序,解决方法synchronized。
内存可见性解决方法:
1.使用synchronized关键字 使用synchronized不光能保证指令的原子性,同时也能保证内存的可见性。被synchronized包裹起来的代码编译器就不会从寄存器里读。
2.使用volatile关键字 能够保证内存可见性,禁止编译器作出上述优化,编译器每次执行判定相等都会重新从内存读取。
在java中每个类都是继承自Object,每个new出来的实例里面一方面包含自己安排的属性,另一方面包含了“对象头”即对象的一些元数据。加锁操作就是在这个对象头里面设置一个标志位。
使用synchronized的时候本质上是对某个“对象”进行加锁,此时的锁对象就是this。加锁操作就是在设置this的对象头的标志位,当两个线程同时尝试对同一个对象加锁的时候才有竞争,如果是两个线程在针对两个不同对象加锁就没有竞争。
class Counter{ public int count; synchronized public void increase(){ count++; } }
需要显示制定针对那个对象加锁(java中的任意对象都可以作为锁对象)
public void increase(){ synchronized(this){ count++; } }
相当于针对当前类的类对象加锁,类对象就是运行程序的时候。class文件被加载到JVM内存中的模样。
synchronized public static void func(){ }
或者
public static void func(){ synchronized(Counter.class){ } }
可重入锁就是同一个线程针对同一个锁,连续加锁两次,如果出现死锁就是不可重入锁,如果不会死锁就是可重入的。因此就把synchronized实现为可重入锁,下面的例子里啊连续加锁操作不会导致死锁。可重入锁内部会记录所被哪个线程占用也会记录加锁次数,因此后续再加锁就不是真的加锁而是单纯地把技术给自增。
synchronized public void increase(){ synchronized(this){ count++; } }
1.一个线程一把锁
2.两个线程两把锁
3.N个线程M把锁(哲学家就餐问题,解决方法:先拿编号小的筷子)
死锁的四个必要条件(前三个都是锁本身的特点)
1.互斥使用,一个锁被另一个线程占用后其他线程就用不了(锁的本质,保证原子性)
2.不可抢占,一个锁被一个线程占用后其他线程不可把这个锁给挖走
3.请求和保持,当一个线程占据了多把锁之后,除非显示的释放否则这些锁中都是该线程持有的
4.环路等待,等待关系成环(解决:遵循固定的顺序加锁就不会出现环路等待)
java线程类:
不安全的:ArrayList,LinkedList,HashMap,TreeMap,HashSet,TreeSet,StringBuilder
安全的:Vector,HashTable,ConcurrentHashMap,StringBuffer,String
禁止编译器优化保证内存可见性,产生原因:计算机想执行一些计算就需要把内存的数据读到CPU寄存器中,然后再从寄存器中计算写回到内存中,因为CPU访问寄存器的速度比访问内存快很多,当CPU连续多次访问内存结果都一样,CPU就会选择访问寄存器。
JMM(Java Memory Model)Java内存模型
就是把硬件结构在java中用专业的术语又重新抽象封装了一遍。
工作内存(work memory)其实指的不是内存,而是CPU寄存器。
主内存(main memeory)这才是主内存。
原因:java作为一个跨平台编程语言要把硬件细节封装起来,假设某个计算机没有CPU或者内存同样可以套到上述模型中。
寄存器,缓存和内存之间的关系
CPU从内存取数据太慢,因此把数据直接放到寄存器里来读,但寄存器空间太紧张于是又搞了一个存储空间,比寄存器大比内存小速度比寄存器慢比内存快称为缓存。寄存器和缓存统称为工作内存。
寄存器,缓存和内存之间的关系图
存储空间:CPU
速度:CPU>L1>L2>L3>内存
成本:CPU>L1>L2>L3>内存
volatile和synchronized的区别
volatile只是保证可见性不保证原子性,只是处理一个线程读和一个线程写的过程。
synchronized都能处理
wait和notify
等待和通知处理线程调度随机性问题的,join也是一种控制顺序的方式更倾向于控制线程结束。wait和notify都是Object对象的方法,调用wait方法的线程就会陷入阻塞,阻塞到有线程通过notify来通知。
public class Demo9 { public static void main(String[] args) throws InterruptedException { Object object = new Object(); System.out.println("wait前"); object.wait(); System.out.println("wait后"); } }
wait内部会做三件事;
1.先释放锁
2.等待其他线程的通知
3.收到通知后重新获得锁并继续往下执行
因此想用wait/notify就得搭配synchronized
public class Demo9 { public static void main(String[] args) throws InterruptedException { Object object = new Object(); synchronized (object){ System.out.println("wait前"); object.wait(); System.out.println("wait后"); } } }
注意:wait notify都是针对同一对象来操作的,例如现在有一个对象o,有10个线程都调用了o.wait,此时10个线程都是阻塞状态。如果调用了o.notify就会把10个线程中的一个线程唤醒。而notifyAll就会把所有10个线程全都给唤醒,此时就会竞争锁。
以上是Java多執行緒:使用Thread類別。的詳細內容。更多資訊請關注PHP中文網其他相關文章!