目录
线程
并行:某时间点上A和B同时发生
并发:微观上的交替运行
线程的父类: Thread
程序: 安装的软件, 例如: QQ WeChat LOL…
进程: 在运行的程序
线程: 进程中多个同时在执行的任务
主方法程序运行就是打开了一个进程, 进程中至少存在一个线程 - 主线程
开启多线程任务
创建多个线程对象 Thread,并发的,有先后顺序
注意: 不是哪个线程先start, 就先执行哪个线程,线程的执行顺序, 是不固定的
自定义线程类
自定义线程类, 继承 Thread -> 重写run方法-> 创建线程对象 -> start() 开启线程
- 特点:一个类只能有一个父类, 当他继承了Thread,这个类就只能是线程类, 有局限性
// 步骤1. 自定义的线程类, 继承 Thread
public class MyThread1 extends Thread {
// 步骤2: 重写run方法, 线程要执行的任务
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println("多线程执行了: " + i);
}
}
}
public static void main(String[] args) {
// 开启多线程任务
// 步骤3: 创建线程对象
MyThread1 t = new MyThread1();
// 步骤4: 开启线程
// t.run(); // 错误写法
t.start();
// 主方法的主线程任务
for (int i = 0; i < 10; i++) {
System.out.println("main: " + i);
}
自定义任务类
自定义任务类, 实现了Runnable接口 -> 重写run方法 -> 创建任务对象, 通过任务对象, 构造线程对象 -> start() 开启线程
- 特点:类实现 Runnable 接口, 还可以继承其他的类,和其他的接口,功能扩展性比较强, 没有太多局限性
// 步骤1. 自定义的任务类, 实现 Runnable
public class MyThread2 implements Runnable {
// 步骤2: 重写run方法, 线程要执行的任务
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println("多线程执行了: " + i);
}
}
}
public static void main(String[] args) {
// 开启多线程任务
// 步骤3.1: 创建任务对象
MyThread2 task = new MyThread2();
// 步骤3.2: 通过任务对象, 构造线程对象
Thread t = new Thread(task);
// 步骤4: 开启线程
t.start();
// 主方法的主线程任务
for (int i = 0; i < 10; i++) {
System.out.println("main: " + i);
}
}
匿名内部类实现多线程
以上两种方式的匿名内部类改写
public static void main(String[] args) {
Thread t = new Thread() {
public void run() {
Thread t = Thread.currentThread();//获得当前正在执行的线程对象
for (int i = 0; i < 10; i++) {
System.out.println(t.getName() + ": " + i);
}
}
};
t.start();
Runnable run = new Runnable() {
public void run() {
Thread t = Thread.currentThread();
for (int i = 0; i < 10; i++) {
System.out.println(t.getName() + ": " + i);
}
}
};
Thread t1 = new Thread(run);
t1.start();
构造方法和常用API
构造方法
1.new 自定义线程类(): 自定义类的构造方法, 随意
2.new Thread(): 无参构造器
3.new Thread(String): String->指定的线程名
4.new Thread(Runnable): Runnable->线程任务
5.new Thread(Runnable, String): Runnable->线程任务, String->指定的线程名
常用API
1.static Thread currentThread(): 获得当前正在执行的线程对象
2.String getName(): 获得线程对象的名字, 线程在创建时可以指定名字, 也可以默认分配名字
3.int getPriority(): 返回此线程的优先级
void setPriority(int): 设置线程的优先级
4.boolean isDaemon(): 测试这个线程是否是守护线程
void setDaemon(boolean): 设置这个线程是守护线程
5.static void sleep(long): 线程休眠指定时间
会有一个已检查异常, 所以必须要 try-catch
6.void join(): 等待调用这个方法的线程结束, 再继续后续代码
会有一个已检查异常, 所以必须要 try-catch
7.static void yield(): 主动放弃cpu的时间片
优先级和守护线程
- 优先级: 1~10
改变CPU分配时间片的概率,并不能改变先后顺序
int getPriority(): 返回此线程的优先级
void setPriority(int): 设置线程的优先级
Thread t1 = new Thread(run, "线程1");
Thread t2 = new Thread(run, "线程2");
Thread t3 = new Thread(run, "线程3");
// 默认优先级, 都是5
System.out.println("t1: " + t1.getPriority());
System.out.println("t2: " + t2.getPriority());
System.out.println("t3: " + t3.getPriority());
t1.setPriority(Thread.MIN_PRIORITY);
t3.setPriority(10); // 最大优先级,CPU分配的概率越大
t1.start();
t2.start();
t3.start();
- 守护线程 - 守护前台线程
当所有的前台线程结束, 守护线程也会自动结束
GC 就是守护线程
boolean isDaemon(): 测试这个线程是否是守护线程
void setDaemon(boolean): 设置这个线程是守护线程
public static void main(String[] args) {
Runnable run1 = new Runnable() {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
// 制造延迟, 让线程暂停一下
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("I will jump!");
}
System.out.println("aa a a a a a");
System.out.println("噗通...!");
}
};
Runnable run2 = new Runnable() {
@Override
public void run() {
while (true) {//死循环
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("you jump, I jump!");
}
}
};
Thread rose = new Thread(run1);
Thread jack = new Thread(run2);
// 默认所有线程都不是守护线程
System.out.println("jack是不是守护线程: " + jack.isDaemon());
// 设置守护线程
jack.setDaemon(true);
rose.start();
jack.start();//rose结束后,jack也跟着结束死循环
}
线程同步的安全问题
多线程中资源共享 - 抢夺资源
synchronized
synchronized: 同步锁, 锁方法/代码块[借助对象],只能同时被一个线程持有,当线程执行完这个方法, 才会将锁释放
加到方法上, 同步方法锁
加到代码上, 借助对象, 通常是锁this对象,确保同步的线程, 对象共享即可
锁静态方法: 锁 类.class(类的字节码) 对象
// 模拟当前票的余量
public class Ticket {
// 票的余量是100张
public int count = 100;
private Object object = new Object();
public void saleTicket() {
// synchronized (this) {
synchronized (object) {
if (count == 0) {
throw new RuntimeException("票卖完了!");
}
System.out.println(Thread.currentThread().getName() + "正在出票: " + count);
count--;
}
System.out.println(Thread.currentThread().getName()+"卖完一张票");
}
}
public class MyThread extends Thread {
private Ticket ticket;
public MyThread(Ticket ticket, String name) {
super(name);
this.ticket = ticket;
}
public void run() {
// 卖票
while (ticket.count > 0) {
ticket.saleTicket();
try {
Thread.sleep((long)(Math.random() * 90 + 10));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
// 100张票
Ticket ticket = new Ticket();
MyThread t1 = new MyThread(ticket, "窗口1");
MyThread t2 = new MyThread(ticket, "窗口2");
MyThread t3 = new MyThread(ticket, "窗口3");
t1.start();
t2.start();
t3.start();
//窗口1正在出票: 4
//窗口1卖完一张票
//窗口3正在出票: 3
//窗口3卖完一张票
//窗口1正在出票: 2
//窗口1卖完一张票
//窗口3正在出票: 1
//窗口3卖完一张票
//加锁就可以让最后一个线程结束后把锁拿走,其他线程就没办法进来了,这样子就不会出现票数为负数的情况
}
lock锁
Lock - 接口
实现类: ReentrantLock lock = new ReentrantLock();
加锁: 锁对象.lock();
解锁: 锁对象.unlock();
// 模拟当前票的余量
public class Ticket {
// 票的余量是100张
public int count = 100;
// 创建锁对象
private ReentrantLock lock = new ReentrantLock();
public void saleTicket() {
// 加上锁
lock.lock();
if (count == 0) {
throw new RuntimeException("票卖完了!");
}
System.out.println(Thread.currentThread().getName() + "正在出票: " + count);
count--;
// 打开锁
lock.unlock();
System.out.println(Thread.currentThread().getName() + "卖完一张票");
}
线程状态
- new - 对象
- start() -> 就绪状态/可执行状态 Runnable
- cpu分配时间片 -> 运行状态 running
- run方法结束 -> 死亡状态/被终止
- run->就绪状态: 时间片到期/yield()
- run->阻塞状态
1.锁阻塞: 同步锁
2.计时等待: sleep(long) wait(long)
3.无限等待: wait()
唤醒: notify() notifyAll()
4.a.join(): 调用这个方法的线程进入阻塞
Block阻塞状态图
线程通信
两个线程有共享数据, 线程之间有动作交互
notify() - 每次只能唤醒一个线程, 只能唤醒等待时间久的那个线程
notifyAll() - 唤醒所有正在等待的线程
wait() -> 只能被notify() 或者 notifyAll() 唤醒
wait(long) -> 到时间以后, 自动醒来
- void wait():在其他线程调用此对象的 notify() 方法或 notifyAll() 方法前,导致当前线程等待。 简单来说,就是让当前线程进入阻塞,直到被唤醒,并且会释放调用当前线程占用的对象锁。
- void notify() :唤醒在此对象监视器上等待的单个线程。 如果所有线程都在此对象上等待,则会选择唤醒其中一个线程。选择是任意性的,并在对实现做出决定时发生。线程通过调用其中一个 wait 方法,在对象的监视器上等待。简单来说,就是唤醒被wait方法进入阻塞的线程。
- sleep虽然也能使当前线程进入阻塞状态,但是是不会释放锁和资源的
案例:图片加载显示下载
1.线程1 先负责图片的加载任务. 1%~100% -> 加载完成
再负责图片的下载任务. 1%~100% -> 下载完成
要求图片显示完才能下载
2.线程2 负责图片的显示任务. 要求图片加载完才能显示
public class Picture {
public boolean isLoad; // 标记图片是否加载完成
public boolean isShow; // 标记图片是否限时完成
}
public class LoadPicture extends Thread {
private Picture picture;
public LoadPicture(Picture picture) {
this.picture = picture;
}
public void run() {
// 图片进入加载过程
System.out.println("图片开始加载....");
for (int i = 0; i < 100; i++) {
System.out.println("正在加载: " + (i+1) + "%");
}
System.out.println("图片加载完成");
// 设置图片状态为已加载完成
picture.isLoad = true;
// 图片要开始下载了 - 唤醒正在等待的"显示线程"
synchronized (picture) {
picture.notify();
}
// 等待图片显示完成
if (!picture.isShow) {
synchronized (picture) {
try {
picture.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
// 假设图片已经限时完成
System.out.println("图片开始下载....");
for (int i = 0; i < 100; i++) {
System.out.println("正在下载: " + (i+1) + "%");
}
System.out.println("图片下载完成");
}
public class ShowPicture extends Thread {
private Picture picture ;
public ShowPicture(Picture picture ) {
this.picture = picture;
}
public void run() {
System.out.println("等待图片加载完成....");
// 等待图片状态 isLoad = true
// 当图片没有加载完成, 显示线程 等待
if (!picture.isLoad) {
synchronized (picture) {
try {
picture.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
// 假设图片已经加载完成
System.out.println("显示图片!");
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("图片显示完成!");
// 改变图片显示状态
picture.isShow = true;
// 图片开始下载了 - 唤醒"下载线程"
synchronized (picture) {
picture.notify();
}
}
public class Demo {
public static void main(String[] args) {
Picture picture = new Picture();
LoadPicture load = new LoadPicture(picture);
ShowPicture show = new ShowPicture(picture);
load.start();
show.start();
}
}
图片开始加载....
等待图片加载完成....
正在加载: 1%
正在加载: 2%
正在加载: 3%
正在加载: 4%
正在加载: 5%
正在加载: 6%
正在加载: 96%
正在加载: 97%
正在加载: 98%
正在加载: 99%
正在加载: 100%
图片加载完成
显示图片!
图片显示完成!
图片开始下载....
正在下载: 1%
正在下载: 2%
正在下载: 3%
正在下载: 4%
正在下载: 99%
正在下载: 100%
图片下载完成
案例:包子铺
包⼦铺线程⽣产包⼦,吃货线程消费包⼦。当包⼦没有时( 包⼦状态为false ),吃货线程等待,包⼦铺线程⽣产包⼦( 即包⼦状态为true ),并通知吃货线程( 解除吃货的等待状态 ),因为已经有包⼦了,那么包⼦铺线程进⼊等待状态。接下来,吃货线程能否进⼀步执⾏则取决于锁的获取情况。如果吃货获取到锁,那么就执⾏吃包⼦动作,包⼦吃完( 包⼦状态为false ),并通知包⼦铺线程( 解除包⼦铺的等待状态 ),吃货线程进⼊等待。包⼦铺线程能否进⼀步执⾏则取决于锁的获取情况。
public class BaoZi {
String pier ;
String xianer ;
boolean flag = false ; // 包⼦资源 是否存在 包⼦资源状态
}
/*
消费者: 吃货 线程
run: 1.判断包子状态false
等待包子铺生产包子
2.判断包子状态true
吃包子 baozi.flag = false
唤醒包子铺线程
*/
public class ChiHuo extends Thread{
private BaoZi bz;
public ChiHuo(String name, BaoZi bz) {
super(name);
this.bz = bz;
}
@Override
public void run() {
while(true) {
synchronized (bz) {
if (bz.flag == false) { // 没包⼦
try {
bz.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("吃货正在吃" + bz.pier + bz.xianer + "包⼦");
bz.flag = false;
bz.notify();
}
}
}
/*
生产者: 包子铺 线程
run: 1.判断包子状态false
生产包子 baozi.flag = true
唤醒吃货线程, 来吃包子
2.判断包子状态true
不需要生产
等待 wait
*/
public class BaoZiPu extends Thread{
private BaoZi bz;
public BaoZiPu(String name, BaoZi bz) {
super(name);
this.bz = bz;
}
@Override
public void run() {
int count = 0;
// 造包⼦
while (true) {
// 同步
synchronized (bz) {
if (bz.flag == true) { // 包⼦资源 存在
try {
bz.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 没有包⼦ 造包⼦
System.out.println("包⼦铺开始做包⼦");
if (count % 2 == 0) {
// 冰⽪ 五仁
bz.pier = "冰⽪";
bz.xianer = "五仁";
} else {
// 薄⽪ ⽜⾁⼤葱
bz.pier = "薄⽪";
bz.xianer = "⽜⾁⼤葱";
}
count++;
bz.flag = true;
System.out.println("包⼦造好了:" + bz.pier + bz.xianer);
System.out.println("吃货来吃吧");
// 唤醒等待线程 (吃货)
bz.notify();
}
}
}
public static void main(String[] args) {
// 等待唤醒案例
BaoZi bz = new BaoZi();
ChiHuo ch = new ChiHuo("吃货", bz);
BaoZiPu bzp = new BaoZiPu("包⼦铺", bz);
ch.start();
bzp.start();
}
包⼦铺开始做包⼦
包⼦造好了:冰⽪五仁
吃货来吃吧
吃货正在吃冰⽪五仁包⼦
包⼦铺开始做包⼦
包⼦造好了:薄⽪⽜⾁⼤葱
吃货来吃吧
吃货正在吃薄⽪⽜⾁⼤葱包⼦
包⼦铺开始做包⼦
包⼦造好了:冰⽪五仁
吃货来吃吧
吃货正在吃冰⽪五仁包⼦
......死循环
线程池
Executors 工厂类中的方法,是⼀个容纳多个线程的容器,其中的线程可以反复使⽤,省去了频繁创建线程对象的操作,⽆需反复创建线程⽽消耗过多资源。
线程池种类
-
newCachedThreadPool(): 创建一个根据需要创建新线程的线程池,但在可用时将重新使用以前构造的线程。
-
newFixedThreadPool(int nThreads): 创建一个线程池,该线程池重用固定数量的从共享无界队列中运行的线程。
-
newScheduledThreadPool(int corePoolSize): 创建一个线程池,可以调度命令在给定的延迟之后运行,或定期执行。
-
newSingleThreadExecutor(): 创建一个使用从无界队列运行的单个工作线程的执行程序。
线程池开启线程任务的API
- submit(Runnable/Callable)有返回值,提交指定的任务去执行并且返回Future对象
- execute(Runnable)
public static void main(String[] args) {
// 通过线程任务, 来获得线程对象并且直接开始线程
Runnable run = new Runnable() {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName() + ": " + i);
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
};
// 创建一个固定线程数量的线程池, 创建好线程池的时候, 就已经有了三个线程对象
ExecutorService pool = Executors.newFixedThreadPool(3);
// 将线程任务交给线程池 -- 线程对象可以反复使用
pool.execute(run);
Future f = pool.submit(run); // f = null ,submit(Runnable/Callable)有返回值,提交指定的任务去执行并且返回Future对象
pool.submit(run);
pool.submit(run);
// 如果没有关闭线程池, 线程对象依然在, 程序就不会结束
// 手动关闭线程池 - 会自动将里面的线程对象销毁
pool.shutdown();
线程池的好处/why使用
- 降低资源消耗。减少了创建和销毁线程的次数,每个⼯作线程都可以被重复利⽤,可执⾏多个任务。
- 提⾼响应速度。当任务到达时,任务可以不需要的等到线程创建就能⽴即执⾏。
- 提⾼线程的可管理性。可以根据系统的承受能⼒,调整线程池中⼯作线线程的数⽬,防⽌因为消耗过多的内存,⽽把服务器累趴下(每个线程需要⼤约1MB内存,线程开的越多,消耗的内存也就越⼤,最后死机)。
Callable
Callable(线程任务, 只能用在线程池) -> Runnable
new Thread(new Runnable(){});
new Thread(new Callable(){}); // — 错误的!!
Callable对象只能在 : Future f = pool.submit(Callable);
f.get() -> 得到call方法的返回值
可能会遇到阻塞
f.get(long, TimeUnit.xx) -> 超时继续
public static void main(String[] args) throws ExecutionException, InterruptedException {
// 创建一个固定线程数量的线程池, 创建好线程池的时候, 就已经有了三个线程对象
ExecutorService pool = Executors.newFixedThreadPool(3);
// 通过线程任务, 来获得线程对象并且直接开始线程
Callable run = new Callable<Date>(){
@Override
public Date call() throws Exception{
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName() + ": " + i);
}
Thread.sleep(10000);
return new Date();
}
};
// 将线程任务交给线程池 -- 线程对象可以反复使用
Future<Date> f = pool.submit(run);
Date d = f.get(); // 得到Callable里面call方法的返回值
System.out.println(d);
/*try {
Date date = f.get(3, TimeUnit.SECONDS);
System.out.println(date);
} catch (TimeoutException e) {
System.out.println("结果超时了");
}*/
System.out.println("主线程继续");
// 如果没有关闭线程池, 线程对象依然在, 程序就不会结束
// 手动关闭线程池 - 会自动将里面的线程对象销毁
pool.shutdown();
}
pool-1-thread-1: 0
pool-1-thread-1: 1
pool-1-thread-1: 2
pool-1-thread-1: 3
pool-1-thread-1: 4
pool-1-thread-1: 5
pool-1-thread-1: 6
pool-1-thread-1: 7
pool-1-thread-1: 8
pool-1-thread-1: 9
Thu Jul 30 20:26:34 CST 2020
主线程继续