关于多线程中的面试题

关于多线程中的面试题

常见面试的

1.现在有T1、T2、T3三个线程,你怎样保证T2在T1执行完后执行,T3在T2执行完后执行?
答:使用join就OK了。

public class ThreeThread {

    public static void main(String[] args) throws InterruptedException {
        OneThread one =new OneThread();
        one.setName("one");
        one.start();

        one.join();  //进入等待,只有one线程执行完毕,才会释放等待,才能继续往下执行。

        OneThread two =new OneThread();
        two.setName("two");
        two.start();
        two.join();   //join()这一句,就好比:thread.sleep();一样,不过比sleep好很多。



        OneThread three =new OneThread();
        three.setName("three");
        three.start();
    }
}

class OneThread extends Thread {

    @Override
    public void run() {
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName());
    }
}

解答:join可以暂时理解为wait,或者sleep。

join源码分析:


public final void join() throws InterruptedException {
        join(0);
    } 

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) {
             //isAlive()是一个本地方法,判断该线程是否存活,如果该线程还存活着,则一直等待0秒。
            while (isAlive()) {  
                wait(0);
            }
        } else {
            while (isAlive()) {
                long delay = millis - now;
                if (delay <= 0) {
                    break;
                }
                wait(delay);
                now = System.currentTimeMillis() - base;
            }
        }
    }

可以看到join其实就是利用了wait()方法来实现的。当调用one.join()时,main()主线程被wait了,就不能继续往下执行,也就不能执行two.start()。
只有等到one线程死亡,也就是执行完毕后,wait才会被释放,继续往下执行。

2.什么是线程,什么是进程?

进程用于把资源集中到一起,而线程则是在CPU上被调度执行的实体。

进程:指在系统中能独立运行并作为资源分配的基本单位,它是由一组机器指令、数据和堆栈等组成的,是一个能独立运行的活动实体。

线程:是进程的一个实体,是CPU调度和分派的基本单位,他是比进程更小的能独立运行的基本单位,线程自己基本上不拥有系统资源。

多个进程的内部数据和状态都是完全独立的,而多线程是共享一块内存空间和一组系统资源,有可能互相影响. 线程本身的数据通常只有寄存器数据,以及一个程序执行时使用的堆栈,所以线程的切换比进程切换的负担要小。

3.线程上下文切换是什么?

即使是单核CPU也支持多线程执行代码,CPU通过给每个线程分配CPU时间片来实现这个机制。时间片是CPU分配给各个线程的时间,因为时间片非常短,所以CPU通过不停地切换线程执行,让我们感觉多个线程时同时执行的,时间片一般是几十毫秒(ms)。

CPU通过时间片分配算法来循环执行任务,当前任务执行一个时间片后会切换到下一个任务。但是,在切换前会保存上一个任务的状态,以便下次切换回这个任务时,可以再次加载这个任务的状态,从任务保存到再加载的过程就是一次上下文切换。

这就像我们同时读两本书,当我们在读一本英文的技术书籍时,发现某个单词不认识,于是便打开中英文词典,但是在放下英文书籍之前,大脑必须先记住这本书读到了多少页的第多少行,等查完单词之后,能够继续读这本书。这样的切换是会影响读书效率的,同样上下文切换也会影响多线程的执行速度。

更多信息查看我的文章:《java多线程-进程与线程(一)》

4.在java中wait和sleep方法的不同?
最大的不同是在等待时wait会释放锁,而sleep一直持有锁。Wait通常被用于线程间交互,sleep通常被用于暂停执行。
wait线程会被挂起,让出当前cpu调度以及资源,sleep则会根据sleep时间一直占用者cpu以及资源。

更多信息查看我的文章:《java多线程-线程间通信(七)》

5.为什么我们调用start()方法时会执行run()方法,为什么我们不能直接调用run()方法?

线程中断机制

手动中断

public class Handinterrupt{
   public volatile boolean b=true;


