執行緒和行程有什麼差別? 答:一個進程是一個獨立的運作環境,它可以被視為一個程式或一個應用。而執行緒是在行程中執行的一個任務。線程是進程的子集,一個進程可以有很多線程,每個線程並行執行不同的任務。不同的行程使用不同的記憶體空間,而所有的執行緒共享一片相同的記憶體空間。
1)執行緒:在進程中負責程式執行的執行單元
執行緒本身依賴程式進行執行
執行緒是程序中的順序控制流,只能使用分配給程式的資源和環境
2) 行程:執行中的程式
一個行程至少包含一個執行緒
3 )單執行緒:程式中只存在一個線程,實際上主方法就是一個主線程
4 )多線程:在一個程式中執行多個任務
目的是更好地使用CPU資源
1.繼承Thread類別:在java.lang套件中定義, 繼承Thread類別必須重寫run()方法
class MyThread extends Thread{ private static int num = 0; public MyThread(){ num++; } @Override public void run() { System.out.println("主动创建的第"+num+"个线程"); } }
創建好了自己的線程類別之後,就可以建立線程物件了,然後透過start()方法去啟動線程。請注意,不是呼叫run()方法啟動線程,run方法中只是定義需要執行的任務,如果呼叫run方法,即相當於在主執行緒中執行run方法,跟普通的方法呼叫沒有任何區別,此時並不會建立一個新的執行緒來執行定義的任務。
public class Test { public static void main(String[] args) { MyThread thread = new MyThread(); thread.start(); } }class MyThread extends Thread{ private static int num = 0; public MyThread(){ num++; } @Override public void run() { System.out.println("主动创建的第"+num+"个线程"); } }
在上面程式碼中,透過呼叫start()方法,就會建立一個新的執行緒了。為了分辨start()方法呼叫和run()方法呼叫的區別,請看下面一個範例:
public class Test { public static void main(String[] args) { System.out.println("主线程ID:"+Thread.currentThread().getId()); MyThread thread1 = new MyThread("thread1"); thread1.start(); MyThread thread2 = new MyThread("thread2"); thread2.run(); } }class MyThread extends Thread{ private String name; public MyThread(String name){ this.name = name; } @Override public void run() { System.out.println("name:"+name+" 子线程ID:"+Thread.currentThread().getId()); } }
執行結果:
從輸出結果可以得到以下結論:
1)thread1和thread2的線程ID不同,thread2和主線程ID相同,表示透過run方法呼叫並不會創建新的線程,而是在主線程中直接運行run方法,跟普通的方法呼叫沒有任何區別;
2)雖然thread1的start方法調用在thread2的run方法前面調用,但是先輸出的是thread2的run方法調用的相關信息,說明新線程創建的過程不會阻塞主線程的後續執行。
2.實作Runnable介面
在Java中建立執行緒除了繼承Thread類別之外,還可以透過實作Runnable介面來實現類似的功能。實作Runnable介面必須重寫其run方法。
下面是一個例子:
public class Test { public static void main(String[] args) { System.out.println("主线程ID:"+Thread.currentThread().getId()); MyRunnable runnable = new MyRunnable(); Thread thread = new Thread(runnable); thread.start(); } } class MyRunnable implements Runnable{ public MyRunnable() { } @Override public void run() { System.out.println("子线程ID:"+Thread.currentThread().getId()); } }
Runnable的中文意思是“任務”,顧名思義,透過實作Runnable接口,我們定義了一個子任務,然後將子任務交由Thread去執行。注意,這種方式必須將Runnable作為Thread類別的參數,然後透過Thread的start方法來建立一個新執行緒來執行該子任務。如果呼叫Runnable的run方法的話,是不會建立新執行緒的,這根普通的方法呼叫沒有任何差別。
事實上,檢視Thread類別的實作原始碼會發現Thread類別是實作了Runnable介面的。
在Java中,這2種方式都可以用來建立執行緒去執行子任務,具體選擇哪一種方式要看自己的需求。直接繼承Thread類別的話,可能比實作Runnable介面看起來更簡潔,但是由於Java只允許單繼承,所以如果自訂類別需要繼承其他類,則只能選擇實作Runnable介面。
3.使用ExecutorService、Callable、Future實作有回傳結果的多執行緒
##多執行緒後續會學到,這裡暫時先知道有這種方法即可。 ExecutorService、Callable、Future這個物件其實都是屬於Executor框架中的功能類別。想詳細了解Executor框架的可以訪問http://www.javaeye.com/topic/366591 ,這裡面對該框架做了很詳細的解釋。回傳結果的線程是在JDK1.5中引入的新特徵,確實很實用,有了這種特徵我就不需要再為了得到返回值而大費周折了,而且即便實現了也可能漏洞百出。 可傳回值的任務必須實作Callable接口,類似的,無回傳值的任務必須Runnable介面。執行Callable任務後,可以取得一個Future的對象,在該對像上調用get就可以獲取到Callable任務返回的Object了,再結合線程池接口ExecutorService就可以實現傳說中有返回結果的多線程了。下面提供了一個完整的有回傳結果的多執行緒測試例子,在JDK1.5下驗證過沒問題可以直接使用。程式碼如下:/** * 有返回值的线程 */ @SuppressWarnings("unchecked") public class Test { public static void main(String[] args) throws ExecutionException, InterruptedException { System.out.println("----程序开始运行----"); Date date1 = new Date(); int taskSize = 5; // 创建一个线程池 ExecutorService pool = Executors.newFixedThreadPool(taskSize); // 创建多个有返回值的任务 List<Future> list = new ArrayList<Future>(); for (int i = 0; i < taskSize; i++) { Callable c = new MyCallable(i + " "); // 执行任务并获取Future对象 Future f = pool.submit(c); // System.out.println(">>>" + f.get().toString()); list.add(f); } // 关闭线程池 pool.shutdown(); // 获取所有并发任务的运行结果 for (Future f : list) { // 从Future对象上获取任务的返回值,并输出到控制台 System.out.println(">>>" + f.get().toString()); } Date date2 = new Date(); System.out.println("----程序结束运行----,程序运行时间【" + (date2.getTime() - date1.getTime()) + "毫秒】"); } } class MyCallable implements Callable<Object> { private String taskNum; MyCallable(String taskNum) { this.taskNum = taskNum; } public Object call() throws Exception { System.out.println(">>>" + taskNum + "任务启动"); Date dateTmp1 = new Date(); Thread.sleep(1000); Date dateTmp2 = new Date(); long time = dateTmp2.getTime() - dateTmp1.getTime(); System.out.println(">>>" + taskNum + "任务终止"); return taskNum + "任务返回运行结果,当前任务时间【" + time + "毫秒】"; } }程式碼說明:
上述程式碼中Executors類,提供了一系列工廠方法用於創先線程池,返回的線程池都實作了ExecutorService介面。
public static ExecutorService newFixedThreadPool(int nThreads)
建立固定數目執行緒的執行緒池。
public static ExecutorService newCachedThreadPool()
建立一個可快取的執行緒池,呼叫execute 將重複使用先前建構的執行緒(如果執行緒可用)。如果現有執行緒沒有可用的,則建立一個新執行緒並新增到池中。終止並從快取中移除那些已有 60 秒未使用的執行緒。
public static ExecutorService newSingleThreadExecutor()
建立一個單執行緒化的Executor。
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize)
建立一個支援定時及週期性的任務執行的執行緒池,多數情況下可用於替代Timer類別。
ExecutoreService提供了submit()方法,傳遞一個Callable,或Runnable,回傳Future。如果Executor後台執行緒池還沒有完成Callable的計算,這呼叫傳回Future物件的get()方法,會阻塞直到計算完成。
在正式學習Thread類別中的具體方法之前,我們先來了解一下執行緒有哪些狀態,這個將會有助於後面對Thread類別中的方法的理解。
1)建立(new)狀態: 準備好了一個多執行緒的物件
2)就緒(runnable)狀態: 呼叫了start()方法, 等待CPU進行調度
3)運行(running)狀態: 執行run()方法
4)阻塞(blocked)狀態: 暫時停止執行, 可能將資源交給其它執行緒使用
5)終止(dead)狀態: 執行緒銷毀
當需要新起一個執行緒來執行某個子任務時,就建立了一個執行緒。但是在執行緒建立之後,不會立即進入就緒狀態,因為執行緒的運作需要一些條件(例如記憶體資源,在前面的JVM記憶體區域劃分一篇部落格文章中知道程式計數器、Java棧、本機方法棧都是執行緒私有的,所以需要為執行緒分配一定的記憶體空間),只有執行緒運行所需的所有條件都滿足了,才進入就緒狀態。
當執行緒進入就緒狀態後,不代表立刻就能取得CPU執行時間,也許此時CPU正在執行其他的事情,因此它要等待。當得到CPU執行時間之後,執行緒便真正進入運作狀態。
執行緒在運行狀態過程中,可能有多個原因導致當前執行緒不繼續運行下去,例如使用者主動讓執行緒睡眠(睡眠一定的時間之後再重新執行)、使用者主動讓執行緒等待,或者被同步區塊給予阻塞,此時就對應多個狀態:time waiting(睡眠或等待一定的事件)、waiting(等待被喚醒)、blocked(阻塞)。
當因為突然中斷或子任務執行完畢,執行緒就會被消亡。
下面這副圖描述了線程從創建到消亡之間的狀態:
在有些教程上將blocked、waiting、time waiting統稱為阻塞狀態,這個也是可以的,只不過這裡我想將執行緒的狀態和Java中的方法呼叫連結起來,所以將waiting和time waiting兩個狀態分開。
註:sleep和wait的區別:
sleep是Thread類別的方法,wait是Object類別中定義的方法.
Thread.sleep不會導致鎖定行為的改變, 如果當前線程是擁有鎖的, 那麼Thread.sleep不會讓線程釋放鎖.
Thread.sleep和Object.wait都會暫停當前的線程. OS會將執行時間分配給其它線程. 區別是, 呼叫wait後, 需要別的線程執行notify/notifyAll才能夠重新獲得CPU執行時間.
上下文切換
對於單核CPU來說(對於多核CPU,此處就理解為一個核),CPU在一個時刻只能運行一個線程,當在運行一個線程的過程中轉去運行另外一個線程,這個叫做線程上下文切換(對於進程也是類似)。
由於可能目前執行緒的任務並沒有執行完畢,所以在切換時需要保存執行緒的運行狀態,以便下次重新切換回來時能夠繼續切換之前的狀態運行。舉個簡單的例子:例如一個線程A正在讀取一個檔案的內容,正讀到檔案的一半,此時需要暫停線程A,轉去執行線程B,當再次切換回來執行線程A的時候,我們不希望線程A又從檔案的開頭來讀取。
因此需要記錄執行緒A的運作狀態,那麼會記錄哪些資料呢?因為下次恢復時需要知道在這之前當前線程已經執行到哪條指令了,所以需要記錄程序計數器的值,另外比如說線程正在進行某個計算的時候被掛起了,那麼下次繼續執行的時候需要知道之前掛起時變數的值時多少,因此需要記錄CPU暫存器的狀態。所以一般來說,執行緒上下文切換過程中會記錄程式計數器、CPU暫存器狀態等資料。
說簡單點的:對於執行緒的上下文切換其實就是 儲存和恢復CPU狀態的過程,它使得執行緒執行能夠從中斷點恢復執行。
虽然多线程可以使得任务执行的效率得到提升,但是由于在线程切换时同样会带来一定的开销代价,并且多个线程会导致系统资源占用的增加,所以在进行多线程编程时要注意这些因素。
1.静态方法
currentThread()方法
currentThread()方法可以返回代码段正在被哪个线程调用的信息。
public class Run1{ public static void main(String[] args){ System.out.println(Thread.currentThread().getName()); } }
2.sleep()方法
方法sleep()的作用是在指定的毫秒数内让当前“正在执行的线程”休眠(暂停执行)。这个“正在执行的线程”是指this.currentThread()返回的线程。
sleep方法有两个重载版本:
sleep(long millis) //参数为毫秒
sleep(long millis,int nanoseconds) //第一参数为毫秒,第二个参数为纳秒
sleep相当于让线程睡眠,交出CPU,让CPU去执行其他的任务。
但是有一点要非常注意,sleep方法不会释放锁,也就是说如果当前线程持有对某个对象的锁,则即使调用sleep方法,其他线程也无法访问这个对象。看下面这个例子就清楚了:
public class Test { private int i = 10; private Object object = new Object(); public static void main(String[] args) throws IOException { Test test = new Test(); MyThread thread1 = test.new MyThread(); MyThread thread2 = test.new MyThread(); thread1.start(); thread2.start(); } class MyThread extends Thread{ @Override public void run() { synchronized (object) { i++; System.out.println("i:"+i); try { System.out.println("线程"+Thread.currentThread().getName()+"进入睡眠状态"); Thread.currentThread().sleep(10000); } catch (InterruptedException e) { // TODO: handle exception } System.out.println("线程"+Thread.currentThread().getName()+"睡眠结束"); i++; System.out.println("i:"+i); } } } }
从上面输出结果可以看出,当Thread-0进入睡眠状态之后,Thread-1并没有去执行具体的任务。只有当Thread-0执行完之后,此时Thread-0释放了对象锁,Thread-1才开始执行。
注意,如果调用了sleep方法,必须捕获InterruptedException异常或者将该异常向上层抛出。当线程睡眠时间满后,不一定会立即得到执行,因为此时可能CPU正在执行其他的任务。所以说调用sleep方法相当于让线程进入阻塞状态。
3.yield()方法
调用yield方法会让当前线程交出CPU权限,让CPU去执行其他的线程。它跟sleep方法类似,同样不会释放锁。但是yield不能控制具体的交出CPU的时间,另外,yield方法只能让拥有相同优先级的线程有获取CPU执行时间的机会。
注意,调用yield方法并不会让线程进入阻塞状态,而是让线程重回就绪状态,它只需要等待重新获取CPU执行时间,这一点是和sleep方法不一样的。
public class MyThread extends Thread{ @Override public void run() { long beginTime=System.currentTimeMillis(); int count=0; for (int i=0;i<50000000;i++){ count=count+(i+1); //Thread.yield(); } long endTime=System.currentTimeMillis(); System.out.println("用时:"+(endTime-beginTime)+" 毫秒!"); } }public class Run { public static void main(String[] args) { MyThread t= new MyThread(); t.start(); } }
执行结果:
用时:3 毫秒!
如果将 //Thread.yield();的注释去掉,执行结果如下:
用时:16080 毫秒!
对象方法
4.start()方法
start()用来启动一个线程,当调用start方法后,系统才会开启一个新的线程来执行用户定义的子任务,在这个过程中,会为相应的线程分配需要的资源。
5.run()方法
run()方法是不需要用户来调用的,当通过start方法启动一个线程之后,当线程获得了CPU执行时间,便进入run方法体去执行具体的任务。注意,继承Thread类必须重写run方法,在run方法中定义具体要执行的任务。
5.getId()
getId()的作用是取得线程的唯一标识
代码:
public class Test { public static void main(String[] args) { Thread t= Thread.currentThread(); System.out.println(t.getName()+" "+t.getId()); } }
输出:
main 1
6.isAlive()方法
方法isAlive()的功能是判断当前线程是否处于活动状态
代码:
public class MyThread extends Thread{ @Override public void run() { System.out.println("run="+this.isAlive()); } }public class RunTest { public static void main(String[] args) throws InterruptedException { MyThread myThread=new MyThread(); System.out.println("begin =="+myThread.isAlive()); myThread.start(); System.out.println("end =="+myThread.isAlive()); } }
程序运行结果:
begin ==falserun=trueend ==false
方法isAlive()的作用是测试线程是否偶处于活动状态。什么是活动状态呢?活动状态就是线程已经启动且尚未终止。线程处于正在运行或准备开始运行的状态,就认为线程是“存活”的。
有个需要注意的地方
System.out.println(“end ==”+myThread.isAlive());
虽然上面的实例中打印的值是true,但此值是不确定的。打印true值是因为myThread线程还未执行完毕,所以输出true。如果代码改成下面这样,加了个sleep休眠:
public static void main(String[] args) throws InterruptedException { MyThread myThread=new MyThread(); System.out.println("begin =="+myThread.isAlive()); myThread.start(); Thread.sleep(1000); System.out.println("end =="+myThread.isAlive()); }
则上述代码运行的结果输出为false,因为mythread对象已经在1秒之内执行完毕。
7.join()方法
在很多情况下,主线程创建并启动了线程,如果子线程中药进行大量耗时运算,主线程往往将早于子线程结束之前结束。这时,如果主线程想等待子线程执行完成之后再结束,比如子线程处理一个数据,主线程要取得这个数据中的值,就要用到join()方法了。方法join()的作用是等待线程对象销毁。
public class Thread4 extends Thread{ public Thread4(String name) { super(name); } public void run() { for (int i = 0; i < 5; i++) { System.out.println(getName() + " " + i); } } public static void main(String[] args) throws InterruptedException { // 启动子进程 new Thread4("new thread").start(); for (int i = 0; i < 10; i++) { if (i == 5) { Thread4 th = new Thread4("joined thread"); th.start(); th.join(); } System.out.println(Thread.currentThread().getName() + " " + i); } } }
执行结果:
main 0main 1main 2main 3main 4new thread 0new thread 1new thread 2new thread 3new thread 4joined thread 0joined thread 1joined thread 2joined thread 3joined thread 4main 5main 6main 7main 8main 9
由上可以看出main主线程等待joined thread线程先执行完了才结束的。如果把th.join()这行注释掉,运行结果如下:
main 0main 1main 2main 3main 4main 5main 6main 7main 8main 9new thread 0new thread 1new thread 2new thread 3new thread 4joined thread 0joined thread 1joined thread 2joined thread 3joined thread 4
8.其他方法
getName和setName
用来得到或者设置线程名称。
getPriority和setPriority
用来获取和设置线程优先级。
setDaemon和isDaemon
用来设置线程是否成为守护线程和判断线程是否是守护线程。
守护线程和用户线程的区别在于:守护线程依赖于创建它的线程,而用户线程则不依赖。举个简单的例子:如果在main线程中创建了一个守护线程,当main方法运行完毕之后,守护线程也会随着消亡。而用户线程则不会,用户线程会一直运行直到其运行完毕。在JVM中,像垃圾收集器线程就是守护线程。
在上面已经说到了Thread类中的大部分方法,那么Thread类中的方法调用到底会引起线程状态发生怎样的变化呢?下面一幅图就是在上面的图上进行改进而来的:
停止线程是在多线程开发时很重要的技术点,掌握此技术可以对线程的停止进行有效的处理。
停止一个线程可以使用Thread.stop()方法,但最好不用它。该方法是不安全的,已被弃用。
在Java中有以下3种方法可以终止正在运行的线程:
1.使用退出标志,使线程正常退出,也就是当run方法完成后线程终止
使用stop方法强行终止线程,但是不推荐使用这个方法,因为stop和suspend及resume一样,都是作废过期的方法,使用他们可能产生不可预料的结果。
2.使用interrupt方法中断线程,但这个不会终止一个正在运行的线程,还需要加入一个判断才可以完成线程的停止。
3.暂停线程
interrupt()方法
在操作系统中,线程可以划分优先级,优先级较高的线程得到的CPU资源较多,也就是CPU优先执行优先级较高的线程对象中的任务。
设置线程优先级有助于帮“线程规划器”确定在下一次选择哪一个线程来优先执行。
设置线程的优先级使用setPriority()方法,此方法在JDK的源码如下:
public final void setPriority(int newPriority) { ThreadGroup g; checkAccess(); if (newPriority > MAX_PRIORITY || newPriority < MIN_PRIORITY) { throw new IllegalArgumentException(); } if((g = getThreadGroup()) != null) { if (newPriority > g.getMaxPriority()) { newPriority = g.getMaxPriority(); } setPriority0(priority = newPriority); } }
在Java中,线程的优先级分为1~10这10个等级,如果小于1或大于10,则JDK抛出异常throw new IllegalArgumentException()。
JDK中使用3个常量来预置定义优先级的值,代码如下:
public final static int MIN_PRIORITY = 1;public final static int NORM_PRIORITY = 5;public final static int MAX_PRIORITY = 10;
1.线程优先级特性:
1)继承性
比如A线程启动B线程,则B线程的优先级与A是一样的。
2)规则性
高优先级的线程总是大部分先执行完,但不代表高优先级线程全部先执行完。
3)随机性
优先级较高的线程不一定每一次都先执行完。
在Java线程中有两种线程,一种是User Thread(用户线程),另一种是Daemon Thread(守护线程)。
Daemon的作用是为其他线程的运行提供服务,比如说GC线程。其实User Thread线程和Daemon Thread守护线程本质上来说去没啥区别的,唯一的区别之处就在虚拟机的离开:如果User Thread全部撤离,那么Daemon Thread也就没啥线程好服务的了,所以虚拟机也就退出了。
守护线程并非虚拟机内部可以提供,用户也可以自行的设定守护线程,方法:public final void setDaemon(boolean on) ;但是有几点需要注意:
thread.setDaemon(true)必须在thread.start()之前设置,否则会跑出一个IllegalThreadStateException异常。你不能把正在运行的常规线程设置为守护线程。 (备注:这点与守护进程有着明显的区别,守护进程是创建后,让进程摆脱原会话的控制+让进程摆脱原进程组的控制+让进程摆脱原控制终端的控制;所以说寄托于虚拟机的语言机制跟系统级语言有着本质上面的区别)
在Daemon线程中产生的新线程也是Daemon的。 (这一点又是有着本质的区别了:守护进程fork()出来的子进程不再是守护进程,尽管它把父进程的进程相关信息复制过去了,但是子进程的进程的父进程不是init进程,所谓的守护进程本质上说就是“父进程挂掉,init收养,然后文件0,1,2都是/dev/null,当前目录到/”)
不是所有的应用都可以分配给Daemon线程来进行服务,比如读写操作或者计算逻辑。因为在Daemon Thread还没来的及进行操作时,虚拟机可能已经退出了。
1.同步代码块
在代码块上加上”synchronized”关键字,则此代码块就称为同步代码块
同步代码块格式
synchronized(同步物件){
需要同步的程式碼區塊;
}
2.同步方法
除了程式碼區塊可以同步,方法也是可以同步的
方法同步格式
1
synchronized void 方法名稱(){}
以上是零基礎了解java線程的實現,線程的常用方法大全的詳細內容。更多資訊請關注PHP中文網其他相關文章!