1.线程有几种状态?
在Java当中,线程通常都有五种状态,创建,就绪,运行,阻塞,死亡
第一是创建状态。在生成线程对象(new操作),并没有调用该对象的start方法,这是线程处于创建状态;
第二是就绪状态。当调用了线程对象的start方法之后,该线程就进入了就绪状态,等待cpu分配时间片及其资源环境。在线程运行之后,从等待或者睡眠中回来之后,也会处于就绪状态
第三是运行状态。线程调度程序将处于就绪状态的线程设置为当前线程,此时线程就进入了运行状态,开始运行run函数当中的代码。
第四是阻塞状态。线程正在运行的时候,被暂停,通常是为了等待某个时间的发生(比如说某项资源就绪)之后再继续运行。sleep,suspend等方法都可以导致线程阻塞。阻塞完后,会变为就绪状态
第五是死亡状态。如果一个线程的run方法执行结束,该线程就会死亡。对于已经死亡的线程,无法再使用start方法令其进入就绪状态。
2.为什么我们调用start()方法时会执行run()方法,为什么我们不能直接调用run()方法?
这是另一个非常经典的java多线程面试问题。这也是我刚开始写线程程序时候的困惑。现在这个问题通常在电话面试或者是在初中级Java面试的第一轮被问到。这个问题的回答应该是这样的。当你调用start()方法时你将创建新的线程,并且执行run().但是如果你直接调用run(),他不会创建新的线程也不会。
简单来说:new一个Thread,线程进入新建状态,调用start,线程处于就绪状态,当分配到时间片就可以开始运行。start()会执行线程相应准备工作,然后自动执行run().而直接执行run(),会把其当成main线程下的一个普通方法执行,并不会在某个线程中执行它
3.Java中的volatile关键是什么作用?怎样使用它?在Java中它跟synchronized方法有什么不同?
自从Java 5和Java内存模型改变以后,基于volatile关键字的线程问题越来越流行。应该准备好回答关于volatile变量怎样在并发环境中确保可见性。
volatile关键字的作用是:保证变量的可见性。在java内存结构中,每个线程有自己独立的内存空间,这里指线程栈,当需要对一个共享变量操作时,线程会将数据从主存空间复制到自己的独立空间进行操作,然后在某个时刻将修改后的值刷新到主存空间。注意。
这个中间时间就会发生许多奇奇怪怪的线程安全问题了,volatile就出来了,它保证读取数据时只从主存空间读取,修改数据直接修改到主存空间中去,这样就保证了这个变量对多个操作线程的可见性了。换句话说,被volatile修饰的变量,能保证该变量的 单次读或者单次写 操作是原子的。
但是线程安全是两方面需要的 原子性(指的是多条操作)和可见性。volatile只能保证可见性,synchronized是两个均保证的。
volatile轻量级,只能修饰变量;synchronized重量级,还可修饰方法。
volatile不会造成线程的阻塞,而synchronized可能会造成线程的阻塞。
4.什么是原子操作,Java中的原子操作是什么?
非常简单的java线程面试问题,接下来的问题是你是否需要同步一个原子操作。原子操作是不可分割的操作,一个原子操作中间是不会被其他线程打断的。所以不需要同步一个原子操作
i++不是一个原子操作,它包含读取-修改-写入操纵,在多线程下是不安全的,java内存模型允许将64位读写操作分解为2个32位的操作,所以对long和double的单次读写操作并不是原子操作,注意volitile可以使他们成为原子操作
5.现在有T1、T2、T3三个线程,你怎样保证T2在T1执行完后执行,T3在T2执行完后执行?
这个线程问题通常会在第一轮或电话面试阶段被问到,目的是检测你对”join”方法是否熟悉。这个多线程问题比较简单,可以用join方法实现。
核心:
thread.Join把指定的线程加入到当前线程,可以将两个交替执行的线程合并为顺序执行的线程。
比如在线程B中调用了线程A的Join()方法,直到线程A执行完毕后,才会继续执行线程B。
想要更深入了解,建议看一下join的源码,也很简单的,使用wait方法实现的。t.join(); //调用join方法,等待线程t执行完毕
t.join(1000); //等待 t 线程,等待时间是1000毫秒。
public static void main(String[] args) {
method01();
method02();
}
/**
* 第一种实现方式,顺序写死在线程代码的内部了,有时候不方便
*/
private static void method01() {
Thread t1 = new Thread(new Runnable() {
@Override public void run() {
System.out.println("t1 is finished");
}
});
Thread t2 = new Thread(new Runnable() {
@Override public void run() {
try {
t1.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("t2 is finished");
}
});
Thread t3 = new Thread(new Runnable() {
@Override public void run() {
try {
t2.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("t3 is finished");
}
});
t3.start();
t2.start();
t1.start();
}
/**
* 第二种实现方式,线程执行顺序可以在方法中调换
*/
private static void method02(){
Runnable runnable = new Runnable() {
@Override public void run() {
System.out.println(Thread.currentThread().getName() + "执行完成");
}
};
Thread t1 = new Thread(runnable, "t1");
Thread t2 = new Thread(runnable, "t2");
Thread t3 = new Thread(runnable, "t3");
try {
t1.start();
t1.join();
t2.start();
t2.join();
t3.start();
t3.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
join()源码:
/**
* Waits at most <code>millis</code> milliseconds for this thread to
* die. A timeout of <code>0</code> means to wait forever.
*/
//此处A timeout of 0 means to wait forever 字面意思是永远等待,其实是等到t结束后。
public final synchronized void join(long millis) throws InterruptedException {
long base = System.currentTimeMillis();
long now = 0;
if (millis < 0) {
throw new IllegalArgumentException("timeout value is negative");
}
if (millis == 0) {
while (isAlive()) {
wait(0);
}
} else {
while (isAlive()) {
long delay = millis - now;
if (delay <= 0) {
break;
}
wait(delay);
now = System.currentTimeMillis() - base;
}
}
}
说明:
从代码中,我们可以发现。当millis==0时,会进入while( isAlive() )循环;即只要子线程是活的,主线程就不停的等待。
我们根据上面解释join()作用时的代码来理解join()的用法!
wait()的作用是让“当前线程”等待,而这里的“当前线程”是指当前运行的线程。虽然是调用子线程的wait()方法,但是它是通过“主线程”去调用的;所以,休眠的是主线程,而不是“子线程”!
这样理解: 例子中的Thread t只是一个对象 , isAlive()判断当前对象(例子中的t对象)是否存活, wait()阻塞的是当前执行的线程(例子中的main方法)
可以看出,Join方法实现是通过wait()。 当main线程调用t.join时候,main线程会获得线程对象t的锁(wait 意味着拿到该对象的锁),调用该对象的wait(),直到该对象唤醒main线程 ,比如退出后。这就意味着main 线程调用t.join时,必须能够拿到线程t对象的锁。
6.在java中wait和sleep方法的不同?
在等待时wait会释放锁,sleep一直持有锁.wait通常用于线程间交互,sleep通常用于暂停执行。
start stop yield sleep interrupt join(Thread常用方法)
wait/nofity/notifyAll(Object线程有关方法)
7.用Java写一个会导致死锁的程序,你将怎么解决?
这是我最喜欢的Java线程面试问题,因为即使死锁问题在写多线程并发程序时非常普遍,但是很多侯选者并不能写deadlock free code(无死锁代码?),他们很挣扎。只要告诉他们,你有N个资源和N个线程,并且你需要所有的资源来完成一个操作。为了简单这里的n可以替换为2,越大的数据会使问题看起来更复杂。通过避免Java中的死锁来得到关于死锁的更多信息。(参考自Java多线程编程核心技术)
/**
* 简单死锁程序
* lockA、lockB分别是两个资源,线程A、B必须同是拿到才能工作
* 但A线程先拿lockA、再拿lockB
* 线程先拿lockB、再拿lockA
* @author liqiang
* @date 2019-8-11
*/
public class SimpleDeadLock{
public static void main(String[] args) {
Object lock1 = new Object();
Object lock2 = new Object();
A a = new A(lock1, lock2);
B b = new B(lock1, lock2);
a.start();
b.start();
}
}
class A extends Thread{
private final Object lock1;
private final Object lock2;
public A(Object lock1,Object lock2){
this.lock1 = lock1;
this.lock2 = lock2;
}
public void run(){
synchronized (lock1){
try{
Thread.sleep(1000);
synchronized (lock2){
System.out.println("A");
}
}catch(InterruptedException e){
}
}
}
}
class B extends Thread{
private final Object lock1;
private final Object lock2;
public B(Object lock1,Object lock2){
this.lock1 = lock1;
this.lock2 = lock2;
}
public void run(){
synchronized (lock2){
try{
Thread.sleep(1000);
synchronized (lock1){
System.out.println("B");
}
}catch(InterruptedException e){
}
}
}
}
本文参考:
https://blog.youkuaiyun.com/ll666634/article/details/78615505
Java多线程编程核心技术