多线程面试题整合

(1)Java编程写一个会导致死锁的程序

package 设计模式.Single_Thread_Execution.多线程面试题;

import java.util.concurrent.TimeUnit;

/**
 * @author Heian
 * @time 19/01/23 10:42
 * @copyright(C) 2019 深圳市北辰德科技股份有限公司
 * 用途:手写一个死锁程序
 */
public class DeadLock {
    /**
     * 造成死锁的原因:线程一持有A对象的锁等待获取B对象锁(没拿到不允许结束生命周期),线程二持有对象B的锁等待获取A的锁,而A,B只有一个,就会出现死锁
     * 典故:科学家吃面  A科学家想吃面但是只拿到了叉子,B科学家也想吃面,但是只拿到了刀子,于是两人相互等待着
     */
    private static final Object A = new Object ();
    private static final Object B = new Object ();
    
    //持有A的锁,想去拿B的锁,完成吃面
    public void getB() {
        synchronized (A){
            System.out.println (Thread.currentThread ().getName () + "拿到了A对象的锁");
            try {
                TimeUnit.SECONDS.sleep (1);
            } catch (InterruptedException e) {
                e.printStackTrace ();
            }
            synchronized (B){
                System.out.println (Thread.currentThread ().getName () + "拿到了B对象的锁");
            }
        }
    }
    //持有B的锁,想去拿A的锁,完成吃面
    public void getA(){
        synchronized (B){
            System.out.println (Thread.currentThread ().getName () + "拿到了B对象的锁");
            synchronized (A){
                System.out.println (Thread.currentThread ().getName () + "拿到了A对象的锁");
            }
        }
    }

    public static void main(String[] args) {
        DeadLock deadLock = new DeadLock ();
        new Thread (() -> {
            while(true){
                deadLock.getB ();
            }
        },"线程一").start ();

        new Thread (() -> {
            while(true){
                deadLock.getA ();
            }
        },"线程二").start ();

    }

}

(2)什么是自旋

    很多synchronized里面的代码只是一些很简单的代码,执行时间非常快,此时等待的线程都加锁可能是一种不太值得的操作,因为线程阻塞涉及到用户态和内核态切换的问题。既然synchronized里面的代码执行得非常快,不妨让等待锁的线程不要被阻塞,而是在synchronized的边界做忙循环,这就是自旋。如果做了多次忙循环发现还没有获得锁,再阻塞,这样可能是一种更好的策略。

(3)什么是CAS

    CAS,全称为Compare and Swap,即比较-替换;CAS机制中使用了3个基本操作数:内存地址V,旧的预期值A,要修改的新值B,当且仅当预期值A和内存值V相同时,才会将内存值修改为B并返回true,否则什么都不做并返回false。CAS是通过无限循环来获取数据的,若在第一轮循环中,a线程获取地址里面的值被b线程修改了,那么a线程需要自旋,到下次循环才有可能机会执行。

    synchronized属于悲观锁,悲观的认为程序中的并发情况严重,所以严防死守,CAS属于乐观锁,乐观地认为程序中的并发情况不那么严重,所以让线程不断去重试更新

(4)什么是乐观锁和悲观锁

    乐观锁:就像它的名字一样,对于并发间操作产生的线程安全问题持乐观状态,乐观锁认为竞争不总是会发生,因此它不需要持有锁,将比较-替换这两个动作作为一个原子操作尝试去修改内存中的变量,如果失败则表示发生冲突,那么就应该有相应的重试逻辑,例如CAS。

    悲观锁:还是像它的名字一样,对于并发间操作产生的线程安全问题持悲观状态,悲观锁认为竞争总是会发生,因此每次对某资源进行操作时,都会持有一个独占的锁,就像synchronized,不管三七二十一,直接上了锁就操作资源了,例如Syncronized。

 (5)  什么是AQS

    简单说一下AQS,AQS全称为AbstractQueuedSychronizer,翻译过来应该是抽象队列同步器。如果说java.util.concurrent的基础是CAS的话,那么AQS就是整个Java并发包的核心了,ReentrantLock、CountDownLatch、Semaphore等等都用到了它。AQS实际上以双向队列的形式连接所有的Entry,比方说ReentrantLock,所有等待的线程都被放在一个Entry中并连成双向队列,前面一个线程使用ReentrantLock好了,则双向队列实际上的第一个Entry开始运行。AQS定义了对双向队列所有的操作,而只开放了tryLock和tryRelease方法给开发者使用,开发者可以根据自己的实现重写tryLock和tryRelease方法,以实现自己的并发功能。