   public void run(){
       while(b){  //通过外面b变量来中断
           .... 
       }
   }
}

interrupt中断

class MyThreadStop extends Thread {

    private int temp;

    public MyThreadStop(int temp) {
        this.temp = temp;
    }

    @Override
    public void run() {
        for (int i = 0; i < 5; i++) { 
            System.out.println("i=" + i);
            if (Thread.interrupted()) { //接受停止的状态
                System.out.println("线程已经停止了哦");
                break;
            }
        }
        System.out.println(Thread.currentThread().getName() + temp);
    }

}


public class ThreadStopDome {


    public static void main(String orgs[]) {
        MyThreadStop myThread = new MyThreadStop(1);
        myThread.start();
        myThread.interrupt();//发起停止线程
        System.out.println("运行完毕");

    }

}

interrupt并不能真正的停止线程,只是将线程的状态标记为停止;需要配合Thread.interrupted()使用,这个读取线程的状态后,发现线程被停止了, 然后做一些相关操作。

lockInterruptibly中断


public class ReentrantDome4 {

    private Lock lock = new ReentrantLock();

    public void serviceA() {
        try { 
            //可中断,响应中断。这个和lock是一样的,只不过在lock()的基础上加了一个判断,用来响应线程的中断。
            lock.lockInterruptibly();
            Thread.sleep(500);
            System.out.println("lock了。。。。。" + Thread.currentThread().getId());

        } catch (InterruptedException e) {
            e.printStackTrace();
            System.out.println("中断了.....");
        } finally {
            lock.unlock();
        }
    }


    public static void main(String[] args) {

        final ReentrantDome4 dome4 = new ReentrantDome4();

        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }


        Thread t2 = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("threadB=" + Thread.currentThread().getId());
                dome4.serviceA();

            }
        });
        t2.start();
        t2.interrupt(); //如果把这个去了,相对来说lock.lockInterruptibly()就和lock.lock()功能一样了。


    }


}

lock.lockInterruptibly();加锁时判断线程是否被停止了。如果线程停止了,直接抛异常,不往下执行了。

t2.start(); //开始线程
t2.interrupt(); //停止线程

如果start已经开始了在到达lock.lockInterrup 时执行了interrupt线程才可能停止,如果已经执行到lock.locIn之后的语句了,也就无法停止了。lock.lockInterruptibly()只是在获取锁时,判断是否该线程已经停止了, 如果停止了则直接抛异常,不往下执行了。如果已经获取到锁,并且执行了,这个时候才停止,则停止是无效的。

源码:

public final void acquireInterruptibly(int arg)
            throws InterruptedException {
        if (Thread.interrupted())  //判断是否停止了
            throw new InterruptedException();
        if (!tryAcquire(arg))
            doAcquireInterruptibly(arg);
    }

public void interrupt() {
        if (this != Thread.currentThread())
            checkAccess();

        synchronized (blockerLock) {
            Interruptible b = blocker;
            if (b != null) {
                interrupt0();           // Just to set the interrupt flag
                b.interrupt(this);
                return;
            }
        }
        interrupt0();
    }

多线程中的线程中断

ExecutorService pool = Executors.newFixedThreadPool(10);
pool.shutdown();  //停止线程池。线程池停止了,就是说线程池里面的线程也应该停止了。

源码:

public void shutdown() {
        final ReentrantLock mainLock = this.mainLock;
        mainLock.lock();
        try {
            checkShutdownAccess();
            advanceRunState(SHUTDOWN);
            interruptIdleWorkers(); //停止线程
            onShutdown(); // hook for ScheduledThreadPoolExecutor
        } finally {
            mainLock.unlock();
        }
        tryTerminate();
}

