🌟线程与进程
进程是指一个内存中运行的应用程序,每个进程都有一个独立的内存空间。
线程是进程中的一个执行路径,共享一个内存空间,一个进程最少有一个线程,是对进程进一步划分
✨当进程里面一个线程都没有时,该进程就被关闭了
例如:一个QQ音乐进程,拥有听歌显示图片刷评论等多个线程
分时调度:
为了能让每个线程均分CPU,一千多个线程快速轮流使用CPU的使用权,交替执行,给人造成一个假象好像多个任务同时在进行一样
Java使用的调度机制——抢占式调度:
哪个线程优先级高就先抢到CPU使用权概率大,优先级可以被设置
🌟同步与异步
同步:执行某件事情排队执行(效率低但安全)
异步:同时执行(效率高但数据不安全)可能会导致数据错乱
🌟并发与并行
并发:指定时间段内发生(一天的并发量,一秒的并发量是多少)
并行:指两个或多个事件同时发生
🌟两种代码实现多线程的方式:
1.Thread 线程类,任何要被执行的线程类都要继承Thread类,重写run()
2.线程类中的run() 方法就是线程要实行的任务方法
主方法中的代码顺序执行,走着走着,出现了一个多线程,多线程被执行时要调用Thread类中的start() 方法交替被运行(看谁先抢到CPU)
public static void main(String[] args){
MyThread m = new MyThread();
m.start();
for(int I =0;i<10;i++){
System.out.println(“汗滴禾下土”+i);
}
public class MyThread extends Thread{
//run 方法就是线程要执行的任务方法
public void run(){
//这里的代码 就是一体阿信的执行路径
//这个执行路径的处理方式不是调用run()方法,而是通过thread对象的start()来启动任务
for(int I =0;i<10;i++){
System.out.println(“锄禾日当午”+i);
}
}
}
⚠️注意:每个线程都有自己的栈空间,共用一个堆内存。子线程当中的任务方法都在子线程中运行,即在自己的栈空间中入栈和弹栈
✨实现Runnable接口任务
public static void main(String[] args){
//实现Runnable
//1.创建一个任务对象
MyRunnable r = new MyRunnable();
//2.创建一个线程,并为其分配一个任务
Thread t =new Thread(r);
//3.执行这个线程
t.start();
for(int I =0;i<10;i++){
System.out.println(“疑是地上霜”+i);
}
}
public class MyRunnable implements Runnable{
public void run(){
//线程的任务
for(int i =0;i<10;i++){
System.out.println(“床前明月光”+i);
}
}
}
✨实现Runnable 与 继承Thread相比有如下优势
1.通过创建任务,然后给线程分配的方式来实现的多线程,更适合多个线程同时执行相同任务的情况
2.避免单继承的局限性
3.任务与线程分身是分离的,提高了程序的健壮性
4.后续学习的线程池技术,接受Runnable类型的任务,不接收Thread类型的线程
🌟Thread方法结束不能用stop(),这是已过时的方法,用变量,当变量是1可以执行,如果变量改变,变成-1,return方法停止线程
✨Thread类常用的方法有给线程设置优先级,sleep();休眠等
🌟获取当前运行线程对象/名称
public static void main(String[] args){
System.out.println(Thread.currentThread().getName());
Thread t = new Thread(new MyRunnable());
t.setName();
t.start();
new Thread(new MyRunnable(),name:”锄禾日当午”).start();
new Thread(new MyRunnable()).start();
}
static class MyRunnable implements Runnable{
public void run(){
System.out.println(Thread.currentThread().getName());
}
}
上述代码打印结果:
main
Thread-2
Thread-1
Thread-0
🌟线程的休眠 sleep()可以指定时间休眠
public static void main(String[] args){
for(int i=0;i<10;i++){
System.out.println(i);
Thread.sleep(1000); //sleep()方法是静态类,直接由Thread类调用即可,内部放毫秒值,每打印一次就休息1秒
}
}
🌟线程阻塞
所有消耗时间的操作都称为线程阻塞,又称为耗时操作
如:等待用户输入,线程是不往下走的,一定要等用户输入完毕才会继续执行
🌟线程中断
一个线程是一个独立的执行路径,它是否应该结束,应该有其自身决定stop()不能使用了因为外部掐死了线程,现在给线程打一个中断标记
代码示例:
public static void main(String[] args){
Thread t1 = new Thread(new MyRunnable());
t1.start();
for(int i=0;i<5;i++){
System.out.println(i);
try{
Thread.sleep(1000);
}catch(InterruptedException e){
e.printStackTrace();}
}
//给线程t1添加中断标记,在线程等待或休眠时会审查是否有标记,有中断标记会抛出一个异常,提醒程序员是否要中断线程
t1.interrupt();
}
static class MyRunnable implements Runnable{
public void run(){
for(int i=0;i<10;i++){
System.out.println(i);
try{
Thread.sleep(1000);
}catch(InterruptedException e){
//e.printStackTrace();
System.out.println(“发现了标记,现在停止此线程”);
return;
}
}
}
}
🌟守护线程
线程分为守护线程和用户线程
用户线程:当一个进程不包含任何的存活的用户线程时,进程结束
守护线程:守护用户线程,当最后一个用户线程结束时,所有守护线程自动死亡,用户进程即将关闭的时间内守护线程还没有死
代码示例:
public static void main(String[] args){
Thread t1 = new Thread(new MyRunnable());
//设置t1为守护线程,当main方法结束时,t1线程自动死亡
t1.setDaemon(true);
t1.start();
for(int i=0;i<5;i++){
System.out.println(i);
try{
Thread.sleep(1000);
}catch(InterruptedException e){
e.printStackTrace();}
}
}
static class MyRunnable implements Runnable{
public void run(){
for(int i=0;i<10;i++){
System.out.println(i);
try{
Thread.sleep(1000);
}catch(InterruptedException e){
//e.printStackTrace();
System.out.println(“发现了标记,现在停止此线程”);
return;
}
}
}
}
🌟解决线程安全问题
✨方案1:同步代码块
格式:
synchronized(锁对象){
//锁对象可以是万物,无意义,只用来告诉代码被这个东西锁住了,打上个标记
}
public class test {
public static void main(String[] args){
Runnable t = new Ticket();
new Thread(t).start();
new Thread(t).start();
new Thread(t).start();
}
}
public static class Ticket implements Runnable{
//声明票数
private int count =10;
//创建锁对象
private Object o = new Object();
public void run(){
while(true){
synchronized(o){
if(count>0){
System.out.println("正在出票...");
try{
Thread.sleep(1000);
}catch(InterruptedException e){
e.printStackTrace();
}
count--;
System.out.println(Thread.currentThread().getName()+"出票成功,余票:"+count);
}
}
}
}
}
✨方案2:同步方法
格式:修饰符 synchronized 返回值类型 方法名(参数列表){
访问了共享数据的代码
}
使用步骤:
1.把访问了共享数据的代码抽取出来,放到一个方法中
2.在方法上添加synchronized 修饰符
public class test {
public static void main(String[] args){
RunnableImpl r = new Runnable();
new Thread(r).start();
new Thread(r).start();
new Thread(r).start();
}
}
public static class RunnableImpl implements Runnable{
//声明票数
private int count =10;
//设置线程卖票
public void run(){
//使用死循环,让卖票操作重复执行
while(true){
payTicket();
}
//定义一个同步方法:
public Synchronized void payTicket(){
if(count>0){
System.out.println("正在出票...");
try{
Thread.sleep(1000);
}catch(InterruptedException e){
e.printStackTrace();
}
count--;
System.out.println(Thread.currentThread().getName()+"出票成功,余票:"+count);
}
}
✨方案3:显式锁Lock
前两种方案都是隐式锁
public class test {
public static void main(String[] args){
Runnable t = new Ticket();
new Thread(t).start();
new Thread(t).start();
new Thread(t).start();
}
}
public static class Ticket implements Runnable{
//声明票数
private int count =10;
//创建锁对象
Lock l = new ReentrantLock();//1.自己创建锁对象
public void run(){
while(true){
l.lock(); //2.自己锁🔒显式锁
if(count>0){
System.out.println("正在出票...");
try{
Thread.sleep(1000);
}catch(InterruptedException e){
e.printStackTrace();
}
count--;
System.out.println(Thread.currentThread().getName()+"出票成功,余票:"+count);
}
l.unlock();//3.解锁🔒显式锁
}
}
}
🌟公平锁和非公平锁
公平锁是排队谁先来给谁,非公平锁是大家一起抢使用权。
以上学习的三种锁,也就是Java中的三种锁都是非公平锁,而Lock显式锁🔒的参数设置为true就变成了公平锁:
Lock l = new ReentrantLock(true);
🌟线程死锁
对峙状态,警察与罪犯对峙,为避免死锁状态在一个方法中启动一个锁时不要再调用另一个锁
🌟多线程通信问题
生产者与消费者问题,A在下载,B在等待,A下载完毕叫醒B的过程
交替进行
等待与唤醒案例
🌟线程的六种状态
New 该开始创建未运行时的状态
Runnable 运行时的状态
Blocked 阻塞状态,排队是的状态
Waiting 无限休眠等待的状态,被唤醒回到Runnable状态
TimeWaiting 定时休眠等待自动唤醒状态,回到Runnable状态
Terminated 线程死亡状态
🌟带返回值的线程Callable
🚩第三种创建线程的方法
有两种创建线程的方法-一种是通过创建Thread类,另一种是通过使用Runnable创建线程。但是,Runnable缺少的一项功能是,当线程终止时(即run()完成时),我们无法使线程返回结果。为了支持此功能,Java中提供了Callable接口。
为了实现Runnable,需要实现不返回任何内容的run()方法,而对于Callable,需要实现在完成时返回结果的call()方法。请注意,不能使用Callable创建线程,只能使用Runnable创建线程。
另一个区别是call()方法可以引发异常,而run()则不能。
为实现Callable而必须重写call方法。
当call()方法完成时,结果必须存储在主线程已知的对象中,以便主线程可以知道该线程返回的结果。为此,可以使用Future对象。将Future视为保存结果的对象–它可能暂时不保存结果,但将来会保存(一旦Callable返回)。因此,Future基本上是主线程可以跟踪进度以及其他线程的结果的一种方式。要实现此接口,必须重写5种方法,但是由于下面的示例使用了库中的具体实现,因此这里仅列出了重要的方法。
public boolean cancel(boolean mayInterrupt):用于停止任务。如果尚未启动,它将停止任务。如果已启动,则仅在mayInterrupt为true时才会中断任务。
public Object get()抛出InterruptedException,ExecutionException:用于获取任务的结果。如果任务完成,它将立即返回结果,否则将等待任务完成,然后返回结果。
public boolean isDone():如果任务完成,则返回true,否则返回false
可以看到Callable和Future做两件事-Callable与Runnable类似,因为它封装了要在另一个线程上运行的任务,而Future用于存储从另一个线程获得的结果。实际上,future也可以与Runnable一起使用。
要创建线程,需要Runnable。为了获得结果,需要future。
Java库具有具体的FutureTask类型,该类型实现Runnable和Future,并方便地将两种功能组合在一起。
可以通过为其构造函数提供Callable来创建FutureTask。然后,将FutureTask对象提供给Thread的构造函数以创建Thread对象。因此,间接地使用Callable创建线程。
🌟线程池概述
很优秀,可以重复使用,有时很闲,给到任务时很忙,定长线程池长度固定,没有被完成的任务会排队等待线程A闲下来,再去完成
✨缓存线程池(长度无限制)
任务加入后的流程:
1.判断线程池是否存在空闲线程
2.存在则使用
3.不存在,则创建线程,并放入线程池,然后使用
package thread;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class test {
public static void main(String[] args){
//创建一个缓存线程池
ExecutorService service = Executors.newCachedThreadPool();
//指挥线程池执行新的任务
service.execute(new Runnable(){
public void run(){
System.out.println(Thread.currentThread().getName()+"锄禾日当午");
}
});
service.execute(new Runnable(){
public void run(){
System.out.println(Thread.currentThread().getName()+"h汗滴禾下土");
}
});
service.execute(new Runnable(){
public void run(){
System.out.println(Thread.currentThread().getName()+"谁知盘中餐");
}
});
try{
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
service.execute(new Runnable(){
public void run(){
System.out.println(Thread.currentThread().getName()+"谁知盘中餐");
}
});
}
}
结论:缓存线程池中主线程休眠一秒,任务会被给到已创建了的空闲线程执行
✨定长线程池:长度固定,不允许在扩容
任务加入后的流程:
1.判断线程池是否存在空闲线程
2.存在则使用
3.不存在空闲线程,且线程池未满,创建线程并放入线程池再使用
4.不存在空闲线程,且线程池已满,等待线程池存在空闲线程
public class test {
public static void main(String[] args){
//创建一个定长线程池,固定长度给2
ExecutorService service = Executors.newFixedThreadPool(2);
//指挥线程池执行新的任务
service.execute(new Runnable(){
public void run(){
System.out.println(Thread.currentThread().getName()+"锄禾日当午");
}
});
与缓存线程池一样,每执行一个任务休眠3秒钟,任务会排队给到线程池中空闲下来的线程去执行
✨单线程线程池:任务均由一个线程去执行
执行流程:
1.判断线程池的那个线程 是空闲
2.空闲则使用
3.不空闲则等待,池中的单个线程空闲后 使用
public class test {
public static void main(String[] args){
//创建一个定长线程池,固定长度给2
ExecutorService service = Executors.newSingleThreadExecutor();
//指挥线程池执行新的任务
service.execute(new Runnable(){
public void run(){
System.out.println(Thread.currentThread().getName()+"锄禾日当午");
}
});
✨周期定长线程池:
1.判断线程池是否存在空闲线程
2.存在则使用
3.不存在空闲线程,且线程池未满,创建线程并放入线程池再使用
4.不存在空闲线程,且线程池已满,等待线程池存在空闲线程
周期性任务执行时:
定时执行,当某个时机触发时,自动执行某任务
创建1:定时执行一次任务
public class test {
public static void main(String[] args){
//创建一个定长线程池,固定长度给2
ScheduledExcecutorService service=Executors.newScheduledThreadPool(2);
//1.定时执行一次:3个参数
参数1:定时执行的任务
参数2:时长数字
参数3:时长数字的时间单位,TimeUnit的常量指定(如:秒、分)
service.schedule(new Runnable(){
public void run(){
System.out.println(Thread.currentThread().getName()+"锄禾日当午");
}
},delay:5,TimeUnit.SECONDS);
创建2:周期执行一次任务
public class test {
public static void main(String[] args){
//创建一个定长线程池,固定长度给2
ScheduledExcecutorService service=Executors.newScheduledThreadPool(2);
//2.周期执行:4个参数
参数1:任务
参数2:延迟时长数字(第一次执行在什么时间以后)
参数3:周期时长数字(每隔多久执行一次)
参数4.时长数字的单位
service.scheduleFixedRate(new Runnable(){
public void run(){
System.out.println(Thread.currentThread().getName()+"锄禾日当午");
}
},initialDelay:5,period:1,TimeUnit.SECONDS);
🌟Lambda表达式
格式:
(参数列表)->(一些方法的代码);
解释格式:
():接口中的抽象方法的参数列表,没有参数,就空着;有参数就写出参数,多个参数使用逗号分隔
->:传递的意思,把参数传递给方法体{}
{}:重写接口的抽象方法
✨无参数返回值的练习
public class test {
public static void main(String[] args) {
//调用invokeCook方法,参数是Cook接口,传递Cook接口的匿名内部类
invokeCook(new Cook() {
public void makeFood() {
System.out.println("吃饭了");
}
});
//使用Lambda比阿大使,简化匿名内部类
invokeCook(()->{
System.out.println("吃饭了");
});
}
//定义一个方法,参数传递Cook接口,方法内部调用Cook接口中的方法makeFood
public static void invokeCook(Cook cook){
cook.makeFood();
}
public interface Cook{
void makeFood();
}
✨Lambada有参数练习
例1 需求:
使用数组存储多个Person对象
对数组中的Person对象使用Array的sort方法通过年龄进行升序排序
代码实现:
package main;
import Person.Person;
import java.util.Arrays;
import java.util.Comparator;
public class Test {
public static void main(String[] args) {
//使用数组存储多个Person对象
Person[] arr = {
new Person("柳岩",38),
new Person("迪丽热巴",18),
new Person("古力娜扎",19)
};
//对数组中的Person对象使用Arrays的sort()方法通过年龄进行升序(前边-后边)排序
/*Arrays.sort(arr,new Comparator<Person>(){
public int compare(Person o1,Person o2){
return o1.getAge()-o2.getAge();
}
});*/
//使用Lambda表达式,简化匿名内部类
//因为想要简化的内部类对象有参数,
Arrays.sort(arr,(Person o1,Person o2)->{
return o1.getAge()-o2.getAge();
});
//遍历数组
for(Person p:arr){
System.out.println(p);
}
}
}
//打印结果则是按照年龄顺序排列的
✨Lambada有参数有返回值的练习
例2 需求:
给定一个计算器Calculator接口,内含抽象方法calc可以讲两个int数字相加得到和值,使用Lambda的标准格式调用invokeCalc方法,完成120和130的相加计算
需求解析:
1.定义一个方法
2.参数传递两个int类型的整数
3.参数传递Calculator接口
4.方法内部调用Calculator中的方法calc计算两个整数的和
接口:
package main;
//给定一个计算器Calculator接口,内含抽象方法calc可以将两个int数字相加得到和值
public interface Calculator {
//定义一个计算两个int整数和的方法,并返回结果
public abstract int calc(int a,int b);
}
测试类:
package main;
public class Test{
public static void main(String[] args) {
//调用invokeCalc方法,方法的参数是一个接口,可以使用匿名内部类
invokeCalc(10,20,new Calculator(){
public int calc(int a,int b){
return a+b;
}
});
//使用invokeCalc方法,方法的参数是一个接口,可以使用匿名内部类
invokeCalc(120,130,(int a,int b)->{
return a + b;
});
}
/**
* 定义一个方法
* 参数传递两个int类型的整数
* 参数传递Calculator接口
* 方法内部调用Calculator中的方法calc计算两个整数的和
*/
public static void invokeCalc(int a,int b,Calculator c){
int sum = c.calc(a,b);
System.out.println(sum);
}
}
✨Lambda表达式:可推导,可以省略
凡是根据上下文推导出来的内容,都可以省略不写
可以省略的内容:
1.(参数列表):括号中的参数列表的数据类型,可以省略不写
2.(参数列表):括号中的参数如果只有一个,那么类和()都可以省略
3.(一些代码):如果()中的代码只有一行,无论是否有返回值,都可以省略({},return,分号)。注意:要省略{},return,分号必须一起省略
如:
1.new Thread(()->{
System.out.println(“Thread.currentThread().getName()+”新线程建立了”);
}).start();
优化省略lambda:
new Thread(
()->System.out.println(“Thread.currentThread().getName()+”新线程建立了”)).start();
2.invokeCook(()->{System.out.println(“吃饭了”);});
优化省略Lambda:
invokeCook(()->System.out.println(“吃饭了”));
3.Arrays.sort(arr.(Person o1,Person o2)->{
Return o1.getAge()-o2.getAge();
});
优化省略Lambda:
Arrays.sort(arr,(o1,o2)->o1.getAge()-o2.getAge());
4.invokeCalc(120,130(int a, int b)->){
return a+b;
}
invokeCalc(120,130,(a,b)->a+b);