线程入门
1.创建线程
在Java中,创建线程有3种方式。
(1)继承Thread类,并且重写run方法
Thread类中的run方法不是抽象方法,Thread类也不是抽象类。
MyThread当继承了Thread类之后,它就是一个独立的线程。
要让线程启动。调用线程的start方法。
当调用start方法启动一个线程时,会执行重写的run方法的代码。
调用的是start,执行的是run,为什么不直接调run?–> myThread.run(); //是普通的对象调方法,与咱们线程什么的无关。
线程的优先级:>>>主方法的优先级高。
注意~~线程的优先级是概率问题!做不到100%
90%的可能会先跑主方法,10%的可能先跑myThread
主方法是一个独立的线程,调用启动开启另一个独立线程,它俩就是并行的两个线程。
(2)实现Runnable接口
–>使用箭头函数(lambda表达式λ)
箭头函数依赖于接口/抽象类,重写方法。小括号代表重写里面的无参方法
线程的休眠..线程中止异常
Java的单继承多实现现状,注意优先考虑实现Runnable接口(–>函数式接口,考虑使用箭头函数)。
如果想让线程启动,必须调用Thread中的start方法。
问题:实现Runnable接口后,找不到start方法了
(3)实现Callable接口
2.守护线程
Java种提供两种类型的线程:用户线程、守护程序线程。
守护线程为用户线程提供服务,仅在用户线程运行时才需要。
守护线程对于后台支持任务非常有用。
垃圾回收:大多数JVM线程都是守护线程。
QQ:主程序就是用户线程。
创建守护线程:
任何线程继承创建它的线程守护进程状态的时候,由于主线程是用户线程,因此在main方法内启动的任何线程默认都是守护线程。(用Java操作线程基本上操作的都是守护线程。)
线程的生命周期:从摇篮到坟墓
NEW:这个状态主要是线程未被start()调用执行
RUNNABLE:线程正在JVM中被执行,等待来自操作系统的调度。
BLOCKED:阻塞,因为某些原因不能立即执行,需要挂起等待。
WAITING:无限期等待。Object类,如果没有唤醒,则一直等待。
TIMED_WAITING:有限期等待,线程等待一个指定的时间。
TERMINATED:终止线程的状态,线程已经执行完毕。
等待和阻塞两个概念有点像,阻塞因为外部原因,需要等待。而等待一般是主动调用方法,发起主动的等待,等待还可以传入参数确定等待的时间。
sleep休眠
join插队
3.线程安全
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纳秒
从CPU到寄存器,0.3纳秒
进一步优化,CPU每次读取一个数据,读取的是与它相邻的64个字节的数据。缓存行
两个CPU读取同一个数据..高并发
英特尔提出一个协议MESI协议
1.修改态,此缓存被动过,内容与主内存中不同,为此缓存专有
2.专有态,此缓存与主内存一致,但是其他CPU中没有
3.共享态,此缓存与主内存一致,其他的缓存也有
4.无效态,此缓存无效,需要从主内存中重新读取
指令重排
四条指令,四个人在四张纸上写下“恭喜发财”
Java内存模型-JMM
尽量做到硬件和操作系统之间达到一致的访问效果
我们发现测试结果中大部分感觉是正确的,(0,1)或(1,0),一个是线程1先执行,一个是线程2先执行。
按道理来说,绝对不会出现(0,0),如果出现(0,0)代表存在指令重排,乱序执行。
使用volatile关键字来保证一个变量在一次读写操作时,避免指令重排。
我们在读写操作之前加入一条指令,当CPU碰到这条指令后必须等到前面的执行执行完成后才能继续执行下一条指令..内存屏障
线程之间的可见性
thread线程一直在高速读取缓存中的isOver,不能感知主线程已经把isOver修改了。
这就是线程可见性的问题。
怎么解决?–volatile能够强制改变变量的读写直接在内存中操作
线程争抢
解决线程争抢的问题,最好的办法就是加锁。
synchronized同步锁,线程同步。
当一个方法加上了synchronized修饰,这个方法就叫做同步方法。
线程安全的实现方法
(1)数据不可变。
一切不可变的对象一定是线程安全的。
对象的方法的实现,方法的调用者,不需要再进行任何的线程安全的保障措施。
比如final关键字修饰的基本数据类型,字符串。
只要一个不可变的对象被正确的创建出来,那外部的可见状态永远都不会改变。
(2)互斥同步。加锁.. 悲观锁
(3)非阻塞同步。无锁编程,自旋。我们会用cas来实现这种非阻塞同步。
(4)无同步方案。多个线程需要共享数据,但是这些数据又可以在单独的线程中计算,而得出结果。
我们可以把共享数据的可见范围限制在一个线程之内,这样就无需同步。把共享的数据拿过来,我用我的,你用你的,从而保证线程安全。ThreadLocal
原创文章,作者:ItWorker,如若转载,请注明出处:https://blog.ytso.com/tech/java/278750.html