private void interruptIdleWorkers(boolean onlyOne) {
        final ReentrantLock mainLock = this.mainLock;
        mainLock.lock();
        try {
            for (Worker w : workers) {
                Thread t = w.thread;
                //尝试获取锁,并且线程被停止了,开始执行t.interrupt
                if (!t.isInterrupted() && w.tryLock()) {
                    try {
                        t.interrupt();
                    } catch (SecurityException ignore) {
                    } finally {
                        w.unlock();
                    }
                }
                if (onlyOne)
                    break;
            }
        } finally {
            mainLock.unlock();
        }
    }

总结:

Java中,明确规定任何线程都不可能停止另外一个正在运行的线程。

上面我们可以看到,他并不是真正的停止了,只是根据接受到的状态,做响应的操作而已,而不是真正意义上的线程停止了。可以看到lock的可停止线程,内部原理也是使用的interrupt来操作的。

死锁问题

写一个简单死锁程序,或者怎么解决死锁,已经死锁的原因?
死锁一般发送在至少两把锁以上被多个线程使用而导致的,或者使用lock忘记锁解锁。

看一个synchronized死锁的问题:


class DeadThread{
    private final Object left = new Object();
    private final Object right = new Object();

    public void leftRight() throws Exception
    {
        synchronized (left)
        {
            System.out.println("left...start");
            Thread.sleep(2000);
            synchronized (right)
            {
                System.out.println("leftRight end!");
            }
            System.out.println("left end");
        }
    }

    public void rightLeft() throws Exception
    {
        synchronized (right)
        {
            System.out.println("right...start");
            Thread.sleep(2000);
            synchronized (left)
            {
                System.out.println("rightLeft end!");
            }
            System.out.println("right....end");
        }
    }
}

public class DeadLock2 {

    public static void main(String[] args) {
        final DeadThread thread=new DeadThread();

        Thread thread1= new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    thread.leftRight();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        });

        Thread thread2 = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    thread.rightLeft();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        });

        thread1.start();
        thread2.start();

    }
}

上面发生死锁了,为什么?因为线程1,2他们几乎一起执行,线程1执行了leftRight方法,刚开始获取到了left对象锁;线程2执行了rightLeft方法,刚进入就获取到了right对象锁。当等待2s过后,线程1需要right这把锁,但是right这把锁在线程2手里,线程2s后需要left这把锁,结果left锁在线程1里面,这样就产生了死锁。

来看下使用Lock的tryLock锁避免死锁。


class LockThread {
    private Lock lock = new ReentrantLock();
    private Lock lock2 = new ReentrantLock();

    void leftRight() throws Exception {
        try {
            lock.lock();
            System.out.println("left...start");
            Thread.sleep(2000);
            boolean a = lock2.tryLock();
            System.out.println("left...a=" + a);
            System.out.println("left end!");
        } finally {
            lock.unlock();
            lock2.unlock();
        }
    }

    void rightLeft() throws Exception {
        try {
            lock2.lock();
            System.out.println("right...start");
            Thread.sleep(2000);
            boolean a = lock.tryLock();
            System.out.println("right...a=" + a);
            System.out.println("right end!");
        } finally {
            lock2.unlock();
            lock.unlock();
        }

    }
}

public class DeadLock3 {

    public static void main(String[] args) {
        final LockThread thread = new LockThread();

        Thread thread1 = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    thread.leftRight();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        });

        Thread thread2 = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    thread.rightLeft();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        });

        thread1.start();
        thread2.start();

    }
}

结果为:
left...start
right...start
right...a=false
right end!
left...a=false
left end!
java.lang.IllegalMonitorStateException
    at java.util.concurrent.locks.ReentrantLock$Sync.tryRelease(ReentrantLock.java:155)
    at java.util.concurrent.locks.AbstractQueuedSynchronizer.release(AbstractQueuedSynchronizer.java:1260)
    at java.util.concurrent.locks.ReentrantLock.unlock(ReentrantLock.java:460)
    at cn.thread.first.dielock.LockThread.rightLeft(DeadLock3.java:35)
    at cn.thread.first.dielock.DeadLock3$2.run(DeadLock3.java:61)
    at java.lang.Thread.run(Thread.java:745)
