打卡面试题-day08(javaSE)

多线程面试题

多线程的创建方式

(1)继承Thread类:但Thread实质上是实现了Runnable接口的一个实例,它代表一个线程的实例,并且,启动线程的唯一方法就是通过Thread类的start()实例方法。start()方法是一个native方法,它将启动一个新线程,并执行run()方法。

public class ThreadTest01 extends Thread {
    @Override
    public void run() {
        System.out.println("threadTest.run()");
    }

    public static void main(String[] args) {
        ThreadTest01 threadTest01 = new ThreadTest01();
        threadTest01.start();
    }
}

(2)实现Runnable接口的方式实现多线程,并且实例化Thread,传入自己的Thread实例,调用run()方法。

public class ThreadTest02 implements Runnable {
    @Override
    public void run() {
        System.out.println("ThreadTest02.run()");
    }

    public static void main(String[] args) {
        ThreadTest02 threadTest02 = new ThreadTest02();
        Thread thread = new Thread(threadTest02);
        thread.start();
    }
}

在java中wait和sleep方法的不同的

最大的不同就是在等待时wait会释放锁,而sleep一直有锁。wait通常用于线程间交互,sleep通常被用于暂停执行。

synchronized和volatile关键字的作用

一旦一个共享变量(类的成员变量,类的静态成员变量)被volatile修饰之后,就具备了两层语义:

  1. 保证了不同线程对这个变量进行操作时的可见性,即一个线程修改了某个变量的值,这新值对其他线程来说就是立即可见的。
  2. 禁止进行指令重排序
    volatile本质是在告诉jvm当前变量在寄存器中的值是不确定的,需要从主存中读取;synchronized则是锁定当前变量,只有当前线程可以访问改变量,其他线程被阻塞住。
    (1)volatile仅能使用在变量级别;synchronized则可以使用在变量,方法和类级别的。
    (2)volatile仅能实现变量的修改可见性,并不能保证原子性;synchronized则可以保证变量的修改可见性和原子性。
    (3)volatile不会造成线程阻塞;synchronized可能会造成线程阻塞
    (4)volatile标记的变量不会被编译期优化;synchronized标记的变量可以被编译器优化

什么是线程池,如何使用

线程池就是事先将多个线程对象放到一个容器中,当使用的时候就不用new线程而是直接去线程池去取即可,节省了开辟线程的时间,提高了代码的执行效率。
在jdk的java.util.concurrent.Executors中提供了生成多种线程池的静态方法,调用它们的execute方法即可

.ExecutorService newCachedThreadPool = Executors.newCachedThreadPool();
 ExecutorService newFixedThreadPool = Executors.newFixedThreadPool(4);
 ScheduledExecutorService newScheduledThreadPool = Executors.newScheduledThreadPool(4);
 ExecutorService newSingleThreadExecutor = Executors.newSingleThreadExecutor();

常用的线程池有哪些

newSingleThreadExecutor:创建一个单线程的线程池,此线程池保证所有任务的执行顺序的提交顺序执行。
newFixedThreadPool:创建固定大小的线程池,每次提交一个任务就创建一个线程,直到线程达到线程池的最大大小。
newCacheThreadPool:创建一个可缓存的线程池,此线程池不会对线程池大小做限制,线程池的大小完全依赖操作系统能够创建的最大线程大小。
newScheeduledThreadPool:创建一个大小无限的线程池,此线程支持特定时以及周期性执行任务的需求。
newSingleThreadExecutor:创建一个单线程的线程池,此线程池支持特定时以及周期性执行任务的需求。

请叙述一下你对线程池的理解

(可以从线程池如何使用,线程池的好处,线程池的启动策略)

  1. 好处
    降低资源消耗,通过重复利用已创建的线程降低线程创建和销毁造成的消耗。
    提高响应速度,当任务到达时,任务可以不需要等到线程创建就能立即执行。
    提高线程的可管理性。线程是稀缺资源,如果无限制地创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控。
  2. 启动策略
    线程池创建时,里面没有一个线程。任务队列是作为参数传进来的。不过,就算队列里有任务,线程池也不会马上执行它们。
    当调用execute()方法时添加一个任务时,线程会作如下判断:
    a. 如果正在运行的线程数量小于corePoolSize,那么马上创建线程运行这个任务。
    b. 如果正在运行的线程数量大于或等于corePoolSize,那么将这个任务放入队列。
    c.如果队列满了,而且正在运行的线程数量小于maximumPoolSize,那么还是要创建线程运行这个任务。
    d. 如果队列满了,而且正在运行的线程的数量大于或等于maximumPoolSize,那么线程会抛出异常,告诉调用者“我不能再接受任务了”
  3. 当一个线程完成任务时,它会从队列中取出下一个任务来执行。
  4. 当一个线程无事可做,超过一定的时间(keepAliveTime)时,线程池会判断,如果当前运行的线程数大于corePoolSize,那么这个线程会被停掉。所以线程池的所有任务完成后,它最终会收缩到corePoolSize的大小。

