多线程的常见面试题

1.线程连续两次调用start()方法所产生的情况

public class TestMain {

    public static void main(String[] args) throws Exception {
        Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("开始运行");
            }
        });
        System.out.println("线程当前的状态为" + thread.getState());
        thread.start();
        System.out.println("start后线程当前的状态为" + thread.getState());
        thread.start();
    }
}

运行结果:
在这里插入图片描述
由此可见:第二次调用start方法,会抛出异常java.lang.IllegalThreadStateException原因看源代码可知

private volatile int threadStatus = 0;

 public synchronized void start() {
        /**
         * This method is not invoked for the main method thread or "system"
         * group threads created/set up by the VM. Any new functionality added
         * to this method in the future may have to also be added to the VM.
         *
         * A zero status value corresponds to state "NEW".
         */
        if (threadStatus != 0)
            throw new IllegalThreadStateException();

A zero status value corresponds to state “NEW”(中文):零状态值对应于状态“NEW”
线程启动前,状态为NEW ,threadStatus 初始值为 0。线程启动后,其初始状态值会改变,所以会抛出异常。

2.调用线程对象的start()方法会执行run(),为什么不能直接调用run()方法??

start()方法启动线程后,整个线程处于就绪状态,等待虚拟机调度, 然后通过此Thread类调用方法run()来完成其运行操作的, 这里方法run()称为线程体,它包含了要执行的这个线程的内容, Run方法运行结束, 此线程终止。期间虚拟机是分时间片轮番调用各个线程体的。

run()方法启动是当作普通方法的方式调用,这里虚拟机不会线程调度,虚拟机会执行这个方法直到结束后自动退出。

3. 如何停止线程

看另一篇博客介绍
线程中断的方式 interrupt与volatile

4.如何处理不可中断的阻塞

我们知道可阻塞的方法都是通过提前返回或者抛出InterruptedException来响应中断请求的,从而使开发人员更容易构建出能响应取消请求的任务,那么不可中断的阻塞方法该如何处理呢?

不可中断阻塞的情况一般有以下几种:

  1. java.io包中的同步Socket I/O
  2. java.io包中的同步I/O
  3. Selector的异步I/O
  4. 获取某个锁

主要关键在于:继承thread类 重写原来中断线程或者取消任务的方法,在方法里面加入自己的取消操作,比如关闭数据流,关闭套接字等,然后再调用父类的中断方法,这样就可以既关闭了阻塞的任务,又中断了线程。

举例子

public class ReaderThread extends Thread {
	private static final int BUFSZ = 1024;
	private final Socket socket;
	private final InputStream in;
 
	public ReaderThread(Socket socket) throws IOException {
		this.socket = socket;
		this.in = socket.getInputStream();
	}
 
	// 覆盖Thread类的interrupt方法, 加入关闭socket的代码
	// 如果发生中断时, 线程阻塞在read方法上, socket的关闭会导致read方法抛出SocketException, 然后run方法运行完毕
	public void interrupt() {
		try {
			socket.close();
		} catch (IOException ignored) {
		} finally {
			super.interrupt();
		}
	}
 
	public void run() {
		try {
			byte[] buf = new byte[BUFSZ];
			while (true) {
				int count = in.read(buf);
				if (count < 0)
					break;
				else if (count > 0)
					processBuffer(buf, count);
			}
		} catch (IOException e) { /*  Allow thread to exit  */
		}
	}
 
	private void processBuffer(byte[] buf, int count) {
		//...
	}
}

5. 线程的状态级生命周期

线程的状态与生命周期及sleep与wait的区别

6. 用程序实现两个线程交替打印 0~100之间的奇偶数

/**
 * 两个线程循环打印0到100的奇偶数
 */
public class WaitTest {
    private static final Object lock = new Object();
    private static int count =0;

    public static void main(String[] args) throws InterruptedException {
        new Thread(new HasAnEvenNumber(lock,count),"偶数").start();
        //Thread.sleep(100);
       new Thread(new HasAnEvenNumber(lock,count),"奇数").start();
    }
}

class HasAnEvenNumber implements Runnable{
    private static int count;
    private Object lock;

    public HasAnEvenNumber(Object lock,int count) {
        this.lock = lock;
        this.count = count;
    }

    @Override
    public void run() {
        synchronized (lock){
            while (count <= 100){
                System.out.println(Thread.currentThread().getName() +"  " + count++);
                lock.notify();
                if (count<=100){
                    try {
                        lock.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }
}

7. 手写生产者消费者模式

package demo_18;

import java.util.ArrayList;


/**
 * 生产者消费者
 */
public class Producers_And_Consumers {

    public static void main(String[] args) {
        MyStack myStack = new MyStack();
        P_Thread p = new P_Thread(myStack);
        C_Thread c = new C_Thread(myStack);

        Thread threadP1 = new Thread(p);
        Thread threadP2 = new Thread(p);
        Thread threadP3 = new Thread(p);
        Thread threadP4 = new Thread(p);
        Thread threadP5 = new Thread(p);
        Thread threadP6 = new Thread(p);

        threadP1.start();
        threadP2.start();
        threadP3.start();
        threadP4.start();
        threadP5.start();
        threadP6.start();


        Thread threadC1 = new Thread(c);
        Thread threadC2 = new Thread(c);
        Thread threadC3 = new Thread(c);
        Thread threadC4 = new Thread(c);
        Thread threadC5 = new Thread(c);
        threadC1.start();
        threadC2.start();
        threadC3.start();
        threadC4.start();
        threadC5.start();

    }
}


/**
 * 生产者
 */
class MyStack{

    ArrayList<String> arrayList = new ArrayList<>();

    synchronized public void set(){
        while (arrayList.size() == 1){
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

        arrayList.add("张三");
        /** 生产后通知消费者消费*/
        this.notifyAll();
        System.out.println("set : "+arrayList.size());
    }

    synchronized public void get(){
        while (arrayList.size() == 0){
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

        System.out.println("get : "+arrayList.get(0));
        arrayList.remove(0);
        /** 消费后通知生产者生产*/
        this.notifyAll();
    }
}
/**
 * 多线程之生产者调用
 */
class P_Thread implements Runnable{

    private MyStack myStack;

    public P_Thread(MyStack myStack){
        this.myStack = myStack;
    }
    @Override
    public void run() {
        while (true){
            myStack.set();
        }
    }
}

/**
 * 多线程之消费者调用
 */
class C_Thread implements Runnable{

    private MyStack myStack;

    public C_Thread(MyStack myStack){
        this.myStack = myStack;
    }
    @Override
    public void run() {
        while (true){
            myStack.get();
        }
    }
}

8. 为什么wait()要在同步代码块中使用,而sleep()不需要

调用wait()就是释放锁,释放锁的前提是必须要先获得锁,先获得锁才能释放锁.
同时为了防止产生死锁。
如下代码:
假设要开发一个线程安全的队列,代码如下所示


class BlockingQueue {
    Queue<String> buffer = new LinkedList<String>();
 
    public void give(String data) {
        buffer.add(data);
        notify();                   // Since someone may be waiting in take!
    }
 
    public String take() throws InterruptedException {
        while (buffer.isEmpty())    // don't use "if" due to spurious wakeups.
            wait();
        return buffer.remove();
    }
}

上面的队列可能存在以下潜在问题:

  1. 消费者A调用 take() 时 buffer.isEmpty()为true

  2. 在消费者A调用wait()方法之前, 生产者B调用了 give(), ( buffer.add(data); notify();)

  3. 之后消费者A调用了 wait() (错过了生产者B调用的notify()).

  4. give()如果之后没有别的生产者调用give()方法,消费者A所在线程则会一直等待。

解决方法如下: 总是保证 give/notify 和 isEmpty/wait 操作的原子性。这也是为什么wait方法要强制在同步代码块中调用.

9. 为什么线程通信的方法wait()、notify()、notifyAll()被定义在Object类里面?而sleep为什么定义在Thread类里面?

wait()、notify()、notifyAll():
首先其三者的作用便是释放锁/唤醒线程。
    java中每个对象都是Object类型。而每个线程的锁都是是对象锁而非线程锁,每个线程想要执行锁内的代码,都需要先获取此对象。因此若想要将那个线程释放锁进入阻塞状态,直接调用此对象的wait()方法即可。

    而处于阻塞状态的线程想要被唤醒便需要使用notify/notifyAll方法。而notify依据什么来唤醒某个处于阻塞状态的线程呢?两者间通过什么关联起来呢?答案便是对象的同步锁。
唤醒线程只有得到同步对象的锁,才能唤醒此对象中处于阻塞的线程。

    wait()方法如果定义在Thread类中的话,那么线程正在等待的是哪个锁就不明确了。此三个方法定义在Object中。

sleep()方法
       由于sleep为什么被定义在Thread中,我们只要从sleep方法的作用来看就知道了,sleep的作用是:让线程在预期的时间内执行,其他时候不要来占用CPU资源。从上面的话术中,便可以理解为sleep是属于线程级别的,它是为了让线程在限定的时间后去执行。而且sleep方法是不会去释放锁的

10. 关于Thread对象调用 wait()方法会怎样

首先需要知道,每个线程对象在执行完毕后,都会调用notifyAll()方法,而wait()方法位于Object类中,属于对象级别。所以如果使用Thread.wait(),在隐式的调用notifyAll后可能会使线程的wait方法失去作用。

11. notifyAll()后所有线程都会再次的抢占锁,如果某线程抢占失败会怎样?

线程处于就绪状态。等待在此抢占cpu资源
补充一点:
线程执行完wait(),join()方法后,处于WAITING状态,执行wait(Long time)/sleep(Long time)方法后处于TIMED_WAITING状态。
当线程处于等待锁释放时,处于BLOCKED状态

12. 用suspend和resume来阻塞线程可以么?

理论上可以,但是不建议使用。
原因:存在很多的弊端
(1)独占:因为suspend在调用过程中不会释放所占用的锁,所以如果使用不当会造成对公共对象的独占,使得其他线程无法访问公共对象,严重的话造成死锁。
如下代码:

public class Test {

    public static void main(String[] args) throws Exception {

        Wait wait = new Wait();
        Thread thread = new Thread(wait);
        thread.start();
        thread.sleep(1);
        thread.suspend();
        System.out.println("main");
    }
}

class Wait implements Runnable{
    int i=0;
    @Override
    public void run() {
        while(true){
            i++;
            System.out.println(i);
        }
    }
}

在这里插入图片描述
线程在打印到1985的时候执行了suspend方法,但是 mian 方法的输出语句无法执行。原因是printlf()方法是线程安全的。
suspend方法执行后,线程处于阻塞状态,但是并不会释放所占用的锁。

(2)当 suspend() 和 resume() 方法使用不当时也容易造成因线程的暂停导致数据不同步的情况。

13. join()期间,线程处于那种状态?

答:WAITING状态,因为join内部执行的是wait方法。线程在执行完成后默认执行notifyAll方法,隐式得在唤醒执行join方法的当前线程。
在这里插入图片描述

14. 什么是守护线程

java有两种状态的线程

  1. 守护线程(例如垃圾回收线程:gc线程)
  2. 非守护线程(用户线程:用户线程即我们手动创建的线程)

守护线程与非守护线程的区别

  1. 守护线程
    有一个特征,例如当主线程运行的时候,垃圾回收线程一起运行。
    当主线程销毁,会和主线程一起销毁。
  2. 非守护线程
    如果主线程销毁,用户线程继续运行且互不影响。

守护线程与非守护线程代码演示区别
创建守护线程

public static void main(String[] args) throws InterruptedException {
		Thread t1 = new Thread(new Runnable() {

			public void run() {
				while (true) {
					try {
						Thread.sleep(1000);

					} catch (Exception e) {
						// TODO: handle exception
					}
					System.out.println("我是子线程(用户线程)");
				}
			}
		});
		// 标识当前方法为守护线程,一定要在启动线程前设置为守护线程
		t1.setDaemon(true);
		t1.start();

守护线程:主线程销毁停止,守护线程一起销毁
非守护线程:如果主线程销毁,用户线程继续运行且互不影响。

15.四种需要注意线程安全的情况

  1. 访问共享的变量或资源,会有并发的危险。比如对象的属性,静态变量,共享缓存,数据库等。
  2. 所有依赖时序的操作,即使每一步都是线程安全,还是存在并发问题。如 read-modify-write,check-then-act
  3. 不同数据之间存在捆绑关系的时候。如ip和端口号,若要修改,就需要一块修改。
  4. 我们使用其他类的时候,要注意是否是线程安全的。
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值