上一篇:Java面试备战篇——JVM篇(二)

1、Java中实现多线程有几种方法

继承Thread类;
实现Runnable接口;
实现Callable接口通过FutureTask包装器来创建Thread线程;
使用ExecutorService、Callable、Future实现有返回结果的多线程(也就是使用了ExecutorService来
管理前面的三种方式)。

2、如何停止一个正在运行的线程

1、使用退出标志,使线程正常退出,也就是当run方法完成后线程终止。
2、使用stop方法强行终止,但是不推荐这个方法,因为stop和suspend及resume一样都是过期作废的
方法。
3、使用interrupt方法中断线程。

class MyThread extends Thread {
    volatile boolean stop = false;

    public void run() {
        while (!stop) {
            System.out.println(getName() + " is running");
            try {
                 sleep(1000);
            } catch (InterruptedException e) {
                System.out.println("week up from blcok...");
                stop = true; // 在异常处理代码中修改共享变量的状态
            }
        }
        System.out.println(getName() + " is exiting...");
   }
}

class InterruptThreadDemo3 {
    public static void main(String[] args) throws InterruptedException {
        MyThread m1 = new MyThread();
        System.out.println("Starting thread...");
        m1.start();
        Thread.sleep(3000);
        System.out.println("Interrupt thread...: " + m1.getName());
        m1.stop = true; // 设置共享变量为true
        m1.interrupt(); // 阻塞时退出阻塞状态
        Thread.sleep(3000); // 主线程休眠3秒以便观察线程m1的中断情况
        System.out.println("Stopping application...");
   }
}

3、notify()和notifyAll()有什么区别?

notify可能会导致死锁,而notifyAll则不会

任何时候只有一个线程可以获得锁,也就是说只有一个线程可以运行synchronized 中的代码

使用notifyall,可以唤醒
所有处于wait状态的线程,使其重新进入锁的争夺队列中,而notify只能唤醒一个。

wait() 应配合while循环使用,不应使用if,务必在wait()调用前后都检查条件,如果不满足,必须调用
notify()唤醒另外的线程来处理,自己继续wait()直至条件满足再往下执行。

notify() 是对notifyAll()的一个优化,但它有很精确的应用场景,并且要求正确使用。不然可能导致死
锁。正确的场景应该是 WaitSet中等待的是相同的条件,唤醒任一个都能正确处理接下来的事项,如果
唤醒的线程无法正确处理,务必确保继续notify()下一个线程,并且自身需要重新回到WaitSet中.

4、sleep()和wait() 有什么区别?

对于sleep()方法,我们首先要知道该方法是属于Thread类中的。而wait()方法,则是属于Object类中
的。

sleep()方法导致了程序暂停执行指定的时间,让出cpu该其他线程,但是他的监控状态依然保持者,当
指定的时间到了又会自动恢复运行状态。在调用sleep()方法的过程中,线程不会释放对象锁。

当调用wait()方法的时候,线程会放弃对象锁,进入等待此对象的等待锁定池,只有针对此对象调用
notify()方法后本线程才进入对象锁定池准备,获取对象锁进入运行状态。

5、volatile 是什么?可以保证有序性吗?

一旦一个共享变量(类的成员变量、类的静态成员变量)被volatile修饰之后,那么就具备了两层语
义:
1)保证了不同线程对这个变量进行操作时的可见性,即一个线程修改了某个变量的值,这新值对其他
线程来说是立即可见的,volatile关键字会强制将修改的值立即写入主存。

2)禁止进行指令重排序。

volatile 不是原子性操作

什么叫保证部分有序性?

当程序执行到volatile变量的读操作或者写操作时,在其前面的操作的更改肯定全部已经进行,且结果
已经对后面的操作可见;在其后面的操作肯定还没有进行;

x = 2; //语句1 
y = 0; //语句2 
flag = true; //语句3 
x = 4; //语句4 
y = -1; //语句5

由于flag变量为volatile变量,那么在进行指令重排序的过程的时候,不会将语句3放到语句1、语句2前
面,也不会讲语句3放到语句4、语句5后面。但是要注意语句1和语句2的顺序、语句4和语句5的顺序是
不作任何保证的。

使用 Volatile 一般用于 状态标记量 和 单例模式的双检锁

6、Thread 类中的start() 和 run() 方法有什么区别?

start()方法被用来启动新创建的线程,而且start()内部调用了run()方法,这和直接调用run()方法的效果
不一样。当你调用run()方法的时候,只会是在原来的线程中调用,没有新的线程启动,start()方法才会
启动新线程。

7、为什么wait, notify 和 notifyAll这些方法不在thread类里面?

明显的原因是JAVA提供的锁是对象级的而不是线程级的,每个对象都有锁,通过线程获得。如果线程需
要等待某些锁那么调用对象中的wait()方法就有意义了。如果wait()方法定义在Thread类中,线程正在
等待的是哪个锁就不明显了。简单的说,由于wait,notify和notifyAll都是锁级别的操作,所以把他们
定义在Object类中因为锁属于对象。

