一、获取线程引用Thread.currentThread()
若使用继承Thread,直接使用this拿到线程实例;
若使用Runnable/lambda,不能使用this(不再指向Thread对象),只能Thread.cyrrentThread();
二、线程休眠 sleep()
sleep清空标志位,是为了提供更多的“可操作行空间”.
若sleep()休眠时间不到,就要终止线程。在catch中加入一些代码进行处理:
1.加break:让线程立即结束;
2.不加break:让线程不结束,继续执行;
3.写一些其他代码再break:让线程执行一些逻辑后再结束;
三、线程状态 getState()
1.NEW:
Thread对象被创建好了,但是还没有调用strat方法在系统中创建线程。
-
描述:线程被创建但尚未启动。
-
触发条件:通过
new Thread()
创建线程对象,但尚未调用start()
方法。 -
示例:
2.RUNNABLE:
就绪状态,正在运行/随时可以去cpu上执行。
-
描述:线程正在 JVM 中执行,但可能正在等待操作系统资源(如 CPU 时间片)。
-
触发条件:调用
start()
方法后,线程进入 RUNNABLE 状态。 -
注意:
-
RUNNABLE 状态包括 就绪(Ready) 和 运行中(Running) 两种情况。
-
线程是否真正运行取决于操作系统的调度。
-
-
示例:
-
3.BLOCKED:
锁竞争引起的堵塞。
-
描述:线程因为等待获取锁而进入阻塞状态。
-
触发条件:
-
线程尝试进入一个被其他线程持有的同步代码块或方法。
-
线程在等待进入
synchronized
块时。
-
-
示例:
-
4.WAITING:
不带时间的堵塞,必须满足一定的条件才能解除堵塞。
-
描述:线程无限期等待其他线程执行特定操作。
-
触发条件:
-
调用
Object.wait()
方法。 -
调用
Thread.join()
方法(不带超时参数)。
-
-
示例:
-
5.TIMED_WAITING:
指定时间的堵塞,到达一定时间会自动解除堵塞。
-
描述:线程在指定的时间内等待。
-
触发条件:
-
调用
Thread.sleep(long millis)
方法。 -
调用
Object.wait(long timeout)
方法。 -
调用
Thread.join(long millis)
方法。
-
-
示例:
-
6.TERMINATED:
Thread对象仍存在,但系统内部线程已经执行完毕。
-
描述:线程执行完毕或异常退出。
-
触发条件:
-
线程的
run()
方法执行完毕。 -
线程抛出未捕获的异常。
-
-
示例:
-
7.关系图
四、线程安全问题
为了实现“并发编程”引入多线程。
某个代码,无论单线程执行/多线程下执行,均不产生bug=>“线程安全”
单线程下运行正确,多线程下产生bug=>“线程不安全”/“存在线程安全问题”
例子:count++
一个线程自增5w次,2个线程自增10w次,实际结果不符合预期=>"bug"
1.load 从内存中读取数据到cpu寄存器中
count++ => 2.add 把寄存器的值+1
3.save 把寄存器的值写回到内存中
需要确保第一个线程save之后,第二个线程再load,否则会有bug。
实际还会存在,一个线程执行2/N次自增,被另一线程覆盖成自增一次的结果。
加锁是把count++这三步操作变成原子的。但在加锁后,执行三个操作过程中,线程仍会调度。
但即使加锁的线程被调度走,其他线程也无法“插队执行”。
线程不安全的原因
1.根本原因
操作系统上的线程是“随机调度,抢占式执行” <=> 线程之间执行的顺序有很多变数
2.代码结构
代码中多个线程,同时修改同一个变量。
①一个线程修改一个变量(安全)
②多个线程读取一个变量(安全)->只读取,变量的内容固定不变
③多个线程修改不同变量(安全)->不同变量,彼此之间不会相互覆盖
3.直接原因
多线程修改操作,本身不是“原子的”
count++ => 多个cpu指令,一个线程执行这些指令,执行到一半,可能被调度走。
若每个cpu指令均为“原子的”要么不执行,要么执行完。
4.内存可见性问题
5.指令重排序问题
解决方法:
针对原因一:
无法做出任何改变
针对原因二:
分情况,有时代码结构可调整,有时不可调整。
针对原因三:synchronized
原子性
通过特殊手段,将三个指令打包到一起,成为“整体”。加锁,锁具有“互斥”,“排他”性。
加锁的目的:为了把三个操作,打包成一个原子的操作(两个线程是否是针对同一个对象加锁)
进行加锁时,需要先准备好“锁对象”。加锁解锁操作,都是依托于这个“锁对象”来展开的。
如果一个线程针对一个对象加锁以后,其他线程也尝试对这个对象加锁。=>阻塞(BLOCKED)
一直阻塞到前一个线程释放锁为止。=>锁冲突/锁竞争
synchronized
是调用系统的api进行加锁,系统的api本质上是靠cpu的特定指令来完成加锁的。
任何一个Object都可作为锁对象。重要的是:两个线程之间是否使用同一个对象,如果是同一个对象,就会竞争,不是同一个就不会。
synchronized修饰普通方法 <=> 给this加锁(锁对象this)
synchronized修饰静态方法 <=> 给类对象加锁
注:如果synchronized加到static方法上 <=>给类对象加锁
①一个线程加锁,一个不加锁仍存在线程安全问题
②两个线程针对不同的对象加锁,仍会存在线程安全问题
③对加锁操作的一些理解
类对象
把count放到一个Test t对象中,通过上述add方法来修改,加锁的时候锁对象写作this
获取到Test的类对象。在一个Java进程中,一个类的类对象只有一个。
注:JVM执行.class时,要先把这个.class文件读取到内存中,才能执行(类加载)
JVM中需要一些特定的数据结构,来表示加载好的这些数据结构。
使用类名.class就会得到这个类的类对象(每个类都有类对象)
类对象中包含了这个类的各种信息:类名,属性,属性名,属性类型(private,public)方法,方法名,参数类型,注解,继承了哪个类,实现了哪些接口......
类对象是反射机制的依据。