文章目錄
1、進程與線程
進程(Process) 是計算機中的程序關於某數據集合上的一次運行活動,是系統進行資源分配和調度的基本單位,是操作系統結構的基礎。線程(Thread) 是操作系統能夠進行運算調度的最小單位。它被包含在進程之中,是進程中的實際運作單位。
程序啓動運行main方法的時候,Java虛擬機會啓動一個進程,並創建主線程main。隨着調用Thread對象的 start(),另外一個新的線程也啓動了,整個應用就在多線程下運行。 所以,一個進程可以包括多個線程。而多進程是對於操作系統和CPU而言的,比如電腦同時啓動了QQ、微信、音樂軟件等應用程序時,每個程序都是一個進程,此時CPU就是多進程的。
2、創建多線程
2.1、繼承Thread類
/**
* @author RuiMing Lin
* @date 2020-03-12 14:19
*/
public class MyThread extends Thread {
@Override
public void run() {
System.out.println("mythread1");
}
public static void main(String[] args) {
MyThread mt = new MyThread();
mt.start();
}
}
2.2、實現Runnable接口
/**
* @author RuiMing Lin
* @date 2020-03-12 14:22
*/
public class MyRunnable implements Runnable{
@Override
public void run() {
System.out.println("MyRunnable 1");
}
public static void main(String[] args) {
MyRunnable myRunnable = new MyRunnable();
Thread thread = new Thread(myRunnable);
thread.start();
}
}
2.3、使用匿名內部類實現
/**
* @author RuiMing Lin
* @date 2020-03-12 14:25
*/
public class Demo1 {
public static void main(String[] args) {
Thread thread1 = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("使用匿名內部類實現");
}
});
thread1.start();
}
}
2.4、實現Runnable接口的好處
實現Runnable接口比繼承Thread類所具有的優勢:
- 可以避免java中的單繼承的侷限性;
- 增加程序的健壯性,實現解耦操作;
- 線程池只能放入實現Runable或Callable類線程,不能直接放入繼承Thread的類。
2.5、使用Callable和Future創建線程
Callable是一個接口,有點類似於Runnable接口,不過較之更加強大。Callable提供了一個類似於run方法的call(),但call()更加強大。主要體現在兩點:call()可以有返回值,call()可以拋出異常。
理論上我們可以類似於上述匿名內部類的方法將Callable接口傳遞給Thread的構造方法,但實際上不行,因爲Thread類提供的構造方法沒有傳遞Callable參數類型的,而Callable又不是Runnable的子接口。所以就有了Future接口,該接口是Runnable的子接口,並有一個實現類FutureTask封裝了Callable接口。
import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;
/**
* @author RuiMing Lin
* @date 2020-03-12 14:25
*/
public class Demo1 {
public static void main(String[] args) throws Exception{
FutureTask<Integer> futureTask = new FutureTask<Integer>(new Callable<Integer>() {
@Override
public Integer call() throws Exception {
int i = 0;
for ( ;i < 10; i++){
System.out.println(Thread.currentThread().getName() + "i = " + i);
}
return i;
}
});
new Thread(futureTask).start();
Integer integer = futureTask.get();//返回call()的返回值,必須等到子棧結束後纔會得到返回值,會導致程序阻塞
System.out.println("integer = " + integer);
}
}
3、線程的生命週期
新建態: 當一個線程被new出來,也就是被創建出來的時候;
就緒態: 被創建的線程調用start()方法
阻塞態線程休眠時間到或者獲得同步鎖等等;
運行態: 就緒態線程獲得CPU資源;
阻塞態: 運行態線程調用sleep()方法進行休眠或者等待同步鎖中;
死亡態: 線程被關閉或者出現異常等等。
4、幾種特殊線程
4.1、join線程
Thread的join()可以讓一個線程等待調用該方法的線程完全完成的方法:
/**
* @author RuiMing Lin
* @date 2020-03-13 20:39
*/
public class MyRunnable1 implements Runnable{
@Override
public void run() {
for (int i = 0; i < 20; i++) {
System.out.println(Thread.currentThread().getName() + "執行了" + i + "次");
}
}
public static void main(String[] args) {
for (int i = 0; i < 100; i++) {
if (i == 20){
Thread thread = new Thread(new MyRunnable1());
thread.setName("我的線程");
thread.start();
try {
thread.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("主線程main方法執行了" + i + "次");
}
}
}
結果輸出爲:
如果不使用join()方法:
/**
* @author RuiMing Lin
* @date 2020-03-13 20:39
*/
public class MyRunnable1 implements Runnable{
@Override
public void run() {
for (int i = 0; i < 20; i++) {
System.out.println(Thread.currentThread().getName() + "執行了" + i + "次");
}
}
public static void main(String[] args) {
for (int i = 0; i < 100; i++) {
if (i == 20){
Thread thread = new Thread(new MyRunnable1());
thread.setName("我的線程");
thread.start();
// try {
// thread.join();
// } catch (InterruptedException e) {
// e.printStackTrace();
// }
}
System.out.println("主線程main方法執行了" + i + "次");
}
}
}
結果輸出爲:
原因分析:當主線程的i = 20時,創建自定義線程,開啓自定義線程並調用join方法,此時主線程main()被掛起,直到自定義線程完全執行結束後才放行讓主線程執行。
4.2、守護線程
守護線程表示只要被守護的線程一結束,它也就被結束了。調用方法:
thread.setDaemon(true);
4.3、休眠線程
運行態的線程調用sleep()進入休眠線程,調用方法:
Thread.sleep(1000);
5、線程安全與線程同步
5.1、安全問題
先拋出一個線程之間的安全問題:如今有十張火車票,兩個窗口同時售賣
/**
* @author RuiMing Lin
* @date 2020-03-13 15:57
*/
public class Ticket1 implements Runnable{
private int ticket = 10; // 表示有十張火車票
@Override
public void run() {
for (int i = 0; i < 10; i++) {
if (ticket > 0){
System.out.println(Thread.currentThread().getName()
+ "賣了編號爲" + ticket + "的火車票");
ticket --;
try {
Thread.sleep(500);
}catch (InterruptedException e){
e.printStackTrace();
}
}
}
}
public static void main(String[] args) {
Ticket1 ticket = new Ticket1();
Thread thread1 = new Thread(ticket);
Thread thread2 = new Thread(ticket);
thread1.setName("窗口A");
thread2.setName("窗口B");
thread1.start();
thread2.start();
}
}
結果輸出爲:
窗口A賣了編號爲10的火車票
窗口B賣了編號爲9的火車票
窗口A賣了編號爲8的火車票
窗口B賣了編號爲8的火車票
窗口A賣了編號爲6的火車票
窗口B賣了編號爲6的火車票
窗口B賣了編號爲4的火車票
窗口A賣了編號爲4的火車票
窗口B賣了編號爲2的火車票
窗口A賣了編號爲2的火車票
很明顯,這樣的結果是不滿足需求的。原因分析:因爲多線程是CPU隨機執行的,當線程一執行System.out.println(Thread.currentThread().getName()+ “賣了編號爲” + ticket + “的火車票”)語句時,還未執行ticket --跳轉到線程二,此時就會出現數據沒有及時更新、同時售賣同一張票的情況。
5.2、解決方案一 ---- 同步代碼塊
對需要連續執行的代碼加一把鎖,稱爲同步代碼塊。代碼代碼塊中的代碼必須執行完纔會切換到其他線程,這樣就能保證了線程之間的安全。
/**
* @author RuiMing Lin
* @date 2020-03-13 15:57
*/
public class Ticket2 implements Runnable{
private int ticket = 10; // 表示有十張火車票
@Override
public void run() {
for (int i = 0; i < 10; i++) {
synchronized (this){
if (ticket > 0){
System.out.println(Thread.currentThread().getName()
+ "賣了編號爲" + ticket + "的火車票");
ticket --;
try {
Thread.sleep(400);
}catch (InterruptedException e){
e.printStackTrace();
}
}
}
}
}
public static void main(String[] args) {
Ticket2 ticket = new Ticket2();
Thread thread1 = new Thread(ticket);
Thread thread2 = new Thread(ticket);
thread1.setName("窗口A");
thread2.setName("窗口B");
thread1.start();
thread2.start();
}
}
結果輸出爲:
窗口A賣了編號爲10的火車票
窗口B賣了編號爲9的火車票
窗口B賣了編號爲8的火車票
窗口B賣了編號爲7的火車票
窗口B賣了編號爲6的火車票
窗口B賣了編號爲5的火車票
窗口B賣了編號爲4的火車票
窗口B賣了編號爲3的火車票
窗口B賣了編號爲2的火車票
窗口A賣了編號爲1的火車票
可以看出,這樣就滿足需求了。
5.3、解決方案二 ---- 同步方法
/**
* @author RuiMing Lin
* @date 2020-03-13 15:57
*/
public class Ticket2 implements Runnable{
private int ticket = 10; // 表示有十張火車票
private synchronized void sale(){
if (ticket > 0){
System.out.println(Thread.currentThread().getName()
+ "賣了編號爲" + ticket + "的火車票");
ticket --;
try {
Thread.sleep(400);
}catch (InterruptedException e){
e.printStackTrace();
}
}
}
@Override
public void run() {
for (int i = 0; i < 10; i++) {
sale();
}
}
public static void main(String[] args) {
Ticket2 ticket = new Ticket2();
Thread thread1 = new Thread(ticket);
Thread thread2 = new Thread(ticket);
thread1.setName("窗口A");
thread2.setName("窗口B");
thread1.start();
thread2.start();
}
}
結果輸出:
窗口A賣了編號爲10的火車票
窗口A賣了編號爲9的火車票
窗口A賣了編號爲8的火車票
窗口A賣了編號爲7的火車票
窗口A賣了編號爲6的火車票
窗口A賣了編號爲5的火車票
窗口B賣了編號爲4的火車票
窗口A賣了編號爲3的火車票
窗口B賣了編號爲2的火車票
窗口A賣了編號爲1的火車票
6、線程通信
6.1、API
wait():導致當前線程等待;
notify():喚醒共享當前同一把鎖的線程其中之一;
notifyAll():喚醒共享當前同一把鎖的所有線程。
6.2、案例
需求:實現1與2交替出現
/**
* @author RuiMing Lin
* @date 2020-03-13 21:03
*/
public class Communication {
public static Object lock = new Object(); // 定義一把鎖
public static void main(String[] args) {
MyThread1 myThread1 = new MyThread1();
MyThread2 myThread2 = new MyThread2();
myThread1.start();
myThread2.start();
}
}
// 定義通訊類一
class MyThread1 extends Thread{
@Override
public void run() {
for (int i = 0; i < 10; i++) {
synchronized (Communication.lock){ //互相通信的線程必須共享同一把鎖
System.out.print(1);
Communication.lock.notify();
try {
Communication.lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
class MyThread2 extends Thread{
@Override
public void run() {
for (int j = 0; j < 10; j++) {
synchronized (Communication.lock){
System.out.print(2);
Communication.lock.notify();
try {
Communication.lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
7、線程池
7.1、線程池介紹
線程池是一個容納多個線程的容器,其中的線程可以反覆使用,省去了頻繁創建線程對象的操作, 無需反覆創建線程而消耗過多資源。這樣做有三個好處:1.降低資源消耗,減少了創建和銷燬線程的次數,每個工作線程都可以被重複利用,可執行多個任務;2. 提高響應速度,當任務到達時,任務可以不需要的等到線程創建就能立即執行;3. 提高線程的可管理性。可以根據系統的承受能力,調整線程池中工作線線程的數目,防止因爲消耗過多的內 存,而把服務器累趴下。
7.2、線程池的使用
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* @author RuiMing Lin
* @date 2020-03-13 21:22
*/
public class Pool {
public static void main(String[] args) {
// 1.創建一個容量爲5的線程池
ExecutorService service = Executors.newFixedThreadPool(5);
// 2.創建Runnable對象
MyRunnable runnable1 = new MyRunnable();
MyRunnable runnable2 = new MyRunnable();
MyRunnable runnable3 = new MyRunnable();
MyRunnable runnable4 = new MyRunnable();
MyRunnable runnable5 = new MyRunnable();
// 3.調用線程池方法
service.submit(runnable1);
service.submit(runnable2);
service.submit(runnable3);
service.submit(runnable4);
service.submit(runnable5);
// 4.無需關閉線程,run()結束後自動將線程歸還給線程池
// 5.關閉線程池
service.shutdown();
}
}
有錯誤的地方敬請指出!覺得寫得可以的話麻煩給個贊!歡迎大家評論區或者私信交流!