概述
並發和並行是即相似又有區別:
- 並行:指兩個或多個事件在同一時刻發生;
- 並發:指兩個或多個事件在同一時間段內發生。
進程是指一個內存中運行中的應用程序。每個進程都有自己獨立的一塊內存空間,一個應用程序可以同時啟動多個進程。比如在Windows系統中,一個運行的abc.exe就是一個進程。
那么我們此時就可以處理同時玩游戲和聽音樂的問題了,我們可以設計成兩個程序,一個專門負責玩游戲,一個專門負責聽音樂。
但是問題來了,要是要設計一個植物大戰僵屍游戲,我得開N個進程才能完成多個功能,這樣的設計顯然是不合理的。
更何況大多數操作系統都不需要一個進程訪問其他進程的內存空間,也就是說進程之間的通信很不方便。
此時我們得引入“線程”這門技術,來解決這個問題。
線程是指進程中的一個執行任務(控制單元),一個進程可以同時並發運行多個線程,如:多線程下載軟件。
一個進程至少有一個線程,為了提高效率,可以在一個進程中開啟多個執行任務,即多線程。
多進程:操作系統中同時運行的多個程序。
多線程:在同一個進程中同時運行的多個任務。
我們查看Windows環境下的任務管理器:
在操作系統中允許多個任務,每一個任務就是一個進程,每一個進程也可以同時執行多個任務,每一個任務就是線程。
多線程作為一種多任務、並發的工作方式,當然有其存在優勢:
- 進程之前不能共享內存,而線程之間共享內存(堆內存)則很簡單。
- 系統創建進程時需要為該進程重新分配系統資源,創建線程則代價小很多,因此實現多任務並發時,多線程效率更高.
- Java語言本身內置多線程功能的支持,而不是單純第作為底層系統的調度方式,從而簡化了多線程編程.
用java語言創建進程
import java.io.IOException;
//如何用java語言開啟一個進程
public class ProcessDemo {
public static void main(String[] args) throws IOException{
//方式一:使用Runtime的exec方法
Runtime.getRuntime().exec("notepad");
//方式二:使用ProcessBuilder類中的start方法
ProcessBuilder pb = new ProcessBuilder("notepad");
pb.start();
}
}
創建和啟動線程
方式一:繼承Thread類
- 自定義類繼承於Thread類,那么該自定義類就是線程類;
2.覆寫run方法,將線程運行的代碼存放在run中;
3.創建自定義類的對象,即線程對象;
4.調用線程對象的start方法,啟動線程。
package thread_create;
//繼承方式 創建和啟動線程
class MusicThread extends Thread{ //MusicThread是線程子類
public void run() { //子類必須重寫run()方法
for(int i = 0 ;i < 50;i++)
{
System.out.println("聽音樂"+i);
}
}
}
public class ExtendsThreadDemo {
public static void main(String[] args){
for(int i = 0 ;i < 50;i++)
{
System.out.println("玩游戲"+i);
if( i == 10){
MusicThread t = new MusicThread(); //創建線程子類的實例
t.start(); //底層也調用了run(); 啟動線程
}
}
}
}
方式二:實現Runnable接口
1.自定義類實現Runnable接口;
2.覆寫run方法,線程運行的代碼存放在run中;
3.通過Thread類創建線程對象,並將實現了Runnable接口的實現類對象作為參數傳遞給Thread類的構造器。
4.Thread類對象調用start方法,啟動線程。
class MusicRunnable implements java.lang.Runnable{ //注意MusicRunnable類不是線程類或者線程子類
public void run() { //接口的實現必須覆蓋方法。
for(int i = 0 ;i < 50;i++)
{
System.out.println("聽音樂"+i);
}
}
}
public class ImplementsRunnableDemo {
public static void main(String[] args){
for(int i = 0 ;i < 50;i++)
{
System.out.println("玩游戲"+i);
if( i == 10){
Runnable t = new MusicRunnable(); //啟動線程必須用線程類對象調用start();
new Thread(t).start(); //類Thread的一個構造器中Thread(Runnable target)分配新的 Thread 對象。
}
}
}
}
對比兩種方法(吃蘋果比賽)
1、繼承Thread類
/**
* 案例:存在50個蘋果,現在有請三個童鞋(小A,小B,小C)上台表演吃蘋果.
* 因為A,B,C三個人可以同時吃蘋果,此時得使用多線程技術來實現這個案例.
*
* 此處程序不合理,ABC每個線程都執行50次,即ABC每個人都吃一次編號50的蘋果
*/
class Person extends Thread{
private int num= 50;
Person(String name){
super(name);
}
public void run() {
for(int i = 0;i < 50;i++){
if(num > 0)
System.out.println(super.getName()+"吃了第"+num--+"個蘋果");
}
}
}
//使用繼承Thread的方式創建線程
public class ExtendsDemo {
public static void main(String[] args){
Person p1 = new Person("A");
p1.start();
Person p2 = new Person("B");
p2.start();
Person p3 = new Person("C");
p3.start();
}
}
2、實現Runnable接口
class Apple implements Runnable{
private int num = 50;
public void run(){
for(int i = 0; i< 50 ;i++){
if(num > 0){
System.out.println(Thread.currentThread().getName()+"吃了第"+num--+"個蘋果");
}//Thread.currentThread() 返回對當前正在執行的線程對象的引用。
}
}
}
//使用實現Runnable接口的方式,這種方法可以解決此問題
public class ImplementsDemo {
public static void main(String[] args){
Runnable r = new Apple();
new Thread(r,"A").start();
new Thread(r,"B").start();
new Thread(r,"C").start();
}
}
對比
繼承方式:
- java中類是單繼承的,如果繼承了Thread,該類就不能再有其他的直接父類
- 從操作上分析,繼承方式更簡單,獲取線程的名字也簡單(操作上,簡單)
- 從多線程共享一個資源上分析,繼承方式不能做到
實現方式
- java中類可以實現接口,此時該類還可以繼承其他類,並且可以實現接口(設計上,更優雅)
- 從操作上分析,實現方式稍微復雜點,獲取線程的名字比較復雜,得使用currentThread()獲取當前線程的引用。
- 從多線程共享一個資源上分析,實現方式可以做到。
