目录
Thread类
1.创建线程(告诉Thread 对象,线程的入口函数)
- 继承Thread 重写run
- 实现Runnable 重写 run,并把Runnable 实例传给Thread
- 使用匿名内部类继承Thread 重写 run
- 使用匿名内部类实现Runnable 重写 run,并把 Runnable 实例传给 Thread
- 使用 lambda 表达式
2.Thread属性
- id
- name
- state
- priority
- daemon
- alive
- islnterrupted
3.启动线程:start(创建新线程)
run不创建新线程
4.中断一个线程,让线程的入口方法执行完毕
5.等待线程:控制线程结束的先后顺序 join
6.获取线程实例:this/ Thread.currentThread()
7.线程休眠:sleep(休眠是指把线程的task struct 放到一个等待队列中)
线程的状态
- Thread.State: Thread 类的线程状态
- NEW-Thread:对象刚刚创建,还没有在系统中创建线程
- RUNNABLE:线程是一个准备就绪的状态,随时可能调度到CPU 上执行(线程的task struct 在就绪队列中)
- BLOCKED/ WAITING/ TIMED_WAITING :线程阻塞
- TERMINATED:系统中的线程结束了(入口方法执行完毕),Thread 对象还没销毁
线程安全
JMM:Java memory model
JVM 中的操作内存的具体的实现方式。
工作内存:CPU 寄存器 + 缓存
主内存:真实内存
原因
1.线程的抢占式执行过程(随机性的来源)
这一部分是操作系统内核决定的,程序员无法改变。
2.多线程修改同一个变量
对同一变量进行操作才会造成线程安全问题,如果两个线程各自操作两个变量,那么互不干扰,不会出现问题。
3.修改操作不是原子的
例如 count++ 操作,需要分为从内存中读取数据、在寄存器中修改数据、写会数据至内存中这三个部分。由于操作不是原子的,每进行一部分都会有其他线程进行抢占,导致最后写回的数据不是最终想要的结果。
4.内存可见性
两个线程同时操作一个内存,进行修改的线程由于没有及时的将数据写回,可能导致读线程读取的数据是修改之前的数据或者修改之后的数据。这时可以禁止编译器优化解决。
5.指令重排序
系统为了性能优化,将指令重排序成最方便的操作顺序。
解决
synchronized 监视器锁:保证操作的原子性、保证内存可见性和禁止指令重排序。
可以修饰方法或代码块
可重入锁:针对锁被锁两次的情况。内部有一个计数器,用于记录当前被锁的次数,进行解锁后,计数器减一,直到计数器为0时,才释放锁。
volatile 辅助保证线程安全:禁止指令重排序,保证内存可见性,但不保证原子性。
主要用于读写同一个变量,但不能用于同时写操作。
对象等待集
协调多个线程之间执行的先后顺序。需要配合 synchronized 一起使用(同一个对象)。
wait:
1.释放锁
2.等待其他线程通知
3.通知来了之后,重新尝试获取锁
notify:
1.通知某个线程被唤醒
2.执行完当前 synchronized 后才释放锁
notify调用 wait 的线程,剩余部分依然在waiting状态的队列。因为 notify 只会唤醒一个线程。
notifyAll调用wait的线程,剩余部分在blocked 状态的队列,因为被唤醒的线程放入 runnable 队列会竞争锁,失败的线程会被放入 blocked 状态的队列。