目录
课上
1.创建线程
在Java中,创建线程有3种
1)继承Thread类,并且重写run方法
Thread类中的run方法不是抽象方法,Thread类也不是抽象类
MyThread当继承了Thread类之后,它就是一个独立的线程。
要让线程启动:调用线程的start方法
start方法存在于Thread方法里
结论:当调用start方法启动一个线程时,会执行重写的run方法的代码
调用的是start,执行的是run,为什么不直接调用run?
因为调run不是启动线程,就是普通的对象调方法。
线程的优先级(概率问题,做不到百分百)
主方法的优先级高(90%会先跑主方法,10%先跑myThread)
2)实现Runnable接口
如果继承Entends和Runnable同时出现,首选Runnable,因为Java中单继承,多实现。
Runnable是函数接口
如果想要让线程启动,必须调用Thread类中的start方法
问题:实现了Runnable接口找不到Thread类中的start方法,怎么办?
答:构建一个Thread
使用箭头函数(lambda表达式)
简写:(代码如下图)
抛出线程终止异常
3)实现Callable接口
借助FutureTask工具类。
FutureTask是泛型类
先把Callable转成FutureTask,再转成Runnable,最后转成Thread
用lambda表达式的前提必须得是函数式接口
2.守护线程
Java中提供两种类型的线程:
- 用户线程
- 守护程序线程
守护线程为用户线程提供服务,仅在用户线程运行时才需要
主方法可以理解成用户线程,自己创建的可以理解成守护程序线程
守护线程自己无法启动,服务于用户线程
守护线程对于后台支持的任务非常有用。
垃圾回收。大多数JVM线程都是守护线程。
创建守护线程
任何线程继承创建它的线程守护进程状态时,由于主线程是用户线程,因此在主方法内启动的任何线程默认都是守护线程
QQ,主程序就是用户线程
3.线程的生命周期(面试)
NEW:这个状态主要是线程未被start()调用执行
RUNNABLE:线程正在JVM中被执行,等待来自操作系统的调度
BLOCKED:(锁)阻塞。因为某些原因不能立即执行,需要挂起等待
WAITING:无限期等待。Object类,如果没有唤醒,则一直等待。
TIMED_WAITING:有限期等待,线程等待一个指定的时间
TERMINATED:终止线程的状态,线程已经执行完毕
思维导图
等待和阻塞两个概念有点像。阻塞是因为外部原因,需要等待;而等待一般是主动调用方法,发起主动地等待。
等待分为两种等待,有限期等待和无限期等待。
等待还可以传入参数确定等待时间。
sleep方法
join方法:本意是阻塞主线程
4. 线程安全
1)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个字节的数据。读取的是【缓存行】。
英特尔提出了一个MESI协议
·修改态:此缓存被动过,内容与主内存中不同,为此缓存专有
·专有态:此缓存与主内存一致,但是其他CPU中没有
·共享态:此缓存与主内存一致,其他的缓存也有
·无效态:此缓存无效,需要从主内存中重新读取
【指令重排】
2)Java内存模型—JVM
尽量做到硬件和操作系统之间达到一致的访问效果。
使用volatile关键字来保证一个变量再一次读写操作时,避免指令重排。
【内存屏障】我们在读写操作之前加入一条指令,当CPU碰到这条指令后必须等到前面指令执行完成才能继续执行下面的指令。
5.线程的可见性
thread线程一直在高速读取缓存中的isOver,不能感知到主线程以及把isOver改成了true,这就是现成的可见性的问题。
怎么解决现成的可见性?用volatile关键字,加在访问权限符后
volatile能够强制改变变量的读写直接在内存中操作。
安全问题:1.指令重排 2.线程的可见性 3.线程的争抢
6.线程的争抢
解决线程争抢的问题最好的办法就是【加锁】
synchronized同步锁,线程同步
当一个方法加上了synchronized修饰,这个方法就叫做同步方法
解决线程争抢问题最好的方式就是加锁(synchronized)
7.线程安全的实现方法
1)保证数据不可变。一切不可变的对象一定是线程安全的
对象的方法的实现、方法的调用者,不需要再进行任何的线程安全的保障措施。
比如final关键字修饰的基本数据类型;字符串。
只要一个不可变的对象能被正确的创建出来,那外部的可见状态永远都不会改变。
2)互斥同步。(加锁)【悲观锁】
3)非阻塞同步【无锁编程】,自旋。我们会用cas来实现这种非阻塞同步
4)无同步方案。多个线程需要共享数据,但是这些数据又可以在单独的线程中计算而得出结果。思想:我们可以把共享数据的可见范围限制在一个线程之内,这样就无需同步。把共享的数据拿过来,我用我的,你用你的,从而保证线程安全。ThreadLocal
课下
个人理解:
进程:
每一个独立运行的程序,每一个进程有自己的地址空间,同一进程内存数据公用
进程的状态:
1)就绪状态(Ready)
2)运行状态(Running)
3)阻塞状态(Blocked)
线程:
进程的一个执行路径,共享内存空间,线程之间可以自由切换,并发执行,一个进程至少一个线程
进程启动之后,里面若干程序又可以划分若干线程
并行:
两个任务同时运行(多个CPU)
并发:
两个任务同时请求运行,处理器一次只接受一个任务,只好轮流执行,cpu运行时间短会感觉两个任务在同时执行
线程的基本使用:
1)继承Thread类
class MyThread extends Thread{
public void run(){
// 逻辑处理
}
}
MyThread mt = new MyThread();
mt.start();
2)实现Runnable接口
class MyRunnable implements Runnable{
public void run(){
// 逻辑处理
}
}
MyRunnable mr = new Runnable();
Thread t = new Thread(mr);
t.start();
两种方法应用
出现交叉(随机性)
package com.vince;
public class ThreadDemo {
public static void main(String[] args) {
MyThread mt = new MyThread();
mt.start();//启动线程
// Runnable接口更灵活
MyRunnable mr = new MyRunnable();
Thread t2 = new Thread(mr);
t2.start();
}
}
/*
* 实现线程得第一种方式:继承Thread类
* */
class MyThread extends Thread{
@Override
public void run() {
super.run();
for (int i = 0; i <100 ; i++) {
//当前线程
System.out.println(Thread.currentThread().getName()+"-"+i);
}
}
}
/*
* 实现线程得第二种方式:实现Runnable接口
* */
class MyRunnable implements Runnable{
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName()+"-"+i);
}
}
}
线程休眠
线程的休眠:在当前线程执行中,暂停指定的毫秒数,释放cpu的时间片
在线程中加入休眠时间,时间内同一进程的线程交替执行
//class MyThread extends Thread{
// @Override
// public void run() {
// super.run();
// for (int i = 0; i <100 ; i++) {
//当前线程对象
// System.out.println(Thread.currentThread().getName()+"-"+i);
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
// }
// }
//}
join方法:
加入线程,让调用的线程先执行指定时间或执行完毕
package com.qf;
// 主线程
public class ThreadDemo {
public static void main(String[] args) {
MyRunnable mr = new MyRunnable(); // Runnable接口三步走:建对象
Thread t = new Thread(mr); // 对象放入
t.start(); // 启动线程
for (int i = 0; i < 50; i++) { // 循环
System.out.println(Thread.currentThread().getName()+"--"+i); // 打印
try { // 线程休眠
Thread.sleep(300);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
if (i==20){
try {
t.join();// 让t线程执行完毕,再执行主线程
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
class MyRunnable implements Runnable{
@Override
public void run() {
for (int i = 0; i < 50; i++) {
System.out.println(Thread.currentThread().getName()+"--"+i);
try {
Thread.sleep(300);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
输出结果:
main--18
Thread-0--18
Thread-0--19
main--19
main--20
Thread-0--20
Thread-0--21
Thread-0--22
Thread-0--23
Thread-0--24
Thread-0--25
Thread-0--26
Thread-0--27
Thread-0--28
Thread-0--29
Thread-0--30
Thread-0--31
Thread-0--32
Thread-0--33
Thread-0--34
Thread-0--35
Thread-0--36
Thread-0--37
Thread-0--38
Thread-0--39
Thread-0--40
Thread-0--41
Thread-0--42
Thread-0--43
Thread-0--44
Thread-0--45
Thread-0--46
Thread-0--47
Thread-0--48
Thread-0--49
main--21
main--22
main--23
main--24
main--25
中断线程
1)使用interrupt:设置一个中断状态(标记)
2)自定义标志的方式(推荐使用)
interruptedException:如果任何线程中断当前线程。当抛出此异常时,当前线程的中断状态将被清除。
package J804;
public class Qf1 {
public static void main(String[] args) {
MyRunnable mr = new MyRunnable(); // Runnable接口三步走:建对象
Thread t = new Thread(mr); // 对象放入
// t.start(); // 启动线程
MyRunnable3 mr3=new MyRunnable3(); //自定义时新建 1
Thread t2 = new Thread(mr3); //自定义时新建 2
t2.start(); //自定义时新建 3
for (int i = 0; i < 50; i++) { // 循环
System.out.println(Thread.currentThread().getName()+"--"+i); // 打印
try { // 线程休眠
Thread.sleep(300);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
if (i==20){
// try {
// t.join();// 让t线程执行完毕,再执行主线程
// } catch (InterruptedException e) {
// e.printStackTrace();
// }
// t.interrupt();//中断线程,只是做了个中断标记
mr3.flag = false;//自定义时新建4
}
}
}
}
class MyRunnable implements Runnable{
@Override
public void run() {
for (int i = 0; i < 50; i++) {
if (Thread.interrupted()){//测试中断状态,此方法会把中断状态清楚
break;
}
System.out.println(Thread.currentThread().getName()+"--"+i);
try {
Thread.sleep(300);
} catch (InterruptedException e) {
e.printStackTrace();
Thread.currentThread().interrupt();
}
}
}
}
//自定义时新建MyRunnable3 5
class MyRunnable3 implements Runnable{
public boolean flag = true;
public MyRunnable3(){
flag=true;
}
@Override
public void run() {
int i = 0;
while (flag){
System.out.println(Thread.currentThread().getName()+"===="+(i++));
try {
Thread.sleep(300);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
输出结果: 原先线程中断,中断执行
Thread-1====20
main--20
Thread-1====21
main--21
main--22
main--23
main--24
main--25。。。
总结
学习了新知识线程,可以说相当难,除了把课上讲的join,sleep方法记下来,其他的还不会写,晚上好好复习,把它学会!