java.lang.IllegalMonitorStateException
    at java.util.concurrent.locks.ReentrantLock$Sync.tryRelease(ReentrantLock.java:155)
    at java.util.concurrent.locks.AbstractQueuedSynchronizer.release(AbstractQueuedSynchronizer.java:1260)
    at java.util.concurrent.locks.ReentrantLock.unlock(ReentrantLock.java:460)
    at cn.thread.first.dielock.LockThread.leftRight(DeadLock3.java:21)
    at cn.thread.first.dielock.DeadLock3$1.run(DeadLock3.java:50)
    at java.lang.Thread.run(Thread.java:745)

可以看到tryLock在尝试过程中没有获取到锁,就不加锁,所以在finally里面,解锁就会抛异常了。

tryLock只是尝试看看自己能否获取锁,能获取就获取锁并返回true,不能获取就直接返回false不做任何操作,没有获取锁的时候并不会把线程加入到等待队列里面(相当于没有锁,不加锁),tryLock只是在能获取锁的时候获取锁。

在多把锁为防止死锁可以尝试使用tryLock来操作,或者使用boolean a = lock2.tryLock(1, TimeUnit.SECONDS);在1s钟内一直尝试获取锁,如果1s后还不能获取则返回false。这样就可以很好的解决死锁带来的问题。

用Java写代码来解决生产者——消费者问题


import java.util.LinkedList;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

class ProducerAndCosumer<T> {

    private static final Integer MAX_ITEMS = 10;
    private LinkedList<T> items = new LinkedList<>();

    private Lock lock = new ReentrantLock();
    private Condition full = lock.newCondition();  //元素满的时候,生产者进入等候
    private Condition empty = lock.newCondition();  //元素为空的时候,消费者进入等候

    public T put(T o) throws InterruptedException {
        lock.lock();
        try {
            while (items.size() == MAX_ITEMS) {
                full.await();
            }
            insert(o);
        } finally {
            lock.unlock();
        }
        return o;
    }

    private void insert(T o) {
        items.add(o);
        empty.signal();  //解锁消费者等候
    }

    public T get() throws InterruptedException {
        lock.lock();
        try {
            if (items.isEmpty()) {
                empty.await();
            }
            full.signal();  //解锁生产者等候
            return items.removeFirst();
        } finally {
            lock.unlock();
        }
    }


}

public class ProducerAndCosumerDemo {

    public static void main(String[] args) {
        final ProducerAndCosumer<Integer> cosumer = new ProducerAndCosumer<>();

        Runnable producerRunnable = new Runnable() {
            int i = 0;

            public void run() {
                while (true) {
                    i++;
                    try {
                        System.out.println("我生产了一个===" + i);
                        cosumer.put(i);
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        };

        Runnable cosumerRunnable = new Runnable() {

            public void run() {
                while (true) {
                    try {
                        System.out.println("消费了一个===" + cosumer.get());
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        };

        Thread thread = new Thread(producerRunnable);
        thread.start();

        Thread thread1 = new Thread(cosumerRunnable);
        thread1.start();


    }

}

队列满了,那么生产者就开始等候;队列空了,消费着就开始等候;队列有值了,就解锁消费者等候;消费掉一条数据时就解锁生产者等候。

可以参考ArrayBlockingQueue源码实现。

Java中的volatile关键是什么作用?怎样使用它?在Java中它跟synchronized方法有什么不同?

volatile赋予了变量可见——禁止编译器对成员变量进行优化,它修饰的成员变量在每次被线程访问时,都强迫从内存中重读该成员变量的值;而且,当成员变量发生变化时,强迫线程将变化值回写到共享内存,这样在任何时刻两个不同线程总是看到某一成员变量的同一个值,这就是保证了可见性

可参看《java多线程-volatile》与《java多线程-synchrionized》

猜你喜欢

转载自blog.csdn.net/piaoslowly/article/details/81562507