java main是多线程的吗_Java多线程之线程及其常用方法
創(chuàng)建線程和常用方法
進(jìn)程與線程的概念進(jìn)程
進(jìn)程是程序執(zhí)行時(shí)的一個(gè)實(shí)例。程序運(yùn)行時(shí)系統(tǒng)就會(huì)創(chuàng)建一個(gè)進(jìn)程,并為它分配資源,然后把該進(jìn)程放入進(jìn)程就緒隊(duì)列,進(jìn)程調(diào)度器選中它的時(shí)候就會(huì)為它分配CPU時(shí)間,程序開始真正運(yùn)行。線程
線程是一條執(zhí)行路徑,是程序執(zhí)行時(shí)的最小單位,它是進(jìn)程的一個(gè)執(zhí)行流,是CPU調(diào)度和分派的基本單位,一個(gè)進(jìn)程可以由很多個(gè)線程組成,線程間共享進(jìn)程的所有資源,每個(gè)線程有自己的堆棧和局部變量。線程由CPU獨(dú)立調(diào)度執(zhí)行,在多CPU環(huán)境下就允許多個(gè)線程同時(shí)(并發(fā))運(yùn)行。
一個(gè)正在運(yùn)行的軟件(如迅雷)就是一個(gè)進(jìn)程,一個(gè)進(jìn)程可以同時(shí)運(yùn)行多個(gè)任務(wù)( 迅雷軟件可以同時(shí)下載多個(gè)文件,每個(gè)下載任務(wù)就是一個(gè)線程), 可以簡單的認(rèn)為進(jìn)程是線程的集合。
為什么要使用多線程
多線程可以提高程序的效率。
實(shí)際生活案例:村長要求喜洋洋在一個(gè)小時(shí)內(nèi)打100桶水,可以喜洋洋一個(gè)小時(shí)只能打25桶水,如果這樣就需要4個(gè)小時(shí)才能完成任務(wù),為了在一個(gè)小時(shí)能夠完成,喜洋洋就請(qǐng)美洋洋、懶洋洋、沸洋洋,來幫忙,這樣4只羊同時(shí)干活,在一小時(shí)內(nèi)完成了任務(wù)。原本用4個(gè)小時(shí)完成的任務(wù)現(xiàn)在只需要1個(gè)小時(shí)就完成了,如果把每只羊看做一個(gè)線程,多只羊即多線程可以提高程序的效率。
并發(fā)編程的概念順序編程public class Main {
// 順序編程:當(dāng)吃飯吃不完的時(shí)候,是不能喝酒的,只能吃完晚才能喝酒
public static void main(String[] args) throws Exception {
// 先吃飯?jiān)俸染?/p>
eat();
drink();
}
private static void eat() throws Exception {
System.out.println("開始吃飯...\t" + new Date());
Thread.sleep(5000);
System.out.println("結(jié)束吃飯...\t" + new Date());
}
private static void drink() throws Exception {
System.out.println("開始喝酒...\t" + new Date());
Thread.sleep(5000);
System.out.println("結(jié)束喝酒...\t" + new Date());
}
}
并發(fā)編程public class Main {
public static void main(String[] args) {
// 一邊吃飯一邊喝酒
new EatThread().start();
new DrinkThread().start();
}
}
class EatThread extends Thread{
@Override
public void run() {
System.out.println("開始吃飯?...\t" + new Date());
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("結(jié)束吃飯?...\t" + new Date());
}
}
class DrinkThread extends Thread {
@Override
public void run() {
System.out.println("開始喝酒??...\t" + new Date());
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("結(jié)束喝酒?...\t" + new Date());
}
}
并發(fā)編程,一邊吃飯一邊喝酒總共用時(shí)5秒,比順序編程更快,因?yàn)椴l(fā)編程可以同時(shí)運(yùn)行,而不必等前面的代碼運(yùn)行完之后才允許后面的代碼。
同一個(gè)時(shí)刻一個(gè)CPU只能做一件事情,即同一時(shí)刻只能一個(gè)線程中的部分代碼,假如有兩個(gè)線程,Thread-0和Thread-1,剛開始CPU說Thread-0你先執(zhí)行,給你3毫秒時(shí)間,Thread-0執(zhí)行了3毫秒時(shí)間,但是沒有執(zhí)行完,此時(shí)CPU會(huì)暫停Thread-0執(zhí)行并記錄Thread-0執(zhí)行到哪行代碼了,當(dāng)時(shí)的變量的值是多少,然后CPU說Thread-1你可以執(zhí)行了,給你2毫秒的時(shí)間,Thread-1執(zhí)行了2毫秒也沒執(zhí)行完,此時(shí)CPU會(huì)暫停Thread-1執(zhí)行并記錄Thread-1執(zhí)行到哪行代碼了,當(dāng)時(shí)的變量的值是多少,此時(shí)CPU又說Thread-0又該你,這次我給你5毫秒時(shí)間,去執(zhí)行吧,此時(shí)CPU就找出上次Thread-0線程執(zhí)行到哪行代碼了,當(dāng)時(shí)的變量值是多少,然后接著上次繼續(xù)執(zhí)行,結(jié)果用了2毫秒就Thread-0就執(zhí)行完了,就終止了,然后CPU說Thread-1又輪到你,這次給你4毫秒,同樣CPU也會(huì)先找出上次Thread-1線程執(zhí)行到哪行代碼了,當(dāng)時(shí)的變量值是多少,然后接著上次繼續(xù)開始執(zhí)行,結(jié)果Thread-1在4毫秒內(nèi)也執(zhí)行結(jié)束了,Thread-1也結(jié)束了終止了。CPU在來回改變線程的執(zhí)行機(jī)會(huì)稱之為線程上下文切換。
線程的狀態(tài)
創(chuàng)建(new)狀態(tài): 準(zhǔn)備好了一個(gè)多線程的對(duì)象,即執(zhí)行了new Thread(); 創(chuàng)建完成后就需要為線程分配內(nèi)存
就緒(runnable)狀態(tài): 調(diào)用了start()方法, 等待CPU進(jìn)行調(diào)度
運(yùn)行(running)狀態(tài): 執(zhí)行run()方法
阻塞(blocked)狀態(tài): 暫時(shí)停止執(zhí)行線程,將線程掛起(sleep()、wait()、join()、沒有獲取到鎖都會(huì)使線程阻塞), 可能將資源交給其它線程使用
死亡(terminated)狀態(tài): 線程銷毀(正常執(zhí)行完畢、發(fā)生異常或者被打斷interrupt()都會(huì)導(dǎo)致線程終止)
創(chuàng)建線程的方式
1.子類繼承Thread類,重寫Thread類run方法,new Thread子類創(chuàng)建線程對(duì)象,調(diào)用線程對(duì)象的start()方法。public class Main {
public static void main(String[] args) {
new MyThread().start();
}
}
class MyThread extends Thread {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "\t" + Thread.currentThread().getId());
}
}
2.實(shí)現(xiàn)類實(shí)現(xiàn)Runnable接口,重寫Runnable接口run()方法,實(shí)例化實(shí)現(xiàn)類并將此實(shí)例作為Thread類的target來創(chuàng)建線程對(duì)象,調(diào)用線程對(duì)象的start()方法。public class Main {
public static void main(String[] args) {
// 將Runnable實(shí)現(xiàn)類作為Thread的構(gòu)造參數(shù)傳遞到Thread類中,然后啟動(dòng)Thread類
MyRunnable runnable = new MyRunnable();
new Thread(runnable).start();
}
}
class MyRunnable implements Runnable {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "\t" + Thread.currentThread().getId());
}
}
3.實(shí)現(xiàn)類實(shí)現(xiàn)Callable接口,重寫Callable帶有返回值的call()方法,用FutureTask包裝Callable實(shí)現(xiàn)類的實(shí)例,將FutureTask的實(shí)例作為Thread類的target來創(chuàng)建線程對(duì)象,調(diào)用線程對(duì)象的start()方法public class Main {
public static void main(String[] args) throws Exception {
// 將Callable包裝成FutureTask,FutureTask也是一種Runnable
MyCallable callable = new MyCallable();
FutureTask futureTask = new FutureTask<>(callable);
new Thread(futureTask).start();
// get方法會(huì)阻塞調(diào)用的線程
Integer sum = futureTask.get();
System.out.println(Thread.currentThread().getName() + Thread.currentThread().getId() + "=" + sum);
}
}
class MyCallable implements Callable {
@Override
public Integer call() throws Exception {
System.out.println(Thread.currentThread().getName() + "\t" + Thread.currentThread().getId() + "\t" + new Date() + " \tstarting...");
int sum = 0;
for (int i = 0; i <= 100000; i++) {
sum += i;
}
Thread.sleep(5000);
System.out.println(Thread.currentThread().getName() + "\t" + Thread.currentThread().getId() + "\t" + new Date() + " \tover...");
return sum;
}
}
三種方式比較:
1.Thread: 繼承方式, 不建議使用, 因?yàn)镴ava是單繼承的,繼承了Thread就沒辦法繼承其它類了,不夠靈活
2.Runnable: 實(shí)現(xiàn)接口,比Thread類更加靈活,沒有單繼承的限制
3.Callable: Thread和Runnable都是重寫的run()方法并且沒有返回值,Callable是重寫的call()方法并且有返回值并可以借助FutureTask類來判斷線程是否已經(jīng)執(zhí)行完畢或者取消線程執(zhí)行
4.當(dāng)線程不需要返回值時(shí)使用Runnable,需要返回值時(shí)就使用Callable
常用方法
1.Thread.currentThread()public static void main(String[] args) {
Thread thread = Thread.currentThread();
// 線程名稱
String name = thread.getName();
// 線程id
long id = thread.getId();
// 線程已經(jīng)啟動(dòng)且尚未終止
// 線程處于正在運(yùn)行或準(zhǔn)備開始運(yùn)行的狀態(tài),就認(rèn)為線程是“存活”的
boolean alive = thread.isAlive();
// 線程優(yōu)先級(jí)
int priority = thread.getPriority();
// 是否守護(hù)線程
boolean daemon = thread.isDaemon();
// Thread[name=main,id=1,alive=true,priority=5,daemon=false]
System.out.println("Thread[name=" + name + ",id=" + id + ",alive=" + alive + ",priority=" + priority + ",daemon=" + daemon + "]");
}
2.sleep() 與 interrupt()
sleep(): 睡眠指定時(shí)間,即讓程序暫停指定時(shí)間運(yùn)行,時(shí)間到了會(huì)繼續(xù)執(zhí)行代碼,如果時(shí)間未到就要醒需要使用interrupt()來隨時(shí)喚醒
interrupt(): 喚醒正在睡眠的程序,調(diào)用interrupt()方法,會(huì)使得sleep()方法拋出InterruptedException異常,當(dāng)sleep()方法拋出異常就中斷了sleep的方法,從而讓程序繼續(xù)運(yùn)行下去public static void main(String[] args) throws Exception {
Thread thread0 = new Thread(()-> {
try {
System.out.println(new Date() + "\t" + Thread.currentThread().getName() + "\t太困了,讓我睡10秒,中間有事叫我,zZZ。。。");
Thread.sleep(10000);
} catch (InterruptedException e) {
System.out.println(new Date() + "\t" + Thread.currentThread().getName() + "\t被叫醒了,又要繼續(xù)干活了");
}
});
thread0.start();
// 這里睡眠只是為了保證先讓上面的那個(gè)線程先執(zhí)行
Thread.sleep(2000);
new Thread(()-> {
System.out.println(new Date() + "\t" + Thread.currentThread().getName() + "\t醒醒,醒醒,別睡了,起來干活了!!!");
// 無需獲取鎖就可以調(diào)用interrupt
thread0.interrupt();
}).start();
}
3.wait() 與 notify()
wait、notify和notifyAll方法是Object類的final方法,這些方法不能被子類重寫。因此在程序中可以通過this或者super來調(diào)this.wait(), super.wait()。
wait(): 導(dǎo)致線程進(jìn)入等待阻塞狀態(tài),會(huì)一直等待直到它被其他線程通過notify()或者notifyAll喚醒。該方法只能在同步方法或同步塊內(nèi)部調(diào)用。如果當(dāng)前線程不是鎖的持有者,該方法拋出一個(gè)IllegalMonitorStateException異常。wait(long timeout): 時(shí)間到了自動(dòng)執(zhí)行,類似于sleep(long millis)
notify(): 該方法只能在同步方法或同步塊內(nèi)部調(diào)用, 隨機(jī)選擇一個(gè)(注意:只會(huì)通知一個(gè))在該對(duì)象上調(diào)用wait方法的線程,解除其阻塞狀態(tài)
notifyAll(): 喚醒所有的wait對(duì)象
wait()是讓程序暫停執(zhí)行,線程進(jìn)入當(dāng)前實(shí)例的等待隊(duì)列,這個(gè)隊(duì)列屬于該實(shí)例對(duì)象,所以調(diào)用notify也必須使用該對(duì)象來調(diào)用,不能使用別的對(duì)象來調(diào)用。調(diào)用wait和notify必須使用同一個(gè)對(duì)象來調(diào)用。
注意:Object.wait()和Object.notify()和Object.notifyall()必須寫在synchronized方法內(nèi)部或者synchronized塊內(nèi)部
讓哪個(gè)對(duì)象等待wait就去通知notify哪個(gè)對(duì)象,不要讓A對(duì)象等待,結(jié)果卻去通知B對(duì)象,要操作同一個(gè)對(duì)象public class WaitNotifyTest {
public static void main(String[] args) throws Exception {
WaitNotifyTest waitNotifyTest = new WaitNotifyTest();
new Thread(() -> {
try {
waitNotifyTest.printFile();
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
new Thread(() -> {
try {
waitNotifyTest.printFile();
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
new Thread(() -> {
try {
System.out.println(new Date() + "\t" + Thread.currentThread().getName() + "\t睡覺1秒中,目的是讓上面的線程先執(zhí)行,即先執(zhí)行wait()");
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
waitNotifyTest.notifyPrint();
}).start();
}
private synchronized void printFile() throws InterruptedException {
System.out.println(new Date() + "\t" + Thread.currentThread().getName() + "\t等待打印文件...");
this.wait();
System.out.println(new Date() + "\t" + Thread.currentThread().getName() + "\t打印結(jié)束。。。");
}
private synchronized void notifyPrint() {
this.notify();
System.out.println(new Date() + "\t" + Thread.currentThread().getName() + "\t通知完成...");
}
}
notifyAll()
4.sleep() 與 wait()
Thread.sleep(long millis): 睡眠時(shí)不會(huì)釋放鎖package com.example.demo;
import java.util.Date;
public class Main {
public static void main(String[] args) throws InterruptedException {
Object lock = new Object();
new Thread(() -> {
synchronized (lock) {
for (int i = 0; i < 5; i++) {
System.out.println(new Date() + "\t" + Thread.currentThread().getName() + "\t" + i);
try { Thread.sleep(1000); } catch (InterruptedException e) { }
}
}
}).start();
Thread.sleep(1000);
new Thread(() -> {
synchronized (lock) {
for (int i = 0; i < 5; i++) {
System.out.println(new Date() + "\t" + Thread.currentThread().getName() + "\t" + i);
}
}
}).start();
}
}
因main方法中Thread.sleep(1000)所以上面的線程Thread-0先被執(zhí)行,當(dāng)循環(huán)第一次時(shí)就會(huì)Thread.sleep(1000)睡眠,因?yàn)閟leep并不會(huì)釋放鎖,所以Thread-1得不到執(zhí)行的機(jī)會(huì),所以直到Thread-0執(zhí)行完畢釋放鎖對(duì)象lock,Thread-1才能拿到鎖,然后執(zhí)行Thread-1;
object.wait(long timeout): 會(huì)釋放鎖public class Main {
public static void main(String[] args) throws InterruptedException {
Object lock = new Object();
new Thread(() -> {
synchronized (object) {
System.out.println(new Date() + "\t" + Thread.currentThread().getName() + "\t等待打印文件...");
try {
object.wait(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(new Date() + "\t" + Thread.currentThread().getName() + "\t打印結(jié)束。。。");
}
}).start();
// 先上面的線程先執(zhí)行
Thread.sleep(1000);
new Thread(() -> {
synchronized (object) {
for (int i = 0; i < 5; i++) {
System.out.println(new Date() + "\t" + Thread.currentThread().getName() + "\t" + i);
}
}
}).start();
}
}
sleep與wait的區(qū)別
sleep在Thread類中,wait在Object類中
sleep不會(huì)釋放鎖,wait會(huì)釋放鎖
sleep使用interrupt()來喚醒,wait需要notify或者notifyAll來通知
5.join()public class JoinTest {
public static void main(String[] args) {
new Thread(new ParentRunnable()).start();
}
}
class ParentRunnable implements Runnable {
@Override
public void run() {
// 線程處于new狀態(tài)
Thread childThread = new Thread(new ChildRunable());
// 線程處于runnable就緒狀態(tài)
childThread.start();
try {
// 當(dāng)調(diào)用join時(shí),parent會(huì)等待child執(zhí)行完畢后再繼續(xù)運(yùn)行
// 將某個(gè)線程加入到當(dāng)前線程
childThread.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
for (int i = 0; i < 5; i++) {
System.out.println(new Date() + "\t" + Thread.currentThread().getName() + "父線程 running");
}
}
}
class ChildRunable implements Runnable {
@Override
public void run() {
for (int i = 0; i < 5; i++) {
try { Thread.sleep(1000); } catch (InterruptedException e) {}
System.out.println(new Date() + "\t" + Thread.currentThread().getName() + "子線程 running");
}
}
}
程序進(jìn)入主線程,運(yùn)行Parent對(duì)應(yīng)的線程,Parent的線程代碼分兩段,一段是啟動(dòng)一個(gè)子線程,一段是Parent線程的線程體代碼,首先會(huì)將Child線程加入到Parent線程,join()方法會(huì)調(diào)用join(0)方法(join()方法是普通方法并沒有加鎖,join(0)會(huì)加鎖),join(0)會(huì)執(zhí)行while(isAlive()) { wait(0);} 循環(huán)判斷線程是否處于活動(dòng)狀態(tài),如果是繼續(xù)wait(0)知道isAlive=false結(jié)束掉join(0), 從而結(jié)束掉join(), 最后回到Parent線程體中繼續(xù)執(zhí)行其它代碼。
6.yield()
交出CPU的執(zhí)行時(shí)間,不會(huì)釋放鎖,讓線程進(jìn)入就緒狀態(tài),等待重新獲取CPU執(zhí)行時(shí)間,yield就像一個(gè)好人似的,當(dāng)CPU輪到它了,它卻說我先不急,先給其他線程執(zhí)行吧, 此方法很少被使用到public class Main {
public static void main(String[] args) {
new Thread(new Runnable() {
int sum = 0;
@Override
public void run() {
long beginTime=System.currentTimeMillis();
for (int i = 0; i < 99999; i++) {
sum += 1;
// 去掉該行執(zhí)行用2毫秒,加上271毫秒
Thread.yield();
}
long endTime=System.currentTimeMillis();
System.out.println("用時(shí):"+ (endTime - beginTime) + " 毫秒!");
}
}).start();
}
}
sleep(long millis) 與 yeid()
sleep(long millis): 需要指定具體睡眠的時(shí)間,不會(huì)釋放鎖,睡眠期間CPU會(huì)執(zhí)行其它線程,睡眠時(shí)間到會(huì)立刻執(zhí)行
yeid(): 交出CPU的執(zhí)行權(quán),不會(huì)釋放鎖,和sleep不同的時(shí)當(dāng)再次獲取到CPU的執(zhí)行,不能確定是什么時(shí)候,而sleep是能確定什么時(shí)候再次執(zhí)行。兩者的區(qū)別就是sleep后再次執(zhí)行的時(shí)間能確定,而yeid是不能確定的
yield會(huì)把CPU的執(zhí)行權(quán)交出去,所以可以用yield來控制線程的執(zhí)行速度,當(dāng)一個(gè)線程執(zhí)行的比較快,此時(shí)想讓它執(zhí)行的稍微慢一些可以使用該方法,想讓線程變慢可以使用sleep和wait,但是這兩個(gè)方法都需要指定具體時(shí)間,而yield不需要指定具體時(shí)間,讓CPU決定什么時(shí)候能再次被執(zhí)行,當(dāng)放棄到下次再次被執(zhí)行的中間時(shí)間就是間歇等待的時(shí)間
7.setDaemon(boolean on)
線程分兩種:
1.用戶線程:如果主線程main停止掉,不會(huì)影響用戶線程,用戶線程可以繼續(xù)運(yùn)行。
2.守護(hù)線程:如果主線程死亡,守護(hù)線程如果沒有執(zhí)行完畢也要跟著一塊死(就像皇上死了,帶刀侍衛(wèi)也要一塊死),GC垃圾回收線程就是守護(hù)線程public class Main {
public static void main(String[] args) {
Thread thread = new Thread() {
@Override
public void run() {
IntStream.range(0, 5).forEach(i -> {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "\ti=" + i);
});
}
};
thread.start();
for (int i = 0; i < 2; i++) {
System.out.println(Thread.currentThread().getName() + "\ti=" + i);
}
System.out.println("主線程執(zhí)行結(jié)束,子線程仍然繼續(xù)執(zhí)行,主線程和用戶線程的生命周期各自獨(dú)立。");
}
}
public class Main {
public static void main(String[] args) {
Thread thread = new Thread() {
@Override
public void run() {
IntStream.range(0, 5).forEach(i -> {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "\ti=" + i);
});
}
};
thread.setDaemon(true);
thread.start();
for (int i = 0; i < 2; i++) {
System.out.println(Thread.currentThread().getName() + "\ti=" + i);
}
System.out.println("主線程死亡,子線程也要陪著一塊死!");
}
}
總結(jié)
以上是生活随笔為你收集整理的java main是多线程的吗_Java多线程之线程及其常用方法的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: java jsoup解析html标签_J
- 下一篇: java设计模式观察者模式_Java设计