线程
线程的父类: Thread
程序: 安装的软件, 例如: QQ、WeChat …
进程: 在运行的程序
线程: 进程中多个同时在执行的任务
主方法程序运行就是打开了一个进程, 进程中至少存在一个线程 - 主线程
创建多线程方法
注意:不是哪个线程先start, 就先执行哪个线程, 线程的执行顺序是不固定的
1、通过自定义线程类, 继承 Thread
/*
一个类只能有一个父类, 当它继承了Thread,
这个类就只能是线程类, 有局限性
*/
// 步骤1. 自定义的线程类, 继承 Thread
public class MyThread extends Thread {
// 步骤2: 重写run方法, 线程要执行的任务
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println("多线程执行了: " + i);
}
}
}
public class Demo01Thread {
public static void main(String[] args) {
// 开启多线程任务
// 步骤3: 创建线程对象
MyThread t = new MyThread();
// 步骤4: 开启线程
// t.run(); // 错误写法
t.start();
// 主方法的主线程任务
for (int i = 0; i < 10; i++) {
System.out.println("main: " + i);
}
}
}
2、通过自定义任务类, 实现Runnable接口
/*
类实现 Runnable 接口, 还可以继承其他的类,和其他的接口
功能扩展性比较强, 没有太多局限性
相同任务的线程, 任务对象可以重复使用
*/
// 步骤1. 自定义的任务类, 实现 Runnable
public class MyRunnable implements Runnable {
// 步骤2: 重写run方法, 线程要执行的任务
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println("多线程执行了: " + i);
}
}
}
public class Demo02Thread {
public static void main(String[] args) {
// 开启多线程任务
// 步骤3.1: 创建任务对象
MyRunnable task = new MyRunnable();
// 步骤3.2: 通过任务对象, 构造线程对象
Thread t = new Thread(task);
// 步骤4: 开启线程
t.start();
// 主方法的主线程任务
for (int i = 0; i < 10; i++) {
System.out.println("main: " + i);
}
}
}
3、用匿名内部类创建线程
// 用匿名内部类创建线程
public class Demo {
public static void main(String[] args) {
// 继承 Thread
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
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();
}
}
构造方法
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)默认的都是5
改变CPU分配时间片的概率
守护线程 - 前台线程
(默认所有的线程都不是守护线程)
守护线程:当所有的前台线程结束,守护线程也会自动结束
例:GC 就是守护线程
线程同步(多个线程, 共享资源)
- synchronized: 同步锁, 只能同时被一个线程持有,
当线程执行完这个方法, 才会将锁释放
- 加到方法上, 同步方法锁
- 加到代码上, 借助对象, 通常是this
(确保同步的线程, 对象共享即可)
/*
在方法上加锁, 称为同步方法锁
锁在方法上, 实际上就是在this对象上加锁
*/
// 模拟卖票
public synchronized void saleTicket() {
if (count == 0) {
throw new RuntimeException("票卖完了!");
}
System.out.println(Thread.currentThread().getName()+"正在出票: " + count);
count--;
System.out.println(Thread.currentThread().getName()+"卖完一张票");
}
/*
synchronized使用原则: 尽量将少的代码加锁
可以只将一部分代码加锁, 同步代码锁, 需要借助一个对象
加锁的对象: 可以是任意对象, 只需要确保多个同步的线程, 对象唯一就可以了
通常使用this对象
对象锁: 一个对象只能加一把锁, 并且只能同时被一个线程持有
*/
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()+"卖完一张票");
}
- Lock - 接口
实现类: ReentrantLock lock = new ReentrantLock();
- 加锁: 锁对象.lock();
- 解锁: 锁对象.unlock();
// 创建锁对象
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() + "卖完一张票");
}
void join()
例:图片加载(使用两个线程)
public class Demo {
public static void main(String[] args) {
// 加载图片
Thread load = new Thread(){
public void run() {
System.out.println("图片开始加载....");
for (int i = 0; i < 100; i++) {
System.out.println("正在加载: " + (i+1) + "%");
}
System.out.println("图片加载完成");
}
};
// 显示图片
Thread show = new Thread(){
public void run() {
System.out.println("等待图片加载完成....");
// 等待 load 线程结束
try {
load.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("显示图片!");
}
};
load.start();
show.start();
}
}
线程的状态图
线程通信
两个线程有共享数据, 线程之间有动作交互
(都需要加锁)
- wait(): 等待(只能被notify() 或者 notifyAll() 唤醒)
- wait(long):到时间以后,自动醒来
- notify(): 每次只能唤醒一个线程, 只能唤醒等待时间久的那个线程
- notifyAll() :唤醒所有正在等待的线程
例:图片加载、下载、显示(使用两个线程)
// 主函数
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();
}
}
// 共享资源(图片类)
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() {
synchronized (picture) {
// 图片进入加载过程
System.out.println("图片开始加载....");
for (int i = 0; i < 100; i++) {
System.out.println("正在加载: " + (i + 1) + "%");
}
System.out.println("图片加载完成");
// 设置图片状态为已加载完成
picture.isLoad = true;
// 图片要开始下载了 - 唤醒正在等待的"显示图片线程"
picture.notify();
// 等待图片显示完成
if (picture.isShow == false) {
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() {
synchronized (picture) {
// 等待图片状态 isLoad = false
// 当图片没有加载完成, 显示线程 等待
if (picture.isLoad == false) {
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;
// 图片开始下载了 - 唤醒"下载图片线程"
picture.notify();
}
}
}
经典例题:消费者与生产者之间的通信
// 主方法
public class Demo {
public static void main(String[] args) {
BaoZi baoZi = new BaoZi();
BaoZiPU pu = new BaoZiPU(baoZi);
ChiHuo chi = new ChiHuo(baoZi, "ww");
pu.start();
chi.start();
}
}
// 共享资源(包子类)
public class BaoZi {
boolean flag = false; // 标记包子现在存不存在
String pi; // 馅
String xian; // 皮
}
/*
消费者: 吃货 线程
run: 1.判断包子状态false
等待包子铺生产包子
2.判断包子状态true
吃包子 baozi.flag = false
唤醒包子铺线程
*/
public class ChiHuo extends Thread {
BaoZi baoZi;
public ChiHuo(BaoZi baoZi, String name) {
super(name);
this.baoZi = baoZi;
}
@Override
public void run() {
while (true) {
System.out.println(getName() + "在等包子铺包包子");
synchronized (baoZi) {
// 1.判断包子状态false 等待包子铺生产包子
if (baoZi.flag == false) {
try {
baoZi.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 运行到这里, 要么是包子状态true, 要么被唤醒了
// 吃包子 baozi.flag = false
System.out.println(getName() + "开始吃" + baoZi.pi + baoZi.xian + "的包子");
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
baoZi.flag = false;
System.out.println(getName() + "吃完包子");
// 唤醒包子铺线程
baoZi.notifyAll();
}
}
}
}
/*
生产者: 包子铺 线程
run: 1.判断包子状态false
生产包子 baozi.flag = true
唤醒吃货线程, 来吃包子
2.判断包子状态true
不需要生产
等待 wait
*/
public class BaoZiPU extends Thread {
BaoZi baoZi;
public BaoZiPU(BaoZi baoZi) {
this.baoZi = baoZi;
}
@Override
public void run() {
while (true) {
synchronized (baoZi) {
// 判断包子状态true
if (baoZi.flag == true) {
try {
baoZi.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 已经被唤醒, 或者包子状态为false
// 生产包子 baozi.flag = true
System.out.println("包子铺正在包包子...");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
baoZi.flag = true;
baoZi.pi = "冰皮";
baoZi.xian = "韭菜大葱馅";
System.out.println("包好了冰皮韭菜大葱馅的包子...");
// 唤醒吃货线程, 来吃包子
baoZi.notify();
}
}
}
}
线程池( Executors 工厂类中的方法)
1、 newCachedThreadPool(): 创建一个根据需要创建新线程的线程池,但在可用时将重新使用以前构造的线程。
2、 newFixedThreadPool(int nThreads): 创建一个线程池,该线程池重用固定数量的从共享无界队列中运行的线程。
3、 newScheduledThreadPool(int corePoolSize): 创建一个线程池,可以调度命令在给定的延迟之后运行,或定期执行。
4、 newSingleThreadExecutor(): 创建一个使用从无界队列运行的单个工作线程的执行程序
线程池执行任务的API
1、submit(Runnable/Callable)
2、execute(Runnable)
Runnable(创建线程任务)
// 通过线程任务, 来获得线程对象并且直接开始线程
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);
// 将线程任务交给线程池 -- 线程对象可以反复使用
// 开启线程(submit()/execute())
pool.execute(run);
Future f = pool.submit(run); // f = null
pool.submit(run);
// 因为就只有3个线程对象,所以第4个线程会先等待
pool.submit(run);
// 如果没有关闭线程池, 线程对象依然在, 程序就不会结束
// 手动关闭线程池 - 会自动将里面的线程对象销毁
pool.shutdown();
}
Callable(创建线程任务)
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);
try {
Date date = f.get(3, TimeUnit.SECONDS); // 得到Callable里面call方法的返回值(会线程阻塞)
System.out.println(date);
} catch (TimeoutException e) {
System.out.println("结果超时了");
}
Callable和Runnable的区别
1、Callable(线程任务, 只能用在线程池)
new Thread(new Runnable(){});
new Thread(new Callable(){}); — 错误的
2、Callable对象只能用在 : Future(泛型) f = pool.submit(Callable);
f.get() -> 得到call方法的返回值(可能会遇到阻塞)
f.get(long, TimeUnit.xx) -> 超时继续(可以控制最大等待得到的时间)