Java多线程基础-第一节3:线程状态和线程安全
Posted 我擦我擦
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java多线程基础-第一节3:线程状态和线程安全相关的知识,希望对你有一定的参考价值。
文章目录
一:线程状态
(1)Java中的线程状态
Java中的线程状态:Java中的线程状态其实是一个枚举类型,即Thread.state
NEW
(新建状态):创建后,启动前,线程就处于该状态RUNNABLE
(可运行状态):线程正在执行代码,就处于该状态BLOCKED
(阻塞状态):一个线程获取synchronized锁对象失败,就处于该状态WAITING
(无限状态):一个线程获取Lock锁对象失败,就处于该状态。调用wait方法,线程也会处于该状态TIMED_WAITING
(计时等待状态):线程正在执行sleep方法,就处于该状态TERMINATED
(消亡状态):线程把任务执行完毕后,就处于该状态
注意BLOCKED
,WAITING
和TIMED_WAITING
这三个状态简单点来讲都表示排队等着其他事情。由于这三种状态还涉及其他一些知识,所以这里不便详细展开说明,到后面我们会深入学习的
public class TestDemo7
public static void main(String[] args)
for (Thread.State state : Thread.State.values())
System.out.println(state);
(2)线程状态转移
线程状态转移:通过方法调用、触发事件等,线程就会在上面6种状态进行切换,如下图
如下代码展示了一个简单的线程状态变化的例子
- 注意:
BLOCKED
,WAITING
和TIMED_WAITING
三种并未涉及或涉及很少,会在后文中详细讲解
public class TestDemo8
public static void main(String[] args) throws InterruptedException
Thread thread = new Thread()
@Override
public void run()
for (int i = 0; i < 10000_0000; i++)
//do nothing
try
Thread.sleep(2000);
catch (InterruptedException e)
throw new RuntimeException(e);
;
System.out.println("【thread.start()之前】");
System.out.println("thread状态:" + thread.getState());
System.out.println("main状态:" + Thread.currentThread().getState());
thread.start();
System.out.println("【thread.start()之后】");
System.out.println("thread状态:" + thread.getState());
System.out.println("main状态:" + Thread.currentThread().getState());
Thread.sleep(500);
System.out.println("【main睡眠0.5s之后】");
System.out.println("thread状态:" + thread.getState());
System.out.println("main状态:" + Thread.currentThread().getState());
thread.join();
System.out.println("【thread.join()之后】");
System.out.println("thread状态:" + thread.getState());
System.out.println("main状态:" + Thread.currentThread().getState());
//结束工作,状态为TERMINGATED
System.out.println("thread状态:" + thread.getState());
System.out.println("main状态:" + Thread.currentThread().getState());
二:线程安全
(1)线程安全的概念
线程安全:如果多线程环境下代码运行的结果是符合我们预期的,即在单线程环境下应该的结果,则说明这个程序是线程安全的
如下是一个线程不安全的例子:两个线程分别对counter
对象中tickets
成员(初始值为10W)连续自减5W次,在单线程情况下,最后tickets
的结果应该为0,但运行后结果却出现了很大的误差,而且每次运行结果都存在差异
class Counter
public int tickets = 100000;
public void decrease()
tickets--;
public class TestDemo9
private static Counter counter = new Counter();
public static void main(String[] args) throws InterruptedException
//下面两个线程,每个线程都会counter进行5W次自减
//正确结果理应为0
Thread thread1 = new Thread()
@Override
public void run()
for(int i = 0; i < 50000; i++)
counter.decrease();
;
Thread thread2 = new Thread()
@Override
public void run()
for(int i = 0; i < 50000; i++)
counter.decrease();
;
thread1.start();
thread2.start();
thread1.join();
thread2.join();
System.out.println("counter: " + counter.tickets);
实际上,tickets--
并非原子性操作,在底层涉及如下三条指令
而线程调度时又没有确定的顺序,所以这两个线程在执行时就可能出现各种各样的执行顺序,结果自然也是五花八门
(2)线程不安全的原因
①抢占式执行:多个线程的调度执行过程,可以视为是全随机的。所以在写多线程代码的时候,就需要考虑到在任意一种调度情况下都能够运行出正确结果,这无疑增加了编程的难度
②修改共享数据:上面的线程不安全代码中,至少涉及2个线程针对counter.tickets
变量的修改,这个counter.tickets
便是多个线程都能访问到的共享数据
counter.tickets
实际位于堆上,所以可以被多个线程共享访问- 在之前的学习中,我们常常会说××是线程安全的。例如String,它被设置为了
private
,是不可变的,所以不能修改,因而是线程安全的
③原子性操作:原子是不可分割的最小单位,原子性操作是指动作要一气呵成而不能中断。上面的线程不安全代码中,tickets--
就不是一个原子性操作,会涉及如下三条指令
mov
sub
mov
CPU执行程序的最小单位便是一个指令,所以有可能会出现三条指令还没有被执行完就被调度走的情况出现,也即一个线程正在对某个变量操作,中途其他线程突然插入了进来导致操作被打断,结果极有可能就会出现错误。原子性也是解决线程不安全最常用的方法,我们可以把多个操作通过特殊手段打包成一个原子操作
④内存可见性:可见性是指一个线程对共享数据的修改,其它线程能够及时看到。这个问题主要是因编译器优化而导致的
- 此内容将在下一节详细介绍
⑤指令重排:
- 此内容将在下一节详细介绍
以上是关于Java多线程基础-第一节3:线程状态和线程安全的主要内容,如果未能解决你的问题,请参考以下文章
不会吧,你连Java 多线程线程安全都还没搞明白,难怪你面试总不过
Java多线程基础-第一节4:synchronized关键字(监视器锁monitor lock)和volatile关键字