三个线程a,b,c并发执行,b,c需要a线程的数据怎么实现

ThreadA,ThreadB,ThreadC三个线程,ThreadA用于初始化数据num,只有num初始化完成后再让ThreadB和ThreadC获取到初始化后的变量num。
代码实现:

public class ThreadCommunication {
    //定义一个变量作为数据
    private static int num;

    /**
     * 解决方案
     * 定义一个信号量,该类内部维持了多个线程锁,可以阻塞多个线程,释放多个线程
     * 线程的阻塞和释放是通过permit概念实现的
     * 线程通过semaphore.acquire()方法获取permit,如果当前semaphore有permit则分配给该线程
     * 如果没有则阻塞该线程直到semaphore
     * 调用release()释放permit
     * 构造函数中参数:permit(允许)个数
     */
    private static Semaphore semaphore = new Semaphore(0);

    public static void main(String[] args) {
        Thread threadA = new Thread(new Runnable() {
            @Override
            public void run() {
                //模拟耗时操作之后初始化便利num
                try {
                    Thread.sleep(1000);
                    num = 1;
                    //初始化完参数后释放两个permit
                    semaphore.release(2);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });

        Thread threadB = new Thread(new Runnable() {
            @Override
            public void run() {
                //获取permit,如果semaphore没有可用的permit则等待,如果有则消耗一个
                try {
                    semaphore.acquire();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName()+"获取到num的值为:"+num);
            }
        });

        Thread threadC = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    semaphore.acquire();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName()+"获取到num的值为:"+num);
            }
        });

        //同时开启三个线程
        threadA.start();
        threadB.start();
        threadC.start();
    }
}

同一个类中的2个方法都加了同步锁,多个线程能同时访问同一个类中的这两个方法吗

考虑Lock与synchronized两个实现锁的不同情形,会有不同的结果。
Lock可以让等待锁的线程响应中断,Lock获取锁,之后需要释放锁,所以多个线程不可访问同一个类中的2个加了Lock锁的方法。

public class LockTest {
    private int count = 0;
    //设置lock锁
    private Lock lock = new ReentrantLock();
    //方法一
    public Runnable run1 = new Runnable() {
        @Override
        public void run() {
            lock.lock();//枷锁
            while (count < 1000) {
                System.out.println(Thread.currentThread().getName() + "run1:" + count++);
            }
        }
    };
    public Runnable run2 = new Runnable() {
        @Override
        public void run() {
            lock.lock();
            while (count<1000){
                System.out.println(Thread.currentThread().getName() + "run2:" + count++);
            }
        }
    };

    public static void main(String[] args) {
        LockTest lock = new LockTest();
        new Thread(lock.run1).start();//获取该对象的方法1
        new Thread(lock.run2).start();//获取该对象的方法2
    }
}

而synchronized却不行,使用synchronized时,当我们访问同一个类对象的时候,是同一把锁,所以可以访问该对象的其他synchronized方法,代码如下:

public class SynchronizedTest {
    private int count = 0;
    //设置lock锁
    private Lock lock = new ReentrantLock();
    //方法一
    public Runnable run1 = new Runnable() {
        @Override
        public void run() {
           synchronized (this){//枷锁
            while (count < 1000) {
                System.out.println(Thread.currentThread().getName() + "run1:" + count++);
            }
        }
    }};
    public Runnable run2 = new Runnable() {
        @Override
        public void run() {
            synchronized (this){
            while (count<1000){
                System.out.println(Thread.currentThread().getName() + "run2:" + count++);
            }
        }
    }};

    public static void main(String[] args) {
        SynchronizedTest lock = new SynchronizedTest();
        new Thread(lock.run1).start();//获取该对象的方法1
        new Thread(lock.run2).start();//获取该对象的方法2
    }
}

什么情况下导致线程死锁,遇到线程死锁该怎么解决

死锁的定义:所谓死锁是指多个线程因竞争资源而造成的一种僵局(互相等待),若无外力作用,这些进城将无法向前推进。

死锁产生的必要条件

