線程與進(jìn)程的區(qū)別
1.每個正在系統(tǒng)上運(yùn)行的程序都是一個進(jìn)程。每個進(jìn)程包含一到多個線程。
線程是一組指令的集合,或者是程序的特殊段,它可以在程序里獨(dú)立執(zhí)行,也可以把它理解為代碼運(yùn)行的上下文,所以線程基本上是輕量級的進(jìn)程,它負(fù)責(zé)在單個程序里執(zhí)行多任務(wù)。通常由操作系統(tǒng)負(fù)責(zé)多個線程的調(diào)度和執(zhí)行。
2.使用線程可以把占據(jù)時間長的程序中的任務(wù)放到后臺去處理,程序的運(yùn)行速度可能加快,在一些等待的任務(wù)實(shí)現(xiàn)上如用戶輸入、文件讀寫和網(wǎng)絡(luò)收發(fā)數(shù)據(jù)等。
3.如果有大量的線程,會影響性能,因?yàn)椴僮飨到y(tǒng)需要在他們之間切換,更多的線程需要更多的內(nèi)存空間,線程的中止需要考慮其對程序運(yùn)行的影響。通常塊模型數(shù)據(jù)是在多個線程間共享的,需要防止線程死鎖情況的發(fā)生。
總結(jié):進(jìn)程是所有線程的集合,每一個線程是進(jìn)程中的一條執(zhí)行路徑。
為什么要使用多線程?
多線程的好處提高程序的效率。
多線程應(yīng)用場景
迅雷多線程下載、分批發(fā)送短信等
多線程創(chuàng)建方式
第一種繼承Thread類 重寫run方法
public class CreateThread extends Thread {
/**
* 創(chuàng)建多線程例子-Thread類 重寫run方法
*/
// run方法中編寫 多線程需要執(zhí)行的代碼
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println("i:" + i);
}
}
public static void main(String[] args) {
System.out.println("-----多線程創(chuàng)建開始-----");
// 1.創(chuàng)建一個線程
CreateThread createThread = new CreateThread();
// 2.開始執(zhí)行線程 注意 開啟線程不是調(diào)用run方法,而是start方法
System.out.println("-----多線程創(chuàng)建啟動-----");
createThread.start();
System.out.println("-----多線程創(chuàng)建結(jié)束-----");
}
}

調(diào)用start方法后,代碼并沒有從上往下執(zhí)行,而是有一條新的執(zhí)行分支。
多線程不同執(zhí)行路徑:

第二種實(shí)現(xiàn)Runnable接口,重寫run方法
/**
* 功能描述:創(chuàng)建多線程例子-Thread類 重寫run方法
*/
class CreateRunnable implements Runnable {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println("i:" + i);
}
}
/**
* 實(shí)現(xiàn)Runnable接口,重寫run方法
*/
public static void main(String[] args) {
System.out.println("-----多線程創(chuàng)建開始-----");
// 1.創(chuàng)建一個線程
CreateRunnable createThread = new CreateRunnable();
// 2.開始執(zhí)行線程 注意 開啟線程不是調(diào)用run方法,而是start方法
System.out.println("-----多線程創(chuàng)建啟動-----");
Thread thread = new Thread(createThread);
thread.start();
System.out.println("-----多線程創(chuàng)建結(jié)束-----");
}
}
使用匿名內(nèi)部類方式
public static void main(String[] args) {
System.out.println("-----多線程創(chuàng)建開始-----");
Thread thread = new Thread(new Runnable() {
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println("i:" + i);
}
}
});
thread.start();
System.out.println("-----多線程創(chuàng)建結(jié)束-----");
}
}
使用實(shí)現(xiàn)Runnable接口比使用繼承Thread類好,因?yàn)閷?shí)現(xiàn)了接口還可以繼續(xù),繼承了類不能再繼承。開啟線程是調(diào)用start方法而不是run方法。
獲取線程對象以及名稱
常用線程api方法
| 方法 | 描述 |
|---|---|
| start() | 啟動線程 |
| currentThread() | 獲取當(dāng)前線程對象 |
| getId() | 獲取當(dāng)前線程Id Thread-編號 該編號從0開始 |
| getName() | 獲取當(dāng)前線程名稱 |
| sleep(long mill) | 休眠線程 |
| stop() | 停止線程 |
常用線程構(gòu)造函數(shù)
| 方法 | 描述 |
|---|---|
| Thread() | 分配一個新的Thread對象 |
| Thread(String name) | 分配一個新的Thread對象,具有指定的name正如其名 |
| Thread(Runable r) | 分配一個新的Thread對象 |
| Thread(Runable r,String name) | 分配一個新的Thread對象 |
守護(hù)線程
Java中有兩種線程,一種是用戶線程,另一種是守護(hù)線程。
用戶線程是指用戶自定義創(chuàng)建的線程,主線程停止,用戶線程不會停止。守護(hù)線程當(dāng)進(jìn)程不存在或主線程停止,守護(hù)線程也會被停止。
public class DaemonThread {
public static void main(String[] args) {
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
while (true) {
try {
Thread.sleep(100);
} catch (Exception e) {
// TODO: handle exception
}
System.out.println("我是子線程...");
}
}
});
thread.setDaemon(true);
thread.start();
for (int i = 0; i < 10; i++) {
try {
Thread.sleep(100);
} catch (Exception e) {
}
System.out.println("我是主線程");
}
System.out.println("主線程執(zhí)行完畢!");
}
}
多線程運(yùn)行狀態(tài)