(6)  怎么让线程顺序执行

package AtomicIntegerTest;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.stream.IntStream;

/**
 * @author Heian
 * @time 19/03/08 23:33
 * @copyright(C) 2019 深圳市北辰德科技股份有限公司
 * 用途:如何使得线程顺序进行
 */
public class JoinAndThreadPool {

    static ExecutorService executorService = Executors.newSingleThreadExecutor ();//单例线程池

    public static void main(String[] args)  {
        //方法一:使用Thread类中的join方法,使得主线程必须等待子线程执行完成执行完
        IntStream.range (0,10).forEach (i ->{
            Thread th = new Thread (() -> System.out.println (Thread.currentThread ().getName () + "执行了"),"线程" + i);
            try {
                th.start ();
                th.join ();// 加了此方法线程方能顺序执行
           } catch (InterruptedException e) {
                e.printStackTrace ();
            }
        } );
        // 线程0执行了 线程1执行了 线程2执行了 线程3执行了 线程4执行了 线程5执行了 线程6执行了 线程7执行了 线程8执行了 线程9执行了
//可以看出,Join方法实现是通过wait(小提示:Object 提供的方法)。 当main线程调用t.join时候,main线程会获得线程对象t的
//锁(wait 意味着拿到该对象的锁),调用该对象的wait(等待时间),直到该对象唤醒main线程 ,比如退出后。这就意味着main 线程调用t.join时,必须能够拿到线程t对象的锁。
//join方法  只会暂停当前main线程,不会影响其它线程的独立运行,我这里是每创建一个线程就暂停main创建第二个,所以会依次进行


        //方法二:使用单例线程池,用唯一的工作线程执行任务,保证所有任务按照指定顺序执行
        Thread thread1 = new Thread (() -> {
            try {
                TimeUnit.SECONDS.sleep (1);
                System.out.println (Thread.currentThread ().getName () + "执行了1");
            } catch (InterruptedException e) {
                e.printStackTrace ();
            }
        },"线程一");
        Thread thread2 = new Thread (() -> {
            try {
                TimeUnit.SECONDS.sleep (1);
                System.out.println (Thread.currentThread ().getName () + "执行了2");
            } catch (InterruptedException e) {
                e.printStackTrace ();
            }
        },"线程二");
        Thread thread3 = new Thread (() -> {
            try {
                TimeUnit.SECONDS.sleep (1);
                System.out.println (Thread.currentThread ().getName () + "执行了3");
            } catch (InterruptedException e) {
                e.printStackTrace ();
            }
        },"线程三");
        executorService.submit (thread1);
        executorService.submit (thread2);
        executorService.submit (thread3);
        executorService.shutdown ();
        //pool-1-thread-1执行了1
        //pool-1-thread-1执行了2
        //pool-1-thread-1执行了3
    }


}

(7)  线程的有哪几种状态?

    new:创建线程对象

    runnable:就绪状态(等待cpu调度)

    runnring:运行状态

    blocked:阻塞状态(如wait、sleep、为了获得锁加入锁队列、io阻塞等)

    terminated:终止状态

(8)  Thread类中的sleep()和对象的wait()区别?

      sleep是线程类的静态方法,调用此方法会让当前线程暂停指定时间,将cpu时间片切换给其它线程,但是对象依然保持,休眠时间结束后会自动回到就绪状态。

     wait是Object类的方法,调用wait方法会放弃对象的锁,线程暂停执行,进入对象等待池,只有调用notify或者notifyAll方法才能唤醒等待池中的线程进入等锁池,如果线程重新获得对象的就可以进入就绪状态。

(9)线程A进入一个对象的同步方法a,线程B 是否可以进入此对象的同步方法b?

    不能,其它线程只能访问该对象的非同步方法,线程A进入该同步方法a说明锁已经被取走,那么其它线程试图进入同步方法b只能在等锁池中等待对象的锁。

(10) 线程的sleep方法和yield方法有什么区别?

    sleep方法给其它线程运行时不考虑线程的优先级,因此会给低优先级的线程运行机会,yield方法只会给相同的优先级或者优先级更高的线程给与运行机会;线程执行sleep方法进入阻塞状态,而执行yield方法后转入就绪状态,sleep抛出interruptedException异常,而yield方法没有任何异常声明。

    

 

猜你喜欢

转载自blog.csdn.net/qq_40826106/article/details/86607122