互斥条件:线程要求对所分配的资源进行排他性控制,即在一段时间内某资源仅为一个线程所占有。此时若有其它线程请求资源,则请求线程只能等待。
不剥夺条件:线程所获得资源在未使用完毕之前,不能被其他线程强行夺走,即只能由获得该资源的线程自己来释放(只能是主动释放)
请求和保持条件:线程已经保持了至少一个资源,但又提出了新的资源请求,而该资源已被其他线程占有,此时请求进程被阻塞,但对自己已获得的资源保持不放。
循环等待条件:存在一种线程资源的循环等待链,链中每一个线程已获得的资源同时被链中下一个线程所请求,即存在一个等到状态的线程集合(p1,p2,p3…pi)其中pi等待的资源被p(i+1)占有,pn等待的资源被p0占有。

如何避免死锁

  1. 枷锁顺序(线程按照一定的顺序加锁)
public class DeadLock {
    public int flag = 1;
    //静态资源是类的所有对象共享的
    private static Object o1 = new Object(),o2 = new Object();
    public void money(int flag){
        this.flag = flag;
        if (flag==1){
            synchronized (o1){
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (o2){
                    System.out.println("当前的线程是"+
                            Thread.currentThread().getName()+" "+"flag 的值"+"1");
                }
            }
        }
        if (flag==0){
            synchronized (o2){
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (o1){
                    System.out.println("当前的线程是"+
                            Thread.currentThread().getName()+" "+"flag 的值"+"0");
                }
            }
        }
    }

    public static void main(String[] args) {
        final DeadLock deadLock1 = new DeadLock();
        final DeadLock deadLock2 = new DeadLock();
        deadLock1.flag=1;
        deadLock2.flag=0;
        //deadLock1,deadLock2都处于执行状态,但JVM线程调度先执行哪个线程是不确定的
        //deadLock2的run()可能在deadLock1的run()之前运行
        final  Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                deadLock1.flag=1;
                deadLock1.money(1);
            }
        });
        t1.start();
        Thread t2 = new Thread(new Runnable() {
            @Override
            public void run() {
                //让deadLock2等待deadLock1执行完
                try {
                    t1.join();//让t1执行完后t2才会执行
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                deadLock2.flag=0;
                deadLock1.money(0);
            }
        });
        t2.start();
    }
}

  1. 加锁时限
    线程尝试获取锁的时候加上一定的时限,超时则放弃对该锁的请求,并释放自己占有的锁
public class DeadLockTest {
    public int flag = 1;
    private static Object o1 = new Object(),o2 = new Object();
    public void money(int flag){
        this.flag = flag;
        if (flag==1){
            synchronized (o1){
                try {
                    Thread.sleep(500);
                   synchronized (o2){
                       System.out.println("当前的线程是"+
                               Thread.currentThread().getName()+" "+"flag 的值"+"1");
                   }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
        if (flag==0){
            synchronized (o2){
                try {
                    Thread.sleep(500);
                    synchronized (o1){
                        System.out.println("当前的线程是"+
                                Thread.currentThread().getName()+" "+"flag 的值"+"0");
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    public static void main(String[] args) {
        final Lock lock = new ReentrantLock();
        final  DeadLockTest td1 = new DeadLockTest();
        final  DeadLockTest td2 = new DeadLockTest();
        td1.flag=1;
        td2.flag=0;
        final Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                String name = Thread.currentThread().getName();
                td1.flag = 1;
                try {
                    if (lock.tryLock(5000, TimeUnit.MILLISECONDS)){
                        System.out.println(name+"获取到锁");
                    }else {
                        System.out.println(name+"获取不到锁");
                        return;
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                try {
                    td1.money(1);
                } catch (Exception e) {
                    System.out.println(name+"出错了!!!");
                } finally {
                    System.out.println("当前线程是:"+Thread.currentThread().getName()+"释放锁!!");
                    lock.unlock();
                }
            }
        });
       t1.start();
       Thread t2 = new Thread(new Runnable() {
           @Override
           public void run() {
               String name = Thread.currentThread().getName();
               td1.flag = 1;
               try {
                   if (lock.tryLock(5000,TimeUnit.MILLISECONDS)){
                       System.out.println(name+"获取到锁!");
                   }else {
                       System.out.println(name+"获取不到锁!!");
                       return;
                   }
               } catch (InterruptedException e) {
                   e.printStackTrace();
               }
               try {
                   td2.money(0);
               } catch (Exception e) {
                   System.out.println(name+"出错了!!!");
               } finally {
                   System.out.println("当前的线程是"+Thread.currentThread().getName()+"释放锁!");
               }
           }
       });
       t2.start();
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

double_lifly

点喜欢就是最好的打赏!!

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值