什麼是程序,進程和線程?
程序:是爲了實現某些目的使用計算機語言編寫的指令集。
進程:一個程序運行起來就會形成一個進程,程序退出後進程也就結束了。進程有獨立的內存和數據空間。
線程:輕量級的進程。線程是進程裏面不同的路徑,或獨立的任務。
多線程的實現
1.繼承Thread類
繼承Thread類,並且重寫Thread類裏面的run方法,再創建對象並且調用start方法。
public class TestThread extends Thread {
public void run(){ //重寫run方法
for(int i=0;i<10;i++){
System.out.println("Thread線程—->"+i);
}
}
public static void main(String[] args) {
TestThread thread = new TestThread();
thread.start();
for(int i=0;i<10;i++){
System.out.println("mian方法-->"+i);
}
}
}
運行結果:
mian方法-->0
Thread線程—->0
mian方法-->1
Thread線程—->1
mian方法-->2
Thread線程—->2
mian方法-->3
Thread線程—->3
mian方法-->4
Thread線程—->4
mian方法-->5
Thread線程—->5
mian方法-->6
Thread線程—->6
mian方法-->7
Thread線程—->7
mian方法-->8
mian方法-->9
Thread線程—->8
Thread線程—->9
實現Runnable接口裏面的run方法,創建對象,並傳給Thread類,創建一個對象,使用Thread類調用start方法。
public class TestThread implements Runnable {
public static void main(String[] args) {
TestThread t=new TestThread();
Thread thread = new Thread(t);
thread.start();
for(int i=0;i<10;i++){
System.out.println("mian方法-->"+i);
}
}
@Override
public void run() {
for(int i=0;i<10;i++){
System.out.println("TestThread線程-->"+i);
}
}
}
運行結果:
mian方法-->0
TestThread線程-->0
mian方法-->1
TestThread線程-->1
mian方法-->2
TestThread線程-->2
mian方法-->3
TestThread線程-->3
mian方法-->4
TestThread線程-->4
mian方法-->5
TestThread線程-->5
mian方法-->6
TestThread線程-->6
mian方法-->7
TestThread線程-->7
mian方法-->8
TestThread線程-->8
mian方法-->9
TestThread線程-->9
可以觀察到使用線程的時候程序此時的運行並不是以往的一個方法運行完之後才運行另一個方法,而是開闢了一條獨立的路徑出來運行。
注意:
1.啓動線程只能調用Thread創建對象,並使用start方法啓動線程。如果調用run方法的話,這個時候就不會是啓動一個線程了,而是我們以往的調用方法,等run方法結束之後其他的代碼纔可以運行。
2.當main方法運行的時候jvm(java虛擬機)就會給我們創建一個線程,我們把這個纖塵稱爲主線程。
TestThread線程-->0
TestThread線程-->1
TestThread線程-->2
TestThread線程-->3
TestThread線程-->4
TestThread線程-->5
TestThread線程-->6
TestThread線程-->7
TestThread線程-->8
TestThread線程-->9
mian方法-->0
mian方法-->1
mian方法-->2
mian方法-->3
mian方法-->4
mian方法-->5
mian方法-->6
mian方法-->7
mian方法-->8
mian方法-->9
Runnbale接口和Thread類的比較
下面來模擬一下搶票系統來對比一下兩者的區別:
Runable接口:
public class TestThread implements Runnable {
public static void main(String[] args) {
TestThread t=new TestThread();
Thread thread1 = new Thread(t,"甲");
Thread thread2 = new Thread(t,"乙");
thread1.start();
thread2.start();
}
private int num=10;
@Override
public void run() {
while(true){
if(num<0){
break;
}
System.out.println(Thread.currentThread().getName()+"搶到了一張票,剩餘的票數:"+num--);
}
}
}
運行結果:
甲搶到了一張票,剩餘的票數:10
乙搶到了一張票,剩餘的票數:9
甲搶到了一張票,剩餘的票數:8
乙搶到了一張票,剩餘的票數:7
甲搶到了一張票,剩餘的票數:6
乙搶到了一張票,剩餘的票數:5
甲搶到了一張票,剩餘的票數:4
乙搶到了一張票,剩餘的票數:3
甲搶到了一張票,剩餘的票數:2
乙搶到了一張票,剩餘的票數:1
甲搶到了一張票,剩餘的票數:0
繼承Thread類:
public class TestThread extends Thread {
public static void main(String[] args) {
TestThread thread1 = new TestThread("甲");
TestThread thread2 = new TestThread("乙");
thread1.start();
thread2.start();
}
private int num=10;
private String name;
public TestThread(String name) {
this.name = name;
}
@Override
public void run() {
while(true){
if(num<0){
break;
}
System.out.println(name+"搶到了一張票,剩餘的票數:"+num--);
}
}
}
運行結果:
甲搶到了一張票,剩餘的票數:10
乙搶到了一張票,剩餘的票數:10
甲搶到了一張票,剩餘的票數:9
乙搶到了一張票,剩餘的票數:9
甲搶到了一張票,剩餘的票數:8
乙搶到了一張票,剩餘的票數:8
甲搶到了一張票,剩餘的票數:7
乙搶到了一張票,剩餘的票數:7
甲搶到了一張票,剩餘的票數:6
乙搶到了一張票,剩餘的票數:6
甲搶到了一張票,剩餘的票數:5
乙搶到了一張票,剩餘的票數:5
甲搶到了一張票,剩餘的票數:4
乙搶到了一張票,剩餘的票數:4
甲搶到了一張票,剩餘的票數:3
乙搶到了一張票,剩餘的票數:3
甲搶到了一張票,剩餘的票數:2
乙搶到了一張票,剩餘的票數:2
甲搶到了一張票,剩餘的票數:1
乙搶到了一張票,剩餘的票數:1
甲搶到了一張票,剩餘的票數:0
乙搶到了一張票,剩餘的票數:0
對比運行結果我們可以明顯的看到使用Runnable接口可以實現數據共享。
所以在實現線程的時候推薦使用Runable結構有以下優點:
1.避免單繼承的侷限性;
2.更加便於數據共享。
線程的運行狀態
1.創建:new一個對象的時候。
2.就緒狀態:當我們創對象之後調用它start方法的時候。
3.運行狀態:當cpu調用我們調用線程的時候。
4.阻塞狀態:由於某些原因線程放棄了cpu的使用權,暫停使用。直到進入就緒狀態之後纔可能繼續執行。
5.死亡:線程執行完之後。Thread類裏有一個isAlive方法可以檢測線程是否還活着。
注意:並不是我們調用start方法之後線程就立馬執行,這個時候還要看cpu,cpu什麼時候調用線程,那麼線程就處於運行狀態了。
sleep,jion,yield方法
sleep:爲了不讓線程獨佔cpu,Thread類裏面有一個靜態方法sleep,可以讓線程進入休眠狀態。它被重寫了,一個參數爲毫秒,另一個參數爲毫秒和納秒。但是一般的虛擬機都精確不到納秒,所以一般使用前者。他還拋出InterruptedException
- 如果任何線程中斷了當前線程。當拋出該異常時,當前線程的中斷狀態 被清除。
jion:等線程結束。使用jion方法之後,一個線程就會進入阻塞狀態,並且等待一個線程結束之後纔會運行。
下面是一個例子:
public class TestThread implements Runnable {
public static void main(String[] args) {
System.out.println(Thread.currentThread().getName()+"線程開始");
TestThread t =new TestThread();
Thread thread =new Thread(t);
thread.start();
try {
thread.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"線程開始");
}
@Override
public void run() {
for(int i=0;i<5;i++){
System.out.println("子線程-->"+i);
}
}
}
運行結果:main線程開始
子線程-->0
子線程-->1
子線程-->2
子線程-->3
子線程-->4
main線程開始
大家可以想一下不加jion方法會是一種什麼情況。
yield:Thread類裏面的靜態方法。當線程執行到某一個條件的時候,讓出cpu的使用權,線程進入就緒狀態。
下面是一個例子:
public class TestThread implements Runnable {
public static void main(String[] args) {
TestThread t =new TestThread();
Thread thread =new Thread(t);
thread.start();
for(int i=0;i<10;i++){
System.out.println(Thread.currentThread().getName()+"-->"+i);
}
}
@Override
public void run() {
for(int i=0;i<10;i++){
if(i%2==0){
Thread.yield();
}
System.out.println("子線程-->"+i);
}
}
}
運行結果:
子線程-->0
main-->0
子線程-->1
子線程-->2
子線程-->3
main-->1
子線程-->4
main-->2
子線程-->5
main-->3
子線程-->6
main-->4
子線程-->7
main-->5
子線程-->8
main-->6
子線程-->9
main-->7
main-->8
main-->9
注意:當執行到某一個條件的時候該線程讓出了cpu的佔有權,進入就緒狀態之後,cpu可能又會繼續調用這個線程。線程的優先級
調整線程優先級:Java線程有優先級,優先級高的線程會獲得較多的運行機會。
Java線程的優先級用整數表示,取值範圍是1~10,Thread類有以下三個靜態常量:
static int MAX_PRIORITY
線程可以具有的最高優先級,取值爲10。
static int MIN_PRIORITY
線程可以具有的最低優先級,取值爲1。
static int NORM_PRIORITY
分配給線程的默認優先級,取值爲5。
Thread類的setPriority()和getPriority()方法分別用來設置和獲取線程的優先級。
需要注意的就是,線程的優先級變大了之後,並不是優先級小的線程要等優先級大的線程運行完之後在運行,而是優先級大的線程運行的概率會變大,優先級小的線程還是會允許但是運行的概率變小了。
線程的同步
下面我們看這樣一個例子:
public class TestThread implements Runnable {
public static void main(String[] args) {
TestThread t=new TestThread();
Thread thread1 = new Thread(t,"甲");
Thread thread2 = new Thread(t,"乙");
thread1.start();
thread2.start();
}
private int num=10;
@Override
public void run() {
while(true){
if(num<0){
break;
}
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"搶到了一張票,剩餘的票數:"+num--);
}
}
}
運行結果爲:
乙搶到了一張票,剩餘的票數:9
甲搶到了一張票,剩餘的票數:10
甲搶到了一張票,剩餘的票數:8
乙搶到了一張票,剩餘的票數:7
甲搶到了一張票,剩餘的票數:6
乙搶到了一張票,剩餘的票數:5
甲搶到了一張票,剩餘的票數:4
乙搶到了一張票,剩餘的票數:3
甲搶到了一張票,剩餘的票數:2
乙搶到了一張票,剩餘的票數:1
甲搶到了一張票,剩餘的票數:0
乙搶到了一張票,剩餘的票數:-1
我們在上面模擬的搶票系統裏面加了休眠之後我們可以看到線程發生了錯誤剩餘的票數爲負數。
這是什麼原因呢?
是因爲當票數爲一張的時候,甲先搶到了一張票,但是之後進入了休眠狀態,此時因爲票數還沒減1,所以之後乙執行了這個線程,當甲搶完票之後,票數就會變成0,所以乙再搶票數就會變成負數。
那麼怎麼解決呢?
使用synchronized關鍵字進行同步。
synchronized有兩種使用方法:
1.在方法上面使用後其格式爲:
權限修飾符 synchronized 類型 methodName{
}
在某個對象實例中,使用同步方法可以避免多個線程同時訪問這個對象的方法。
2.使用synchronized鎖住對象,其格式爲:
權限修飾符 類型 methodName{
synchronized(要鎖住的對象){
}
}
下面我們就是用鎖住對象的方法對下面的例子進行更改:
public class TestThread implements Runnable {
public static void main(String[] args) {
TestThread t=new TestThread();
Thread thread1 = new Thread(t,"甲");
Thread thread2 = new Thread(t,"乙");
thread1.start();
thread2.start();
}
private int num=10;
@Override
public void run() {
while(true){
synchronized(this){
if(num<0){
break;
}
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"搶到了一張票,剩餘的票數:"+num--);
}
}
}
}
運行結果:
甲搶到了一張票,剩餘的票數:10
甲搶到了一張票,剩餘的票數:9
甲搶到了一張票,剩餘的票數:8
甲搶到了一張票,剩餘的票數:7
乙搶到了一張票,剩餘的票數:6
甲搶到了一張票,剩餘的票數:5
乙搶到了一張票,剩餘的票數:4
乙搶到了一張票,剩餘的票數:3
乙搶到了一張票,剩餘的票數:2
乙搶到了一張票,剩餘的票數:1
甲搶到了一張票,剩餘的票數:0
有興趣的人可以試試第一種方法進行同步。