目录
1️⃣多线程的创建
🗝️继承Thread类
/**
目标:多线程的创建方式之一 继承Thread类
*/
public class ThreadDemo {
public static void main(String[] args) {
//3new一个线程对象
Thread t = new MyThread();
//4启动线程对象
t.start();
for (int i = 0; i < 5; i++) {
System.out.println("主线程执行了 " + i);
}
}
}
/**
1定义一个线程类继承Thread
*/
class MyThread extends Thread{
/**
2重写run方法
*/
@Override
public void run() {
for (int i = 0; i < 5; i++) {
System.out.println("子线程执行了 " + i);
}
}
}
优点:编码简单
缺点:线程类已经继承Thread,无法继承其他类,不利于扩展
注意🔔🔔🔔🔔🔔🔔🔔🔔🔔🔔🔔🔔🔔🔔🔔🔔
不可以直接调用RUN方法 否则会直接当成单线程执行
主线程任务放在子线程任务之后
🗝️实现Runnable接口
public class ThreadDemo2 {
public static void main(String[] args) {
//3创建一个任务对象
Runnable target = new MyRunnable();
//4把任务对象交给Thread处理
Thread t = new Thread(target);
//5启动线程
t.start();
for (int i = 0; i < 5; i++) {
System.out.println("主线程执行了 " + i);
}
}
}
/**
1定义一个线程任务类实现Runnable接口
*/
class MyRunnable implements Runnable{
/*
重写run方法,定义线程的执行任务
*/
@Override
public void run() {
for (int i = 0; i < 5; i++) {
System.out.println("子线程执行了 " + i);
}
}
}
优点:线程任务类只是实现接口,可以继续继承类和实现其他接口,拓展性强
缺点:编程多一层对象包装,如果线程执行结果是不可以直接返回的
🗝️JDK 5.0新增:实现Callable接口
/**
目标:学会线程的创建方式三,实现Callable接口,结合FutureTask完成
*/
public class ThreadDemo3 {
public static void main(String[] args) {
//3创建任务对象
Callable<String> call = new MyCallable(100);
//4把Callable任务对象 交给FutureTask 对象
//FutureTask的作用1:是Runnable的对象(实现了Runnable接口),可以交给Thread了
//FutureTask的作用2:可以在线程执行完毕后调用get方法得到线程完成的结果
FutureTask<String> f1 = new FutureTask<>(call);
//5交给线程处理
Thread t1 = new Thread(f1);
t1.start();
Callable<String> call1 = new MyCallable(200);
FutureTask<String> f2 = new FutureTask<>(call1);
Thread t2 = new Thread(f2);
t2.start();
try {
String rs1 = f1.get();
System.out.println("第一个结果 " + rs1);
} catch (Exception e) {
e.printStackTrace();
}
try {
String rs2 = f2.get();
System.out.println("第er个结果 " + rs2);
} catch (Exception e) {
e.printStackTrace();
}
}
}
/**
* 1定义一个任务类 实现Callable接口
*/
class MyCallable implements Callable<String>{
private int n;
public MyCallable(int n) {
this.n = n;
}
/*
2重写call方法(任务方法)
*/
@Override
public String call() throws Exception {
int sum = 0;
for (int i = 0; i <= n; i++) {
sum += i;
}
return "子线程的结果是" + sum;
}
}
优点:可以返回线程执行的结果
2️⃣Thread的常用方法
⚫Thread的常用方法:获取线程名称getName()、设置名称setName()、获取当前线程对象 currentThread()
public class MyThread extends Thread{
public MyThread() {
}
public MyThread(String name) {
//为当前线程对象设置名称,送给父类的有参数构造器初始化名称
super(name);
}
@Override
public void run() {
for (int i = 0; i < 5; i++) {
System.out.println(Thread.currentThread().getName() + "输出 " + i );
}
}
}
public class ThreadDemo1 {
public static void main(String[] args) {
Thread t1 = new MyThread("1号");
//t1.setName("1号");
t1.start();
System.out.println(t1.getName());
Thread t2 = new MyThread("2号");
//t2.setName("2号");
t2.start();
System.out.println(t2.getName());
//哪个线程执行它,他就得到哪个线程对象(当前线程对象)
Thread m= Thread.currentThread();
System.out.println(m.getName());
m.setName("哇哈哈");
for (int i = 0; i < 5; i++) {
System.out.println(m.getName() + "输出 " + i );
}
}
}
3️⃣线程安全(小案例)
为什么会出现线程安全?
①存在多线程并发
②同时访问共享资源
③存在修改共享资源
小案例:小敏和小红是夫妻,他们有一个共同账户,余额10万元,模拟两人同时取钱
账户信息:⬇️⬇️⬇️⬇️⬇️⬇️
public class Account {
private String ID;
private double money;
public Account(String ID, double money) {
this.ID = ID;
this.money = money;
}
public String getID() {
return ID;
}
public void setID(String ID) {
this.ID = ID;
}
public double getMoney() {
return money;
}
public void setMoney(double money) {
this.money = money;
}
public void drawMoney(double money) {
//1先获取谁来取钱
String name = Thread.currentThread().getName();
//2判断余额是否充足
if (this.money >= money){
//3取钱
System.out.println(name + "来取钱,吐出" + money);
//4更新
this.money -= money;
System.out.println(name + "取钱后剩余" + this.money);
}else {
//5余额不足
System.out.println(name + "来取钱,余额不足");
}
}
}
取钱线程类⬇️⬇️⬇️⬇️⬇️⬇️
/**
* 取钱线程类
*/
public class DrawThread extends Thread{
//接受处理账户对象
private Account acc;
public DrawThread(Account acc,String name){
super(name);
this.acc = acc;
}
@Override
public void run() {
//取钱的
acc.drawMoney(100000);
}
}
/**
* 模拟取钱案例
*/
public class ThreadDemo {
public static void main(String[] args) {
//1定义一个线程类,创建共享账户对象
Account acc = new Account("ICBU-123",100000);
//2创建两个线程对象
new DrawThread(acc,"小敏").start();
new DrawThread(acc,"小红").start();
}
}
结果⬇️⬇️⬇️⬇️⬇️⬇️⬇️(出现安全问题)
4️⃣线程同步
线程同步核心思想:把共享资源加上锁,每次只能一个线程进入访问完毕后再解锁其他线程再进来
🗝️方式一:同步代码块
//同步代码块
// public void drawMoney(double money) {
//this == acc
// synchronized (this) { //代表当前对象
// //1先获取谁来取钱
// String name = Thread.currentThread().getName();
// //2判断余额是否充足
// if (this.money >= money){
// //3取钱
// System.out.println(name + "来取钱,吐出" + money);
// //4更新
// this.money -= money;
// System.out.println(name + "取钱后剩余" + this.money);
//
// }else {
// //5余额不足
// System.out.println(name + "来取钱,余额不足");
// }
// }
//
// }
🗝️方式二:同步方法
//同步方法
// public synchronized void drawMoney(double money) {
//
// //1先获取谁来取钱
// String name = Thread.currentThread().getName();
// //2判断余额是否充足
// if (this.money >= money){
// //3取钱
// System.out.println(name + "来取钱,吐出" + money);
// //4更新
// this.money -= money;
// System.out.println(name + "取钱后剩余" + this.money);
//
// }else {
// //5余额不足
// System.out.println(name + "来取钱,余额不足");
// }
//
//
// }
🗝️方式三:Lock锁
//LOCK锁
public void drawMoney(double money) {
//1先获取谁来取钱
String name = Thread.currentThread().getName();
//2判断余额是否充足
//小敏 小红
lock.lock(); //上锁
try {
if (this.money >= money){
//3取钱
System.out.println(name + "来取钱,吐出" + money);
//4更新
this.money -= money;
System.out.println(name + "取钱后剩余" + this.money);
}else {
//5余额不足
System.out.println(name + "来取钱,余额不足");
}
} finally {
lock.unlock();//解锁
}
}
5️⃣线程通信
什么是线程通信、如何实现❓
⚫所谓线程通信就是线程间相互发送数据,线程间共享一个资源即可实现
线程通信实际应用场景
⚫生产者与消费者模型:生产者线程负责生产数据,消费者线程负责消费生产者产生的数据
⚫要求:生产者生产完数据后唤醒消费者,然后等待自己,消费者消费完以后唤醒生产者,然后等待自己
同上Account账户内容添加
public synchronized void drawMoney(double money) {
try {
String name = Thread.currentThread().getName();
if (this.money >= money){
//钱够 可以取
this.money -= money;
System.out.println(name + "来取钱" + money + "成功!余额是" + this.money);
//没钱了
this.notifyAll(); //唤醒所有人
this.wait(); //锁对象 让当前线程进入等待
}else {
//钱不够,不可以取
//唤醒别人 等待自己
this.notifyAll(); //唤醒所有人
this.wait(); //锁对象 让当前线程进入等待
}
} catch (Exception e) {
e.printStackTrace();
}
6️⃣线程池
🗝️线程池概述
什么是线程池?
⚫就是一个可以复用线程的技术
🗝️线程池实现的API 参数说明
🗝️线程池处理Runnable任务
public class MyRunnable implements Runnable{
@Override
public void run() {
for (int i = 0; i < 5; i++) {
System.out.println(Thread.currentThread().getName() + "输出了 " + i);
}
try {
System.out.println(Thread.currentThread().getName() + "本任务与线程绑定了,进入休眠");
Thread.sleep(10000000);
} catch (Exception e) {
e.printStackTrace();
}
}
}
public class ThreadPoolDemo1 {
public static void main(String[] args) {
//1创建线程池对象
/**
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)
*/
ExecutorService pool = new ThreadPoolExecutor(3,5,6, TimeUnit.SECONDS,
new ArrayBlockingQueue<>(5), Executors.defaultThreadFactory(),
new ThreadPoolExecutor.AbortPolicy());
//2给任务线程池处理
Runnable target = new MyRunnable();
pool.execute(target);
pool.execute(target);
pool.execute(target);
pool.execute(target);
pool.execute(target);
pool.execute(target);
pool.execute(target);
pool.execute(target);
//开始创建临时线程
pool.execute(target);
pool.execute(target);
//不创建了 拒绝策略触发 因为最多5个线程
pool.execute(target);
}
}
🗝️线程池处理Callable任务
/**
* 1定义一个任务类 实现Callable接口
*/
public class MyCallable implements Callable<String>{
private int n;
public MyCallable(int n) {
this.n = n;
}
/*
2重写call方法(任务方法)
*/
@Override
public String call() throws Exception {
int sum = 0;
for (int i = 0; i <= n; i++) {
sum += i;
}
return Thread.currentThread().getName() + "执行1-"+ n+ "结果是" + sum;
}
}
public class ThreadPoolDemo2 {
public static void main(String[] args) throws Exception {
//1创建线程池对象
/**
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)
*/
ExecutorService pool = new ThreadPoolExecutor(3,5,6, TimeUnit.SECONDS,
new ArrayBlockingQueue<>(5), Executors.defaultThreadFactory(),
new ThreadPoolExecutor.AbortPolicy());
//2给任务线程池处理
Future<String> f1 = pool.submit(new MyCallable(100));
Future<String> f2 = pool.submit(new MyCallable(200));
Future<String> f3 = pool.submit(new MyCallable(300));
Future<String> f4 = pool.submit(new MyCallable(400));
Future<String> f5 = pool.submit(new MyCallable(500));
// String rs = f1.get();
// System.out.println(rs);
System.out.println(f1.get());
System.out.println(f2.get());
System.out.println(f3.get());
System.out.println(f4.get());
System.out.println(f5.get());
}
}
🗝️Executors工具类实现线程池
/**
目标:使用Executors的工具方法直接得到一个线程池对象
*/
public class ThreadDemo3 {
public static void main(String[] args) {
//1创建固定线数据的线程池
ExecutorService pool = Executors.newFixedThreadPool(3);
pool.execute(new MyRunnable());
pool.execute(new MyRunnable());
pool.execute(new MyRunnable());
pool.execute(new MyRunnable());//已经没有多余的线程了
}
}
7️⃣定时器
/**
* 目标 Timer定时器的使用和了解
*/
public class TimerDemo1 {
public static void main(String[] args) {
//1创建Timer定时器
Timer timer = new Timer(); //定时器本身就是一个单线程
//2调用方法,处理定时器任务
timer.schedule(new TimerTask() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "执行一次~~~~");
}
},3000,2000);
}
}
缺点:Timer是单线程,若因为某个异常使其死掉,会影响后续的执行
public class TimerDemo2 {
public static void main(String[] args) {
//1创建ScheduleExceutorService线程池,做定时器
ScheduledExecutorService pool = Executors.newScheduledThreadPool(3);
//2开启定时任务
pool.scheduleAtFixedRate(new TimerTask() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "执行输出:AA");
try {
Thread.sleep(100000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},0,2, TimeUnit.SECONDS);
pool.scheduleAtFixedRate(new TimerTask() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "执行输出:BB");
System.out.println(10 / 0);
}
},0,2, TimeUnit.SECONDS);
pool.scheduleAtFixedRate(new TimerTask() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "执行输出:CC");
}
},0,2, TimeUnit.SECONDS);
}
}
优点:基于线程池,某个任务执行挂掉不会影响后续执行