1、java线程常见的基本状态有哪些,这些状态分别是做什么的
(1)创建(New):new Thread(),生成线程对象。
(2)就绪(Runnable):当调用线程对象的star()方法,线程就会进入就绪状态,但此刻线程调度还没把该线程设置为当前线程,就是还没有获得CPU使用权。如果线程从睡眠或等待回来之后,也会进入就绪状态。
(3)运行(Running):程序将处于就绪状态的线程设置为当前线程,即获得CPU使用权,这个时候线程进入运行状态,开始运行run里面的逻辑。
(4)阻塞(Blocked):分为等待阻塞跟同步阻塞
等待阻塞:调用wait(状态就会变成WAITING状态),或调用sleep(状态就会变成TIMED_WAITING),调用join或发出IO请求。进入该状态的线程需要等待其他线程做出一定的动作(通知或中断),处于这种状态的线程CPU不会分配过来,它们需要被唤醒,也可能会无限等待下去,阻塞结束后线程重新进入就绪状态。
同步阻塞:线程在获取synchronized同步锁失败,即锁被其他线程占用,它就会进入同步阻塞状态。
(5)死亡(TERMINATED):一个线程run方法执行结束,该线程就死亡了,不能进入就绪状态。
注意:有些文档把就绪和运行两种状态统一称为 “运行中”。
等待(WAITING):进入该状态的线程需要等待其他线程做出一些特定动作(通知或中断)。
超时等待(TIMED_WAITING):该状态不同于WAITING,它可以在指定的时间后自行返回。
2、多线程开发里面常用的方法有哪些,分别解释下
(1)sleep
属于线程Thread的方法
让线程暂缓执行,等待预计时间之后再恢复
交出CPU使用权,不会释放锁
进入阻塞状态TIMED_WAITING,睡眠结束则变为Runnable
(2)yield
属于线程Thread的方法
暂停当前线程的对象,去执行其他线程
交出交出CPU使用权,不会释放锁
不会让线程进入阻塞状态,直接变为就绪Runnable,只需要重新获得CPU使用权
作用:让相同优先级的线程轮流执行,但是不保证一定轮流
(3)join
属于线程Thread的方法
在主线程上运行调用该方法,会让主线程休眠,不会释放已经持有的对象锁
让调用join方法的线程先执行完毕,在执行其他线程
(4)wait
属于Object的方法
当前线程调用对象的wait方法,会释放锁,进入线程的等待队列
需要依靠notify或者notifyAll唤醒,或者wait(timeout)时间自动唤醒
(5)notify
属于Object的方法
唤醒在对象监视器上等待的单个线程,选择是任意的
(6)notifyAll
属于Object的方法
唤醒在对象监视器上等待的全部线程
3、画出线程的状态转换图和这些转换过程常用的api
4、哪些不是线程安全的数据结构(哪些是线程安全的数据结构)
ArrayList、LinkedList、HashSet、TreeSet、HashMap、TreeMap。
(Vector、Hashtable
)
5、Java中可以有哪些方法来保证线程安全
- 加锁,比如synchronize/ReentrantLock
- 使用volatile声明变量,轻量级同步,不能保证原子性(需要解释)
- 使用线程安全类(原子类AtomicXXX,并发容器,同步容器 CopyOnWriteArrayList/ConcurrentHashMap等
- ThreadLocal本地私有变量/信号量Semaphore等
6、解释一下volatile关键字,还有它跟synchronized有什么区别
volatile是轻量级的synchronized,保证了共享变量的可见性,被volatile关键字修饰的变量,如果值发生了变化,其他线程立刻可见,避免出现脏读的现象。
区别:volatile保证可见性,但是不能保证可见性。
synchronized保证可见性,也保证可见性。
7、为什么会出现脏读
JAVA内存模型简称 JMM,JMM规定所有的变量存在主内存,每个线程都有自己的工作内存,线程对变量的操作都在工作内存进行,不能直接对主内存进行。
volatile关键字修饰的变量是随时可以看到自己最新的值,假如线程1对变量v进行修改,那么线程2是可以马上看见。
使用volatile修饰变量的话,每次读取前必须从主内存属性最新的值,每次写入需要立刻写到主内存中。
8、解释下什么是指令重排
指令重排序分两类 编译器重排序和运行时重排序。JVM在编译java代码或者CPU执行JVM字节码时,对现有的指令进行重新排序,主要目的是优化运行效率(不改变程序结果的前提)。
9、解释下happens-before
先行发生原则,volatile的内存可见性就提现了该原则之一。