2022-08-02 第二小组 张鑫 学习笔记
实训二十五天 多线程
1.学习重点
1.线程创建
2.守护线程
3.线程的生命周期
4.CPU多核内存
5.线程安全等问题
2.学习心得
今天初步学习了线程的一些知识,对于概念的理解还是很难的。
3.学习内容
创建线程
在Java中,创建线程有3种方式
- 继承Thread类,并且重写run方法
Thread类中的run方法不是抽象方法,Thread类也不是抽象类
MyThread当继承了Thread类之后,它就是一个独立的线程
要让线程启动,调用线程的start方法
class MyThread extends Thread{
@Override
public void run(){
System.out.println("2");
}
}
public class Ch01 {
public static void main(String[] args) {
System.out.println("1");
//
MyThread myThread =new MyThread();
//当调用start方法启动一个线程时,会执行重写的run方法的代码
//调用的是start。执行的是run,为什么不直接调run
myThread.start();
//普通的对象调方法
myThread.run();
//线程的优先级,概率问题!做不到百分百
//90%会先执行主方法,10%先执行线程
System.out.println("3");
System.out.println("4");
}
- 实现Runnable接口
class MyThread2 implements Runnable{
@Override
public void run(){
System.out.println(2);
}
}
public class Ch02 {
public static void main(String[] args) {
System.out.println(1);
//start
MyThread2 myThread2 =new MyThread2();
//如果想要让线程启动,必须调用Thread类中的start方法
Thread t = new Thread(myThread2);
t.start();
System.out.println(3);
System.out.println(4);
}
}
箭头函数
public class Ch03 {
public static void main(String[] args) {
System.out.println(1);
//箭头函数接口,抽象类,重写方法
new Thread(
() -> System.out.println(2)
).start();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println(3);
System.out.println(4);
}
}
- 实现Callable 接口
class MyThread3 implements Callable<String>{
@Override
public String call() throws Exception{
System.out.println(2);
return "Call方法的返回值";
}
}
public class Ch04 {
public static void main(String[] args) {
System.out.println(1);
FutureTask<String> futureTask = new FutureTask<>(new MyThread3());
new Thread(futureTask).start();
System.out.println(3);
System.out.println(4);
}
}
守护线程
Java中提供两种类型的线程
- 用户线程
- 守护程序线程
守护线程为用户线程提供服务,仅在用户线程运行时才需要
守护线程对于后台支持的任务非常有用
垃圾回收,大多数JVM线程都是守护线程
QQ,主程序就是用户线程
任何线程继承创建它的线程守护进程状态,由于主线程是用户线程
因此在main方法内启动的任何线程默认都是守护线程
public class Ch05 extends Thread{
@Override
public void run(){
super.run();
}
public static void main(String[] args) {
Ch05 ch05 =new Ch05();
ch05.setDaemon(true);
ch05.start();
}
}
线程的生命周期
NEW:这个状态主要是线程未被start()调用执行
RUNNABLE:线程正在JVM中被执行,等待来自操作系统的调度
BLOCKED:阻塞,因为某些原因不能立即执行需要挂起等待
WAITING:无限期等待。Object类,如果没有唤醒,则一直等待
TIME_WAITING:有限期等待,线程等待一个指定的时间
TERMINATED:终止线程状态,线程已经执行完毕
等待和阻塞两个概念有点像,阻塞因为外部原因,需要等待
等待一般是主动调用方法,发起主动的等待,等待还可以传入参数确定等待时间
CPU多核缓存结构
物理内存:硬盘内存。(固态硬盘,尽量不要选择混合硬盘)
CPU缓存为了提高程序运行的性能,现在CPU在很多方面对程序进行优化
CPU处理速度最快,内存次之,硬盘速度最低
在CPU处理内存数据时,如果内存运行速度太慢,就会拖累CPU的速度
为了解决这样的问题,CPU设计了多级缓存策略
CPU分为三级缓存,每个CPU都有L1,L2缓存,但是L3缓存是多核公用的
CPU查找数据时,CPU->l1->l2->l3->内存->硬盘
从CPU到内存,60-80纳秒
从CPU到l3,15纳秒
从CPU到l2,3纳秒
从CPU到l1,1纳秒
寄存器,0.3纳秒
进一步优化,CPU每次读取一个数据,读取的是与它相邻的64个字节的数据
【缓存行】
英特尔提出了一个协议MESI协议
1.修改态,此缓存被动过,内容与主内存中不同,为此缓存专用
2.专有态,此缓存与主内存一致,但是其他CPU中没有
3.共享态,此缓存与主内存一致,其他的缓存也有
4.无效态,此缓存无效,需要从主内存中重新读取
【指令重排】
Java内存模型-JVM
尽量做到硬件和操作系统之间达到尽量一致的访问效果
volatile关键字来保证一个变量在一次读写操作时,避免指令重排
在读写操作之前加入一条指令,当CPU碰到这条指令后必须等到前面的执行执行完成才能继续执行下一条指令
【内存屏障】
可见性
thread线程一直在高速读取缓存中的isOver,不能感知主线程已经把isOver改成true
这就是可见性问题
线程争抢
解决线程争抢问题,最好的办法就是【加锁】
synchronized同步锁,线程同步
当一个方法加上了synchronized修饰,这个方法就叫做同步方法
线程安全的实现方法
(1)数据不可变
一切不可变的对象一定是线程安全的
对象的方法的实现方法的调用者,不需要再进行任何的线程安全的保障措施
比如final关键字修饰的基本数据类型,字符串
只要一个不可变的对象被正确的创建出来,那外部的可见对象永远不会改变
(2)互斥同步,加锁【悲观锁】
(3)非阻塞同步 【无锁编程】 自旋 用cas实现
(4)无同步方案 多个线程需要共享数据,但是这些数据又可以在单独的线程中计算,得出结果
我们可以把共享数据的可见范围限制在一个线程之内,这样就无需同步了,把共享数据拿过来
ThreadLocal
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】