創建線程
Java使用 java.lang.Thread 類代表線程,所有的線程對象都必須是Thread類或其子類的實例。
Thread類
public class MyThread extends Thread{
public MyThread(String name) {
//調用父類的String參數的構造方法,指定線程的名稱
super(name);
}
/**
* 重寫run方法,完成該線程執行的邏輯
*/
@Override
public void run() {
try {
sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
for(int i = 0; i < 100; i++) {
System.out.println(getName() + i);
}
}
}
public class ThreadTest {
public static void main(String[] args) {
//創建自定義線程對象
MyThread mt1 = new MyThread("a");
MyThread mt2 = new MyThread("b");
MyThread mt3 = new MyThread("c");
//開啓新線程
mt1.start();
mt2.start();
mt3.start();
//主線程中的for循環
for (int i = 0; i < 20; i++) {
System.out.println("主線程:"+i);
}
}
}
程序啓動運行main時候,java虛擬機啓動一個進程,主線程main在main()調用時候被創建。隨着調用mt的對象的 start方法,另外三個新的線程也啓動了,這樣,整個應用就在多線程下運行。
Runnable接口
public class MyRunnable implements Runnable{
@Override
public void run() {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
for(int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName() + i);
}
}
}
public class RunableTest {
public static void main(String[] args) {
MyRunnable mr = new MyRunnable();
new Thread(mr, "a").start();
new Thread(mr, "b").start();
new Thread(mr, "c").start();
}
}
通過實現Runnable接口,使得該類有了多線程類的特徵。run()方法是多線程程序的一個執行目標。所有的多線程 代碼都在run方法裏面。Thread類實際上也是實現了Runnable接口的類。
在啓動的多線程的時候,需要先通過Thread類的構造方法Thread(Runnable target) 構造出對象,然後調用Thread 對象的start()方法來運行多線程代碼。
實際上所有的多線程代碼都是通過運行Thread的start()方法來運行的。因此,不管是繼承Thread類還是實現 Runnable接口來實現多線程,最終還是通過Thread的對象的API來控制線程的,熟悉Thread類的API是進行多線程 編程的基礎。
實現Runnable接口比繼承Thread類所具有的優勢
- 適合多個相同的程序代碼的線程去共享同一個資源
- 可以避免java中的單繼承的侷限性
- 增加程序的健壯性,實現解耦操作,代碼可以被多個線程共享,代碼和線程獨立
- 線程池只能放入實現Runable或Callable類線程,不能直接放入繼承Thread的類。
在java中,每次程序運行至少啓動2個線程。一個是main線程,一個是垃圾收集線程。因爲每當使用 java命令執行一個類的時候,實際上都會啓動一個JVM,每一個JVM其實在就是在操作系統中啓動了一個進程。
線程安全
如果有多個線程在同時運行,而這些線程可能會同時運行這段代碼。程序每次運行結果和單線程運行的結果是一樣 的,而且其他的變量的值也和預期的是一樣的,就是線程安全的。
在多線程場景下,如果沒有對資源加鎖,會導致共享資源的訪問出錯,下面模擬售票演示線程不安全:
public class Ticket implements Runnable {
private int ticket = 100;
@Override
public void run() {
while (true) {
if (ticket > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
String name = Thread.currentThread().getName();
System.out.println(name + "正在賣:" + ticket);
ticket--;
}
}
}
}
public class Test1 {
public static void main(String[] args) {
Ticket ticket = new Ticket();
Thread t1 = new Thread(ticket, "窗口1");
Thread t2 = new Thread(ticket, "窗口2");
Thread t3 = new Thread(ticket, "窗口3");
t1.start();
t2.start();
t3.start();
}
}
部分結果:
線程同步
當我們使用多個線程訪問同一資源的時候,且多個線程中對資源有寫操作,就容易出現線程安全問題。
爲了保證每個線程都能正常執行原子操作,Java引入了線程同步機制。有三種方式完成同步操作:
- 同步代碼塊
- 同步方法
- 鎖機制
同步代碼塊
synchronized
關鍵字可以用於方法中的某個區塊中,表示只對這個區塊的資源進行互斥訪問。
synchronized(同步鎖) {
需要同步操作的代碼
}
同步鎖是一個抽象的概念,鎖對象可以是任意類型,比如new Object()也可以。
public class Ticket implements Runnable {
private int ticket = 100;
// 一個對象,作爲一把鎖
Object obj = new Object();
@Override
public void run() {
while (true) {
// 也可以直接用this作爲鎖對象
synchronized (obj) {
if (ticket > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
String name = Thread.currentThread().getName();
System.out.println(name + "正在賣:" + ticket);
ticket--;
}
}
}
}
}
同步方法
使用synchronized修飾的方法叫做同步方法,保證一個線程在執行方法的時候,其他線程阻塞等待。
public synchronized void method() {
可能產生線程安全問題的代碼
}
public class Ticket implements Runnable {
private int ticket = 100;
@Override
public void run() {
while (true) {
payTicket();
}
}
// 鎖對象默認就是this
public synchronized void payTicket() {
if (ticket > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
String name = Thread.currentThread().getName();
System.out.println(name + "正在賣:" + ticket);
ticket--;
}
}
}
同步方法payTicket()也可以設置爲靜態的,對於非靜態方法,同步鎖是this,對於靜態方法,使用當前方法所在類的字節碼對象(類名.class)。
Lock鎖
Lock鎖也稱同步鎖
- public void lock() :加同步鎖
- public void unlock() :釋放同步鎖
public class Ticket implements Runnable {
private int ticket = 100;
Lock l = new ReentrantLock();
@Override
public void run() {
while (true) {
l.lock();
if (ticket > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}finally{
String name = Thread.currentThread().getName();
System.out.println(name + "正在賣:" + ticket);
ticket--;
l.unlock();
}
}
}
}
}
線程間通信
多個線程併發執行時,在默認情況下,CPU是隨機性在線程之間進行切換,但是有時候我們希望它能夠有規律的執行,那麼多線程之間就需要一些協調通信來改變或控制CPU的隨機性。Java提供了等待喚醒機制來解決這個問題,具體來說就是多個線程依靠一個同步鎖,然後藉助於wait()
和notify()
方法就可以實現線程間的協調通信。
同步鎖相當於中間人的作用,多個線程必須用同一個同步鎖,只有同一個鎖上的被等待線程,纔可以被持有該鎖的另一個線程喚醒,使用不同鎖的線程之間不能相互喚醒,也就是無法協調通信。
Java在Object類中提供了一些方法可以用來實現線程間的協調通信:
- public final void wait():釋放當前線程的鎖
- public final native void wait(long timeout):釋放當前線程的鎖,並等待timeout毫秒
- public final native notify():喚醒持有同一個鎖的某個線程
- public final native notifyAll():喚醒持有同一個鎖的所有線程
下面演示使用這幾個API實現多線程情況下,線程有序執行,下面的例子就是循環利用的就是Java中的線程等待喚醒機制。下面的例子就是兩個線程交替執行:
public class MyLock {
public static Object o = new Object();
}
public class ThreadFor1 extends Thread{
public void run() {
for(int i = 0; i < 10; i++) {
synchronized (MyLock.o) {
System.out.println("thread1->"+i);
// 先喚醒其他的一個線程
MyLock.o.notify();
try {
// 再等待
MyLock.o.wait();
}catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
public class ThreadFor2 extends Thread{
public void run() {
for(int i = 0; i < 10; i++) {
synchronized (MyLock.o) {
System.out.println("thread2->"+i);
// 先喚醒其他的一個線程
MyLock.o.notify();
try {
// 再等待
MyLock.o.wait();
}catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
public class Test {
public static void main(String[] args) {
Thread t1 = new ThreadFor1();
Thread t2 = new ThreadFor2();
t1.start();
t2.start();
}
}