新建狀態(tài)
當(dāng)用new操作符創(chuàng)建一個新城時,例如new Thread(r),線程還沒有開始運(yùn)行,此時線程處在新建狀態(tài)。當(dāng)一個線程處于新生狀態(tài)時,程序還沒有開始運(yùn)行線程中的代碼。
就緒狀態(tài)
一個新創(chuàng)建的線程并不自動開始運(yùn)行,要執(zhí)行線程,必須調(diào)用線程的start()方法。當(dāng)線程對象調(diào)用start()方法即啟動了線程,start()方法創(chuàng)建線程運(yùn)行的系統(tǒng)資源,并調(diào)度線程運(yùn)行run()方法。當(dāng)start()方法返回后,線程就處于就緒狀態(tài)。
就緒狀態(tài)
處于就緒狀態(tài)的線程并不一定立即運(yùn)行run()方法,線程還必須同其他線程競爭CPU時間,只有獲得CPU時間才可以運(yùn)行線程。因?yàn)樵趩蜟PU的計(jì)算機(jī)系統(tǒng)中,不可能同時運(yùn)行多個線程,一個時刻僅有一個線程處于運(yùn)行狀態(tài)。因此此時可能有多個線程處于就緒狀態(tài)。對多個處于就緒狀態(tài)的線程是由Java運(yùn)行時系統(tǒng)的線程調(diào)度程序(thread scheduler)來調(diào)度的。
運(yùn)行狀態(tài)
當(dāng)線程獲得CPU時間后,它才進(jìn)入運(yùn)行狀態(tài),真正開始執(zhí)行run()方法。
阻塞狀態(tài)
線程運(yùn)行過程中,可能由于各種原因進(jìn)入阻塞狀態(tài):
1.線程通過調(diào)用sleep方法進(jìn)入睡眠狀態(tài);
2.線程調(diào)用一個在I/O上唄阻塞的操作,即該操作在輸入輸入操作完成之前不會反悔到它的調(diào)用者;
3.線程試圖得到一個鎖,而該鎖正被其他線程持有;
4.線程在等待某個觸發(fā)條件。
死亡狀態(tài)
有兩個原因會導(dǎo)致線程死亡:
1.run方法正常退出而自然死亡;
2.一個為捕獲的異常終止了run方法而使線程猝死。
為了確定線程在當(dāng)前是否存活著(就是要么是可運(yùn)行的,要么是被阻塞了),要使用isAlive方法。如果是可運(yùn)行或被阻塞,這個方法返回true,如果線程仍舊是new狀態(tài)且不是可運(yùn)行的,或者線程死亡了,則返回false。
join()方法作用
join作用是讓其他線程變?yōu)榈却?,t1.join();讓其他線程變?yōu)榈却?,知道?dāng)前t1線程執(zhí)行完畢,才釋放。thread.join把指定的線程加入到當(dāng)前線程,可以將兩個交替執(zhí)行的線程合并為順序執(zhí)行的線程。比如在線程B中調(diào)用了線程A的join()方法,直到線程A執(zhí)行完畢后,才會繼續(xù)執(zhí)行線程B。
創(chuàng)建一個線程,子線程執(zhí)行完畢后,主線程才能執(zhí)行。
/**
* join使用方法
* @author cherry
*
*/
public class JoinThread implements Runnable {
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName() + "---i:" + i);
}
}
public static void main(String[] args) {
JoinThread joinThread = new JoinThread();
Thread t1 = new Thread(joinThread);
Thread t2 = new Thread(joinThread);
t1.start();
t2.start();
try {
// 其他線程變?yōu)榈却隣顟B(tài),等t1線程執(zhí)行完成之后才能執(zhí)行join方法。
t1.join();
} catch (Exception e) {
}
for (int i = 0; i < 100; i++) {
System.out.println("main ---i:" + i);
}
}
}
優(yōu)先級
現(xiàn)代操作系統(tǒng)基本采用時分的形式調(diào)度運(yùn)行的線程,線程分配得到的時間片的多少決定了線程使用處理器資源的多少,也對應(yīng)了線程優(yōu)先級這個概念。在JAVA線程中,通過一個int priority來控制優(yōu)先級,范圍為1-10,其中10最高,默認(rèn)值為5。下面是源碼(基于1.8)中關(guān)于priority的一些量和方法。
public class PrioritytThread implements Runnable {
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().toString() + "---i:" + i);
}
}
public static void main(String[] args) {
PrioritytThread prioritytThread = new PrioritytThread();
Thread t1 = new Thread(prioritytThread);
Thread t2 = new Thread(prioritytThread);
t1.start();
// 注意設(shè)置了優(yōu)先級, 不代表每次都一定會被執(zhí)行。 只是CPU調(diào)度會有限分配
t1.setPriority(10);
t2.start();
}
}
Yield方法
Thread.yield()方法的作用:暫停當(dāng)前正在執(zhí)行的線程,并執(zhí)行其他線程。(可能沒有效果)
yield()讓當(dāng)前正在運(yùn)行的線程回到可運(yùn)行狀態(tài),以允許具有相同優(yōu)先級的其他線程獲得運(yùn)行的機(jī)會。因此,使用yield()的目的是讓具有相同優(yōu)先級的線程之間能夠適當(dāng)?shù)妮啌Q執(zhí)行。但是,實(shí)際中無法保證yield()達(dá)到讓步的目的,因?yàn)?,讓步的線程可能被線程調(diào)度程序再次選中。
結(jié)論:大多數(shù)情況下,yield()將導(dǎo)致線程從運(yùn)行狀態(tài)轉(zhuǎn)到可運(yùn)行狀態(tài),但有可能沒有效果。
多線程分批處理數(shù)據(jù)
例子:假如公司有10萬名用戶,現(xiàn)在公司需要做活動,給每位用戶發(fā)送一條祝福短信。
為了提高程序的效率,使用多線程技術(shù)分批發(fā)送數(shù)據(jù)。
每開一個線程,都會占用CPU資源。
服務(wù)器(電腦)配置CPU核數(shù)。
首先新建一個用戶實(shí)體類User:
public class User {
private String userId;
private String userName;
public String getUserId() {
return userId;
}
public void setUserId(String userId) {
this.userId = userId;
}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
}
**然后建立多線程UserThread執(zhí)行發(fā)送短信:
public class UserThread extends Thread {
private List<User> user;
/**
* 通過構(gòu)造函數(shù) 傳入每個線程需要執(zhí)行的發(fā)送短信內(nèi)容
*
* @param list
*/
public UserThread(List<User> user) {
this.user = user;
}
public void run() {
for (User users : user) {
System.out.println("threadName:" + Thread.currentThread().getName() + "-客戶編號:" + users.getUserId()
+ "---客戶名稱:" + users.getUserName());
// 調(diào)用發(fā)送短信具體代碼
}
}
}
初始化數(shù)據(jù)
public class DataInit {
public static List<User> init() {
List<User> list = new ArrayList<User>();
for (int i = 1; i <= 140; i++) {
User user = new User();
user.setUserId("userId" + i);
user.setUserName("userName" + i);
list.add(user);
}
return list;
}
}
計(jì)算分頁工具類
public class PageUtil {
public static <T> List<List<T>> splitList(List<T> list, int pageSize) {
int listSize = list.size();
int page = (listSize + (pageSize - 1)) / pageSize;
List<List<T>> listArray = new ArrayList<List<T>>();
for (int i = 0; i < page; i++) {
List<T> subList = new ArrayList<T>();
for (int j = 0; j < listSize; j++) {
int pageIndex = ((j + 1) + (pageSize - 1)) / pageSize;
if (pageIndex == (i + 1)) {
subList.add(list.get(j));
}
if ((j + 1) == ((j + 1) * pageSize)) {
break;
}
}
listArray.add(subList);
}
return listArray;
}
}
實(shí)現(xiàn)發(fā)送短信
public class Realize {
public static void main(String[] args) {
// 1.初始化用戶數(shù)據(jù)
List<User> listUser = DataInit.init();
// 2.計(jì)算創(chuàng)建創(chuàng)建多少個線程并且每一個線程需要執(zhí)行“分批發(fā)送短信用戶”
// 每一個線程分批跑多少
int userThreadPage = 50;
// 計(jì)算所有線程數(shù)
List<List<User>> splitUserList = PageUtil.splitList(listUser, userThreadPage);
int threadSaze = splitUserList.size();
for (int i = 0; i < threadSaze; i++) {
List<User> list = splitUserList.get(i);
UserThread userThread = new UserThread(list);
// 3.執(zhí)行任務(wù)發(fā)送短信
userThread.start();
}
}
}