多線程
目的
區(qū)分進程和線程的概念
進程:正在運行的一個程序QQ IDE 瀏覽器
系統(tǒng)會為這個進程分配獨立的內存資源
線程:具體執(zhí)行任務的最小單位
一個進程最少擁有一個線程(主線程 運行起來就執(zhí)行的線程)
線程之間是共享內存資源的(進程申請的)
線程之間可以通信(數據傳遞,多數為主線程和子線程)
每一個線程都有自己的運行回路(生命周期)
1個進程中可以開啟多條線程,每條線程可以并行(同時)執(zhí)行不同的任務
為什么需要創(chuàng)建子線程
如果在主線程中存在有比較耗時的操作:下載視頻 上傳文件 數據處理
這些操作會阻塞主線程 ,后面的任務必須等這些任務執(zhí)行完畢
之后才能執(zhí)行,用戶體驗比較差
為了不阻塞主線程 需要將耗時的任務放在子線程中去處理
技術
1.如何創(chuàng)建一個線程 Thread Runnable
2.線程的同步 synchronized ReentrantLock
3.主線程和子線程之間使用接口回調數據
3.線程之間的通信:synchronized (wait notify notifyAll)
ReentrantLock lock;
Condition c = lock.newCondition();
await signal signalAll
技術實現
- 如何創(chuàng)建一個線程
1.創(chuàng)建一個繼承Thread 實現run方法 方法里面就是具體需要執(zhí)行的代碼
class TestThread extends Thread{
@Override
public void run() {
String name = Thread.currentThread().getName();
for (int i = 0; i < 100; i++) {
System.out.println(name+":"+(i+1));
if (this != MyClass.tt2) {
if (i == 9) {
try {
MyClass.tt2.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
super.run();
}
}
創(chuàng)建該類的對象來調用start方法
將tt2對象靜態(tài)化,實現共享
static TestThread tt2;
// main 方法里面執(zhí)行的代碼 是在主線程里面執(zhí)行的
// 主線程的名稱為main
String name = Thread.currentThread().getName();
System.out.println(name);
// 創(chuàng)建Thread的對象
TestThread tt = new TestThread();
// 設置線程的名稱
tt.setName("子線程1");
// 開啟任務
tt.start();
// for (int i = 0; i < 5; i++) {
// System.out.println("main:"+(i+1));
// }
tt2 = new TestThread();
// 設置線程的名稱
tt2.setName("子線程2");
// 開啟任務
tt2.start();
2.創(chuàng)建一個任務:創(chuàng)建一個類實現Runnable接口
class aThread extends Thread implements Runnable{
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName()+":"+(i+1));
}
}
}
aThread at = new aThread();
// 使用Thread操作這個任務
Thread t = new Thread(at);
t.setName("子線程1");
t.start();
Thread t2 = new Thread(at);
t2.setName("子線程2");
t2.start();
這個任務只做一次
Thread t = new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName()+":"+(i+1));
}
}
});
t.setName("子線程1");
t.start();
創(chuàng)建線程的同時 直接開線程任務
// 不需要操作線程對象本身
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName()+":"+(i+1));
}
}
}).start();
使用Lambda表達式
// 閱讀性差
new Thread(()->{
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName()+":"+(i+1));
}
}).start();
-
線程安全
多個線程操作同一資源,線程無法確定自己什么時候被阻塞
容易導致數據錯誤
synchronized Lock 加鎖解鎖
synchronized 同步監(jiān)聽器 需要一把鎖任何一個對象都有自己的一把鎖
如果多個線程操作同一個代碼塊 并且需要同步
那么就只能操作同一把鎖1.同步代碼塊
synchronized (監(jiān)聽器/對象/鎖){}
2.同步方法 同步監(jiān)聽器是當前對象本身
必須確保多個對象調用的同步方法時操作的同一個對象
public synchronized void test()
本質上就是同步代碼塊等價于 synchronized(this){ test(); } wait notify notifyAll Lock ReentrantLock 可重入鎖
火車站賣票
// 全國的買票系統(tǒng)就一個
// 重慶 上海
用于賣票的任務
class Ticket implements Runnable{
// 定義所有車票的數量
public static int nums = 100;
String name;
public Ticket(String name){
this.name = name;
}
static final Object obj = new Object();
static ReentrantLock lock = new ReentrantLock();
Condition condition = lock.newCondition();
@Override
public void run() {
for (int i = 1; i <= 100; i++) {
// 判斷有沒有票
lock.lock();
// synchronized (obj) {
if (nums > 0) {
System.out.println(name + "出票:" + i);
nums--;
try {
condition.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
} else {
break;
}
lock.unlock();
}
// }
}
}
Ticket ticket1 = new Ticket("重慶");
Thread t1 = new Thread(ticket1);
t1.start();
Ticket ticket2 = new Ticket("上海");
Thread t2 = new Thread(ticket2);
t2.start();
使用接口實現線程間數據回調
Person類
Agent類 線程里面
public class Agent extends Thread{
AgentInterface target;
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
System.out.println("開始找房子");
System.out.println("---------");
System.out.println("房子找到了 即將返回數據");
target.callBack("房子在西南大學");
super.run();
}
public interface AgentInterface{
void callBack(String desc);
}
}
public class Person implements Agent.AgentInterface {
public void needHouse(){
Agent xw = new Agent();
xw.target = this ;
xw.start();
}
@Override
public void callBack(String desc) {
System.out.println("我是小王,接收到你的數據了:"+desc);
}
}
在main函數中創(chuàng)建對象調用函數
Person xw = new Person();
xw.needHouse();

心得
主線程的使用注意:別將比較耗時的操作放到主線程中。
耗時操作會卡住主線程,嚴重影響UI的流暢度,給用戶一種“卡”的壞體驗