8、为什么wait和notify方法要在同步块中调用?

  1. 只有在调用线程拥有某个对象的独占锁时,才能够调用该对象的wait(),notify()和notifyAll()方法。
  2. 如果你不这么做,你的代码会抛出IllegalMonitorStateException异常。
  3. 还有一个原因是为了避免wait和notify之间产生竞态条件。

wait()方法强制当前线程释放对象锁。这意味着在调用某对象的wait()方法之前,当前线程必须已经获得
该对象的锁。因此,线程必须在某个对象的同步方法或同步代码块中才能调用该对象的wait()方法。

在调用对象的notify()和notifyAll()方法之前,调用线程必须已经得到该对象的锁。因此,必须在某个对
象的同步方法或同步代码块中才能调用该对象的notify()或notifyAll()方法。

调用wait()方法的原因通常是,调用线程希望某个特殊的状态(或变量)被设置之后再继续执行。调用
notify()或notifyAll()方法的原因通常是,调用线程希望告诉其他等待中的线程:"特殊状态已经被设置"。
这个状态作为线程间通信的通道,它必须是一个可变的共享状态(或变量)。

9、Java中interrupted 和 isInterruptedd方法的区别?

interrupted() 和 isInterrupted()的主要区别是前者会将中断状态清除而后者不会。Java多线程的中断机
制是用内部标识来实现的,调用Thread.interrupt()来中断一个线程就会设置中断标识为true。当中断线
程调用静态方法Thread.interrupted()来检查中断状态时,中断状态会被清零。而非静态方法
isInterrupted()用来查询其它线程的中断状态且不会改变中断状态标识。简单的说就是任何抛出
InterruptedException异常的方法都会将中断状态清零。无论如何,一个线程的中断状态有有可能被其
它线程调用中断来改变。

10、Java中synchronized 和 ReentrantLock 有什么不同?

相似点:

这两种同步方式有很多相似之处,它们都是加锁方式同步,而且都是阻塞式的同步,也就是说当如果一
个线程获得了对象锁,进入了同步块,其他访问该同步块的线程都必须阻塞在同步块外面等待,而进行
线程阻塞和唤醒的代价是比较高的.

区别:

这两种方式最大区别就是对于Synchronized来说,它是java语言的关键字,是原生语法层面的互斥,需
要jvm实现。而ReentrantLock它是JDK 1.5之后提供的API层面的互斥锁,需要lock()和unlock()方法配
合try/finally语句块来完成。

Synchronized进过编译,会在同步块的前后分别形成monitorenter和monitorexit这个两个字节码指
令。在执行monitorenter指令时,首先要尝试获取对象锁。如果这个对象没被锁定,或者当前线程已经
拥有了那个对象锁,把锁的计算器加1,相应的,在执行monitorexit指令时会将锁计算器就减1,当计
算器为0时,锁就被释放了。如果获取对象锁失败,那当前线程就要阻塞,直到对象锁被另一个线程释
放为止。

由于ReentrantLock是java.util.concurrent包下提供的一套互斥锁,相比Synchronized,
ReentrantLock类提供了一些高级功能,主要有以下3项:

1.等待可中断,持有锁的线程长期不释放的时候,正在等待的线程可以选择放弃等待,这相当于
Synchronized来说可以避免出现死锁的情况。

2.公平锁,多个线程等待同一个锁时,必须按照申请锁的时间顺序获得锁,Synchronized锁非公平锁,
ReentrantLock默认的构造函数是创建的非公平锁,可以通过参数true设为公平锁,但公平锁表现的性
能不是很好。

3.锁绑定多个条件,一个ReentrantLock对象可以同时绑定对个对象。

11、有三个线程T1,T2,T3,如何保证顺序执行?

在多线程中有多种方法让线程按特定顺序执行,你可以用线程类的join()方法在一个线程中启动另一个
线程,另外一个线程完成该线程继续执行。为了确保三个线程的顺序你应该先启动最后一个(T3调用
T2,T2调用T1),这样T1就会先完成而T3最后完成。

实际上先启动三个线程中哪一个都行,
因为在每个线程的run方法中用join方法限定了三个线程的执行顺序。

public class JoinTest2 {

    // 1.现在有T1、T2、T3三个线程,你怎样保证T2在T1执行完后执行,T3在T2执行完后执行

    public static void main(String[] args) {

        final Thread t1 = new Thread(new Runnable() {

            @Override
            public void run() {
                System.out.println("t1");
            }
        });
        final Thread t2 = new Thread(new Runnable() {

            @Override
            public void run() {
                try {
                     // 引用t1线程,等待t1线程执行完
                     t1.join();
                } catch (InterruptedException e) {
                     e.printStackTrace();
                }
                System.out.println("t2");
            }
        });
        Thread t3 = new Thread(new Runnable() {

            @Override
            public void run() {
                try {
                     // 引用t2线程,等待t2线程执行完
                     t2.join();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("t3");
            }
        });
        t3.start();//这里三个线程的启动顺序可以任意,大家可以试下!
        t2.start();
        t1.start();
    }
}

下一篇:Java面试备战篇——多线程&并发篇(二)

欢迎转发点赞支持一下LZ,持续更新中