多线程基本概念
- 进程:类似一个软件
- 线程:软件中的执行路径
线程调度
我们说4核也好,8核也好,这是对计算机更合理的分配,比如一个🧠(cpu),它运行的很快,同一段时间做很多事情,但是他仍旧是每次只做一件事。
打个比方:一人一年,写书,创业,旅游,恋爱。我们需要了解的是计算机的处理速度是远快于人的。
所以
人需要学会灵活运用计算机的算力,锻炼自己如何学习架构一个完备的软件能力,而不是和计算机拼手速。
再举一个栗子:
1000人 8个岗位
1.不排队,就是切换
2.排队,轮流做
哪个更加有效率?
试想一下不排队,切换时候用的时间。接下来说说下面的同步异步。
同步与异步
同步:排队去做某件事,线程安全
异步:同时执行,线程不安全
想象一下:夏天排队买西瓜 (同步)
西瓜车倒了,去抢西瓜。(异步)
并发&并行
并发:多个事件在同一个时间段内发生
并行:多个事件在同一个时刻发生(同时)
再举栗子 写书,创业,旅游,恋爱 今年在Allen发生(并发)
A,B在2021.6.31 在云南大理相遇(并行)
常用方法
Thread线程里的run方法
MyThread 去重写Thread中的run方法
我们想用该方法,就要调用start()
注意:如果我们采用的是run(),那就是先
运行MyThread线程后,再去运行Main对象的方法
实现Runnable方法,重写里面的run方法
创建任务对象 MyRunnable
创建线程,分配任务
执行线程
1.创建任务,给线程分配的方式实现多线程
2.避免单继承所带来的局限
3.任务与线程分离,提高程序的健壮性
4.后续学习的线程池技术,接受Runnable类型的任务,不接受Thread类型的线程
匿名内部类继承Thread的方式
new Thread(new Runnable(){
public void run(){
System.out.print("哥斯拉大战金刚");
}
}).start();
注意:
线程检测-1,从而return 释放资源
sleep() 休眠操作
Deamon线程(守护线程),用户线程
线程休眠sleep:注意此处的millis有1000ms为1s
public class Demo4 {
public static void main(String[] args) throws InterruptedException {
for (int i = 0 ; i < 10; i++ ){
System.out.println("Build your dream.");
Thread.sleep(1);
}
}
}
线程阻塞
缺点:耗时
发生在:文件读取
线程中断:是否应该结束,由其自身决定
public class Demo5 {
public static void main(String[] args) {
Thread ss = new Thread(new MyRunnable1());
ss.start();
//如何获取当前的线程名称?
// Thread.currentThread().getname()
for (int j = 0; j < 5; j++) {
System.out.println(Thread.currentThread().getName()+j);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
ss.interrupt(); //main进程向子线程发出提示命令,此处如果
}
static class MyRunnable1 implements Runnable{
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println("To be" + i);
try {
Thread.sleep(1000);
} catch (Exception e) {
// e.getStackTrace();
System.out.println("收到命令,准备赴死");
return; //返回,程序终止 没有return,多加载一个
}
}
}
}
}
为什么我们此处用try-catch命令?
Runnnable接口没有声明异常,那么子线程就不抛出比父线程更大的异常?
使用t1.interrupt 标识块目标是?
提醒子线程死亡,此时我们虽然跳转到catch块,但就是不报错
return才会把资源释放了
注意:父线程需要在子线程之前结束,才能触发子线程的结束命令。具体可参见上述代码。
守护线程:守护用户线程,最后一个用户线程结束时,所有守护线程自动死亡。
骑士 在前 ss.setDaemon(true);
用户线程:当一个进程不包含任何的存活的用户线程时,进程结束
公主 在后 ss.start();
再次强调:守护线程在用户线程之前
线程安全问题
售票系统
卖票,休眠,减去票数,出票
我们会在这两个过程里面发现BC线程在A线程休眠的时候抢到了票(因为票数Ticket还没完成,自减)
package ThreadsDemo;
/**
* @Author:Allen
* @Date:3/23/2021 3:36 PM
*/
public class Demo6 {
public static void main(String[] args) {
Thread s1 = new Thread(new MyrRunnable());
s1.start();
new Thread(s1).start();
new Thread(s1).start(); //两个线程同时去争夺资源,想象两头狮子强地盘,如果来第三头呢?
}
static class MyrRunnable implements Runnable{
private int ticket =10;
@Override
public void run() {
for (int i =0;i < 10;i++){
System.out.println("准备取票请稍后"+Thread.currentThread().getName());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
ticket--;
System.out.println("取票成功,还剩"+ticket);
}
}
}
}
Tips:
1.ticket不放在 @Override后面
2. Runnable s1 = new MyrRunnable();
new Thread(s1).start();
new Thread(s1).start();
new Thread(s1).start();
总结:线程不安全更多出现在资源少,大家争夺比较密集的情况。
隐式锁
1.同步代码块
synchronized(锁对象)
while(true)
方案一,用锁对象去锁 if判断票的量
方案二,不合理的解决是每个进程都有自己的一把锁
采用方案二,如下图重写的run方法,很明显就是每个创建的对象都可以拥有一把锁,那么就是不安全的。
试想一下: 小伙子们不用手抓饭,而是一伙人用碗一起乘饭。
public void run() {
Object o =new Object();
while (true) {
synchronized (o) { //自带“饭碗”,但仍旧不安全
if (ticket > 0) {
System.out.println("准备取票请稍后" + Thread.currentThread().getName());
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
ticket--;
System.out.println("取票成功,还剩" + ticket);
} else {
break;
}
}
}
}
可以看见出现-1情况
lock锁放在if判断语句之外
package ThreadsDemo;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/** 显示锁 思考lock锁应该存放的位置 如果lock锁放在if判断语句之后
* 1.意味着每个人去抢饭,然后抢到后用碗乘自己在那吃饭,吃完后把碗扔了
* 2. 每个人抢饭,抢到饭的把锅扣着,别人吃不了
* @Author:Allen
* @Date:3/23/2021 3:36 PM
*/
public class Demo7 {
public static void main(String[] args) {
Runnable s1= new MyrRunnable();
Thread s2 = new Thread(new MyrRunnable());
new Thread(s1).start();
new Thread(s1).start();
new Thread(s1).start();
}
static class MyrRunnable implements Runnable{
private int ticket =10;
private Lock l = new ReentrantLock(true);
//private
@Override
public void run() {
while(true){
l.lock();
if (ticket > 0) {
System.out.println("准备取票请稍后" + Thread.currentThread().getName());
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
ticket--;
System.out.println("取票成功,还剩" + ticket);
} else {
break;
}
l.unlock();
}
}
}
}
情况一: 如果我们创建三个对象,他们是不安全的,会同时去抢进程
情况二: synchronized(this){}这个出现,我们可以想象一个挡了两条路,
其他的都走不了
显示锁 Lock
给任务run一把锁
注意: Lock不会自动解除,需要调用lock方法,之后解锁lock方法
Synchronize自动解除
显示锁和隐式锁之间的区别?
Synchronize vs Lock
悲观锁 乐观锁
公平锁,非公平锁?
先来先得,公平锁
死锁 线程是否冲突?
是否产生 A等B进程执行,B等A进程执行的情况
第三种线程实现的方式,Callable
package ThreadsDemo;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
/**
* @Author:Allen
* @Date:3/25/2021 7:28 PM
*/
public class Demo10 {
public static void main(String[] args) throws ExecutionException, InterruptedException {
MyCallable myCallable = new MyCallable();
FutureTask futureTask = new FutureTask(myCallable);
System.out.println(futureTask.isDone());
new Thread(futureTask).start();
Integer i = (Integer) futureTask.get();
System.out.println("--------------------"+i);
System.out.println(futureTask.cancel(true));
for (int j = 0 ; j < 10 ;j++){
System.out.println(j);
}
}
static class MyCallable implements Callable{
@Override
public Integer call() throws Exception {
for (int i = 0 ; i < 10 ; i++){
System.out.println(i);
}
return 1;
}
}
}
wait notify
多线程通信问题,生产者,消费者
package ThreadsDemo;
/**
* @Author:Allen
* @Date:3/25/2021 4:12 PM
*/
public class Demo9 {
public static void main(String[] args) {
Food food = new Food();
new Cook(food).start();
new Waiter(food).start();
}
static class Cook extends Thread{
private Food food;
public Cook(Food food) {
this.food = food;
}
@Override
public void run() {
for (int i = 0;i<50; i++){
if (i%2==0){
food.setNameAndTaste("太湖白鱼","鲜");
}else {
food.setNameAndTaste("川系鲈鱼","辣");
}
}
}
}
static class Waiter extends Thread{
private Food food;
public Waiter(Food food) {
this.food = food;
}
@Override
public void run() {
for (int j = 0; j < 50; j++) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
food.get();
}
}
}
static class Food{
private String name;
private String taste;
private Boolean flag =true;
public synchronized void setNameAndTaste(String name,String taste) {
if(flag) {
this.name = name;
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
this.taste = taste; //此时厨师做饭结束后,使flag为false,唤起waiter进程, //自己准备“睡觉”
flag = false;
this.notify();
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public synchronized void get() {
// if(!flag) {
System.out.println("Waiter takes:" + name + ",it tastes:" + taste);
flag=true;
this.notify(); //waiter调用的get方法,令flag为true,唤醒厨师做饭,自己准备去休眠
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
线程的六个状态,手绘版本
创建线程,
创建任务,
执行任务,
关闭线程
线程池
缓存线程池
package ThreadsDemo;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/** 缓存线程池 这里我们有一个 排队的概念:
* 澡堂AB位置,AB同学先到,在澡堂淋浴。随后C同学也到,排队等A同学洗好澡后,在A位置淋浴
* @Author:Allen
* @Date:3/25/2021 7:51 PM
*/
public class Demo11 {
public static void main(String[] args) {
ExecutorService executorService = Executors.newCachedThreadPool();
executorService.execute(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+":The Shawshank Redemption");
}
});
executorService.execute(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+":Forrest Gump");
}
});
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
executorService.execute(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+":Summer!");
}
});
}
}
定长线程池
try catch的原因是什么?
打印后再去执行休眠操作
package ThreadsDemo;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/** 定长线程池
* @Author:Allen
* @Date:3/26/2021 11:12 AM
*/
public class Demo12 {
public static void main(String[] args) {
ExecutorService executorService = Executors.newFixedThreadPool(2);
executorService.execute(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Love you three Thousand and one times");
}
});
executorService.execute(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Love you three Thousand and two times");
}
});
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
executorService.execute(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Love you three Thousand and three times ");
}
});
}
}
单线程池
package ThreadsDemo;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* @Author:Allen
* @Date:3/26/2021 2:09 PM
*/
public class Demo13 {
public static void main(String[] args) {
ExecutorService executorService = Executors.newSingleThreadExecutor();
executorService.execute(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"付出不亚于任何人的努力,热爱工作!");
}
});
executorService.execute(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"付出不亚于任何人的努力,热爱工作把!");
}
});
}
}
周期定长线程池
1.参数一,任务
2.参数二,延迟时长数字(第一次的执行时间)
3.参数三,周期时长数字(每间隔多久执行一次)
4.参数四,时长数字的单位
package ThreadsDemo;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
/**
* @Author:Allen
* @Date:3/26/2021 2:14 PM
*/
public class Demo14 {
public static void main(String[] args) {
ScheduledExecutorService service = Executors.newScheduledThreadPool(2);
service.schedule(new Runnable() {
@Override
public void run() {
System.out.println("动机至善,私信了无!");
}
},2,TimeUnit.SECONDS);
}
}
Lambda表达式
函数式编程思想
面对对象,创建对象调用方法
方法一 匿名内部类,简化书写的过程
方法二 Lambda表达式 ()-> sout
函数式编程
在主函数中调用一个 Lambda表达式
定义一个抽象方法,定义一个接口(一个接口采用一个函数)
package ThreadsDemo;
/**
- @Author:Allen
- @Date:3/26/2021 2:32 PM
*/
public class Demo16 {
public static void main(String[] args) {
/* 法一 匿名对象
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("Thunder!");
}
});
thread.start();
*/
/* 法二 函数式编程
Thread thread = new Thread(()-> System.out.println("Love!"));
thread.start();
*/
print((int x, int y)-> {
return x+y;
},100,200);
}
public static void print(MyMath m,int x,int y){
int num = m.sum(x,y);
System.out.println(num);
}
static interface MyMath{
int sum(int x,int y);
}
}
总结:多线程需掌握三种实现方式,Thread,Runnable(与线程池技术紧密联系),Callable
Synchronize和Lock的区别,前者自动释放,后者手动
线程池需要把握,线程与任务的关系
感谢你耐心的阅读,下期见