一、线程的概述
1、线程:
a.线程(Thread)是一个程序内部的一条执行路径
b.在启动程序执行后,main方法的执行就是一条单独的执行路径
c.程序中如果只有一条执行路径,那麽这个程序就是单线程的程序
2、多线程
是指从软硬件上实现多条执行流程技术
二、线程创建的三种方式
1、线程创建方式一
注意事项:
实现代码:
package thread.app.d1_create;
/**
* 目标:多线程创建方式一:继承Thread类实现
*/
public class ThreadDemo1 {
public static void main(String[] args) {
//3.new一个新线程对象
Thread t = new MyThread();
//4.调用start方法启动线程(执行的还是run)
t.start();
//主线程输出
for (int i = 0; i < 5; i++) {
System.out.println("主线程输出:" + i);
}
}
}
/**
* 定义一个线程类继承Thread类
*/
class MyThread extends Thread {
/**
* 2.重写run方法,里边定义线程要做什么
*/
@Override
public void run() {
for (int i = 0; i < 5; i++) {
System.out.println("子线程输出:" + i);
}
}
}
2、线程创建方式二
a.如何创建线程:
b.优缺点
实现代码:
package thread.app.d1_create;
/**
目标:学会线程的创建方式二,理解优缺点
*/
public class ThreadDemo2 {
public static void main(String[] args) {
//3.创建任务对象
Runnable target=new MyRunnble();
//4.把任务对象交给Thread处理
Thread thread=new Thread(target);
//5.启动线程
thread.start();
//主线程任务
for (int i = 0; i < 10; i++) {
System.out.println("主线程执行输出:"+i);
}
}
}
/**
1.定义一个线程任务类 实现Runnable接口
*/
class MyRunnble implements Runnable{
/**
重写run方法
*/
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println("子线程执行输出:"+i);
}
}
}
3.线程创建方式三
a.利用Callable,FutureTask接口实现
b.FutureTask的API及其优缺点
实现代码:
package thread.app.d1_create;
import java.util.Calendar;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
import java.util.function.Function;
/**
* 目标:学会线程的创建方式三,实现Callable接口,结合FutureTask实现
*/
public class ThreadDemo3 {
public static void main(String[] args) {
//3.创建Callable任务对象
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);
//6.启动线程
t1.start();
Callable<String> call2=new MyCallable(200);
FutureTask<String> f2=new FutureTask<>(call2);
Thread t2=new Thread(f2);
t2.start();
try {
//如果f1没有执行完毕,这里的代码会等待,知道线程1跑完才提取结果
String rs1=f1.get();
System.out.println("第一个结果:"+rs1);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
try {
String rs2=f2.get();
System.out.println("第二个结果:"+rs2);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException 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 = 1; i < n; i++) {
sum+=i;
}
return "子线程执行的结果为:"+sum;
}
}
三、Thread的常用方法
Thread常用方法:获取线程名称getName,设置名称setName,获取当前线程对象currentThread,线程睡眠sleep。
实现代码:
MyThread类
package thread.app.d2_api;
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);
}
}
}
ThreadDemo1类(实现类)
package thread.app.d2_api;
/**
目标:线程API
*/
public class ThreadDemo1 {
public static void main(String[] args) {
//对线程t1与t2进行命名
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());
//哪个线程执行就得到那个线程对象(主线程对象)
//主线程名称就叫main
Thread m=Thread.currentThread();
System.out.println(m.getName());
//对主线程取名字
m.setName("666");
for (int i = 0; i < 5; i++) {
System.out.println(m.getName()+"主线程输出:"+i);
}
}
}
ThreadDemo2类(实现类)
package thread.app.d2_api;
/**
目标:Thread类线程休眠方法的API(让当前线程休眠指定时间后再执行)
*/
public class ThreadDemo2 {
//main方法是由主线程调度的
public static void main(String[] args) throws Exception {
for (int i = 0; i < 5; i++) {
System.out.println("输出:"+i);
if (i==3){
//让线程进入休眠状态
Thread.sleep(3000);
}
}
}
}
四、线程安全
1.线程安全问题
多个线程同时操作同一共享资源的时候可能会出现业务安全问题,成为线程安全问题。
2.案例
模拟取钱案例 :小明,小红他们有一个共同账户,余额10万,模拟两人同时取10万。
实现代码:
Account类(账户类)
package thread.app.d3_thread_safe;
/**
账户类
*/
public class Account {
private String cardID;
private double money;
public Account() {
}
public Account(String cardID, double money) {
this.cardID = cardID;
this.money = money;
}
/**
取钱方法
* @param money
*/
public void drawMoney(double money) {
//0.先获取谁来取钱
String name=Thread.currentThread().getName();
//1.判断账户是否够钱
if (this.money>=money) {
//2.取钱
System.out.println(name+"来取钱,吐出"+money);
//3.更新余额
this.money-=money;
System.out.println("剩余的钱:"+this.money);
}else {
//4.余额不足
System.out.println("余额不足!!!");
}
}
public String getCardID() {
return cardID;
}
public void setCardID(String cardID) {
this.cardID = cardID;
}
public double getMoney() {
return money;
}
public void setMoney(double money) {
this.money = money;
}
}
DrawTread类 (取钱的线程类)
package thread.app.d3_thread_safe;
/**
* 取钱的线程类
*/
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);
}
}
ThreadDemo类(实现类)
package thread.app.d3_thread_safe;
/**
需求:模拟取钱案例
小明,小红他们有一个共同账户,余额10万,模拟两人同时取10万。
*/
public class ThreadDemo {
public static void main(String[] args) {
//1.定义线程类,创建一个共享账户
Account acc=new Account("123456789",100000);
//2.定义2个线程对象,代表小明,小红同时进来了
new DrawThread(acc,"小明").start();
new DrawThread(acc,"小红").start();
}
}
运行截图:
五、线程同步
1.线程同步思想
加锁,把共享资源进行上锁,每次只能一个线程进入访问完毕以后解锁,然后其他线程才能进来。
2.加锁的方式
a.同步代码块
注意:
将取钱的代码进行上锁
/**
取钱方法
* @param money
*/
public void drawMoney(double money) {
//0.先获取谁来取钱
String name=Thread.currentThread().getName();
//同步代码块,声明锁对象(实现线程安全,依次进入)
//小明 小红
//this==acc 共享账户
synchronized (this) {
//1.判断账户是否够钱
if (this.money>=money) {
//2.取钱
System.out.println(name+"来取钱,吐出"+money);
//3.更新余额
this.money-=money;
System.out.println("剩余的钱:"+this.money);
}else {
//4.余额不足
System.out.println(name+"余额不足!!!");
}
}
}
b.同步方法
注意:
/**
取钱方法
同步方法,先进来的方法执行完后,才能再执行其他的
* @param money
*/
public synchronized void drawMoney(double money) {
//0.先获取谁来取钱
String name=Thread.currentThread().getName();
//1.判断账户是否够钱
lock.lock();//上锁
try {
if (this.money>=money) {
//2.取钱
System.out.println(name+"来取钱,吐出"+money);
//3.更新余额
this.money-=money;
System.out.println("剩余的钱:"+this.money);
}else {
//4.余额不足
System.out.println("余额不足!!!");
}
} finally {
//如果上方代码出现bug,也会执行解锁
lock.unlock();//执行完后解锁
}
}
c.Lock锁
客户类的代码
package thread.app.d5_thread_synchronizes_method; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; /** 账户类 */ public class Account { private String cardID; private double money; //final修饰后:锁对象是唯一,不可替换的 private final Lock lock=new ReentrantLock(); public Account() { } public Account(String cardID, double money) { this.cardID = cardID; this.money = money; } /** 取钱方法 同步方法,先进来的方法执行完后,才能再执行其他的 * @param money */ public void drawMoney(double money) { //0.先获取谁来取钱 String name=Thread.currentThread().getName(); //1.判断账户是否够钱 lock.lock();//上锁 try { if (this.money>=money) { //2.取钱 System.out.println(name+"来取钱,吐出"+money); //3.更新余额 this.money-=money; System.out.println("剩余的钱:"+this.money); }else { //4.余额不足 System.out.println("余额不足!!!"); } } finally { //如果上方代码出现bug,也会执行解锁 lock.unlock();//执行完后解锁 } } public String getCardID() { return cardID; } public void setCardID(String cardID) { this.cardID = cardID; } public double getMoney() { return money; } public void setMoney(double money) { this.money = money; } }
六、线程通信
1.什么是线程通信,如何实现?
2.线程通信常见模型
3..线程通信的三种方式
注意:
上述方法应当使用当前同步锁对象进行调用
七、线程池
1.线程池概述
线程池就是可以复用线程的技术。
2.线程池实现的API,参数说明
a.如何获得线程池对象
b.ThreadPoolExecutor构造器的参数说明
public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler)
注意:
c.线程池处理Runnable任务
实现代码:
MyRunnable类
package thread.app.d8_threadpool;
/**
* @author zhixu
* @date 2022/5/24
* @apiNote
*/
public class MyRunnable implements Runnable {
@Override
public void run() {
for (int i = 0; i < 5; i++) {
System.out.println(Thread.currentThread().getName()+"输出了:HelloWord==>"+i);
}
try {
System.out.println(Thread.currentThread().getName()+"本任务与线程绑定,线程进入休眠!!!");
Thread.sleep(10000000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
实现类
package thread.app.d8_threadpool;
import java.util.concurrent.*;
/**
* @author zhixu
* @date 2022/5/24
* @apiNote
* 目标:自定义一个线程池对象,并测试其性能
*/
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);
// //不创建,拒绝策略出发!!!
// pool.execute(target);
//关闭线程池(一般不使用)
// pool.shutdownNow();//立即关闭,即使任务没有完成,丢失任务
pool.shutdown();//等所有任务完成后,再关闭
}
}
运行截图:
d.线程池处理Callable任务
实现代码:
MyCallable类
package thread.app.d8_threadpool;
import java.util.concurrent.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 = 1; i < n; i++) {
sum+=i;
}
return Thread.currentThread().getName()+"执行1-"+n+"的和结果是:"+sum;
}
}
实现类
package thread.app.d8_threadpool;
import java.util.concurrent.*;
/**
* @author zhixu
* @date 2022/5/24
* @apiNote
* 目标:自定义一个线程池对象,并测试其性能
*/
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(400));
Future<String> f3=pool.submit(new MyCallable(500));
Future<String> f4=pool.submit(new MyCallable(600));
System.out.println(f1.get());
System.out.println(f2.get());
System.out.println(f3.get());
System.out.println(f4.get());
}
}
3.Executors工具类构建线程池对象
注意:
Executors的底层也是基于线程池ThreadPoolExecutor创建线程池对象。
实现代码:
package thread.app.d8_threadpool;
import java.util.concurrent.*;
/**
* @author zhixu
* @date 2022/5/24
* @apiNote
* 目标:使用Executors的工具方法直接得到一个线程池对象(如果不注意,会出现系统风险)
*/
public class ThreadPoolDemo3 {
public static void main(String[] args) throws Exception {
//1.创建固定线程数量的线程池
ExecutorService pool=Executors.newFixedThreadPool(3);//永远只有3个线程
pool.execute(new MyRunnable());
pool.execute(new MyRunnable());
pool.execute(new MyRunnable());
pool.execute(new MyRunnable());//已经没有多余的线程
//2.
}
}
2.Executors工具类使用可能存在的风险
八、定时器
1.定时器概念
定时器是一种控制任务延时调用,或周期调用的技术。
2.定时器的实现方式
a.Timer定时器(不推荐使用)
注意:
实现代码:
package thread.app.d9_timer;
import javax.xml.crypto.Data;
import java.util.Date;
import java.util.Timer;
import java.util.TimerTask;
/**
* @author zhixu
* @date 2022/5/25
* @apiNote
* 目标: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()+"执行A!!!"+new Date());
//任务休眠,会干扰下个定时器的任务
// try {
// Thread.sleep(50000);
// } catch (InterruptedException e) {
// e.printStackTrace();
// }
}
},0,2000);
timer.schedule(new TimerTask() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"执行B!!!"+new Date());
}
},0,2000);
}
}
运行截图:
b.ScheduledExecutorService定时器
优点:
基于线程池,某个任务执行情况不影响其他定时任务的执行。
实现代码:
package thread.app.d9_timer;
import java.util.Date;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
/**
* @author zhixu
* @date 2022/5/25
* @apiNote
* 目标:Timer定时器的使用和了解(不常用)
*/
public class TimerDemo2 {
public static void main(String[] args) {
//1.创建ScheduledExecutorService线程池,做定时器(基于线程池,不影响其他定时任务的执行)
ScheduledExecutorService pool= Executors.newScheduledThreadPool(3);
//2.开始定时任务
/**
public ScheduledFuture<?> scheduleAtFixedRate(Runnable command,
long initialDelay,
long period,
TimeUnit unit);
*/
pool.scheduleAtFixedRate(new TimerTask() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"输出执行:AAA"+new Date());
//任务休眠,不会干扰下个定时器的任务
try {
Thread.sleep(50000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},0,2, TimeUnit.SECONDS);
pool.scheduleAtFixedRate(new TimerTask() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"输出执行:BBB"+new Date());
}
},0,2, TimeUnit.SECONDS);
}
}
运行截图:
九、并发并行,线程的生命周期
1.并发与并行
并发与并行的含义:
2.线程的生命周期