【java】java多线程及线程池面试题

阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6

目录

前言

线程是什么多线程是什么

线程 是操作系统能够进行运算调度的最小单位。它被包含在进程之中是进程中的实际运作单位。一条线程指的是进程中一个单一顺序的控制流比如一段程序、一个函数等。
多线程 多个线程并发执行的技术。

多线程的作用和好处以及缺点

作用 充分利用CPU的资源,采用多线程的方法去同时完成几件事情而互不干扰。

好处
使用线程可以把程序中占据时间长的任务放到后台去处理如图片、视频的下载。
发挥多核处理器的优势并发执行让系统运行的更快、更流畅用户体验更好。

坏处
大量的线程降低代码的可读性。
更多的线程需要更多的内存空间。
存在线程不安全问题。

守护线程和用户线程

守护线程 jvm给的线程。比如GC线程。
用户线程 用户自己定义的线程。比如main线程。

扩展
Thread.setDaemon(false)设置为用户线程
Thread.setDaemon(true)设置为守护线程

并发和并行的区别

**并发**一个处理器同时处理多个任务。(一个人同时吃两个苹果)
**并行**多个处理器同时处理多个任务。(两个人同时吃两个苹果)

一.线程的状态和常用方法

1.线程各种状态转化图

在这里插入图片描述

2.线程相关常用方法有

① wait()

  • wait() 线程等待会释放锁 (用于同步代码块或同步方法中不然会报错)然后进入等待状态和notify()和notifyAll()一起使用。
  • wait(long timeout) 等待规定的时间如果在规定时间内被唤醒就继续执行如果超过规定时间也会继续向下执行。
public class TestWait implements Runnable {
    private Object lock;
    public TestWait(Object lock) {
        this.lock = lock;
    }
    public static void main(String[] args) throws InterruptedException {
        Object lock = new Object();
        // 创建两个线程
        Thread t1 = new Thread(new TestWait(lock),"t1");
        Thread t2 = new Thread(new TestWait(lock),"t2");
        t1.start();
        t2.start();
    }

    @Override
    public void run() {
        synchronized (lock) {
            System.out.println(Thread.currentThread().getName() + "准备进入等待状态");
            try {
                //唤醒等待中的线程进入就绪状态
                lock.notify();
                
                //线程等待释放锁
                lock.wait();
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            System.out.println(Thread.currentThread().getName() + "等待结束继续执行");
        }
    }
}

运行结果
t2准备进入等待状态                
t1准备进入等待状态
t2等待结束本线程继续执行

运行结果不唯一但是总会有一个线程执行不完。
以上运行步骤如下
1.t2线程运行调用notify()方法唤醒等待中的线程(现在等待队列中没有线程)然后调用wait()方法进入等待队列。
2.t1线程运行调用notify()方法唤醒等待中的线程(这时唤醒了t2线程因为只有等待队列中只有t2所以一定是t2获取cpu使用权)然后wait()方法进入等待队列。
3.t2线程继续向下运行然后结束t1还在等待队列没有被唤醒所以t1执行不完。


注意被唤醒的线程会从等待队列进入就绪状态然后去竞争cpu的使用权。

② sleep(long timeout)

  • sleep() 线程睡眠不会释放锁(规定时间到了之后继续执行线程) 。一搬用于模拟网络延时。
public class TestSleep implements Runnable {

    public static void main(String[] args) throws InterruptedException {
        Object lock = new Object();
        // 创建两个线程
        Thread t1 = new Thread(new TestSleep(),"t1");
        Thread t2 = new Thread(new TestSleep(),"t2");
        t1.start();
        t2.start();
    }

    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName() + "准备进入等待状态");
        try {
            //线程睡眠两秒之后继续执行不会释放锁。
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        System.out.println(Thread.currentThread().getName() + "等待结束继续执行");
    }
}

运行结果
t1准备进入睡眠状态
t2准备进入睡眠状态
t1睡眠结束继续执行
t2睡眠结束继续执行

以上结果不唯一。

③ join()

  • join() 指定的线程加入到当前线程可以将两个交替执行的线程合并为顺序执行的线程。底层是利用wait()方法实现。可以应用于必须多个线程完成才能执行主线程比如三个人去酒店吃饭三个都到酒店才能上菜。

正常执行

public class TestJoin {

    public static void main(String[] args) {
      Thread t1 = new Thread(new Runnable() { //线程t1
            @Override
            public void run() {
                try {
                    Thread.sleep(2000);
                    System.out.println("执行t1");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });

        Thread t2 = new Thread(new Runnable() { //线程t2
            @Override
            public void run() {
                try {
                    Thread.sleep(3000);
                    System.out.println("执行t2");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        t1.start();
        t2.start();
        System.out.println("执行main");
    }
}

运行结果
执行main
执行t1
执行t2

让主线程(main)最后执行让 t1 和 t2 先执行完成。

public class TestJoin {

    public static void main(String[] args) throws InterruptedException {
      Thread t1 = new Thread(new Runnable() { //线程t1
            @Override
            public void run() {
                try {
                    System.out.println("执行t1");
                    Thread.sleep(4000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });

        Thread t2 = new Thread(new Runnable() { //线程t2
            @Override
            public void run() {
                try {
                    System.out.println("执行t2");
                    Thread.sleep(3000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        t1.start();
        t2.start();
        t1.join(); //t1参与到当前线程(main)的执行中所以主线程需要等待t1执行完成才会继续执行但是t2不受影响、。
        System.out.println("执行main");
    }
}

运行结果
执行t2
执行t1
执行main

或者

执行t1
执行t2
执行main

让 t1 和 t2 按顺序执行

public class TestJoin {

    public static void main(String[] args) throws InterruptedException {
      Thread t1 = new Thread(new Runnable() { //线程t1
            @Override
            public void run() {
                try {
                    System.out.println("执行t1");
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });

        Thread t2 = new Thread(new Runnable() { //线程t2
            @Override
            public void run() {
                try {
                    System.out.println("执行t2");
                    Thread.sleep(3000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        t1.start();
        t1.join(); //t1参与到当前线程的执行中所以主线程需要等待t1执行完成才会继续执行这是t2需要等待t1执行完毕之后才能执行。
        t2.start();
    }
}

运行结果
执行t1
执行t2

④ yield()

  • yield() 线程让步(正在执行的线程)会使线程让出cpu使用权进入就绪状态。
public class TestYield {

    public static void main(String[] args) throws InterruptedException {
      Thread t1 = new Thread(new Runnable() { //线程t1
            @Override
            public void run() {
                System.out.println("执行t1");
                Thread.yield();//t1让出cpu使用权限回到就绪状态
                System.out.println("执行结束t1");
            }
        });

        Thread t2 = new Thread(new Runnable() { //线程t2
            @Override
            public void run() {
                System.out.println("执行t2");
                System.out.println("执行结束t2");
            }
        });
        t1.start();
        t2.start();
    }
}

如果先执行t1然后t1会让出线程进入就绪状态然后t2执行完成t1再去竞争cpu的使用权在继续执行。

⑤ notify()和notifyAll()

  • notify() 随机唤醒一个在等待状态的线程进入就绪状态。
public class TestNotify implements Runnable{
    static Object lock = new Object();

    public static void main(String[] args) {
        Thread thread1 = new Thread(new TestNotify(), "t1");
        Thread thread2 = new Thread(new TestNotify(), "t2");
        thread1.start();
        thread2.start();
        try {
            Thread.sleep(1000); //主线程睡眠让线程t1或t2执行
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        synchronized (lock) {
            System.out.println(Thread.currentThread().getName() + " 获得了锁");
            System.out.println("唤醒前" + thread1.getName() + "状态是" + thread1.getState());
            System.out.println("唤醒前" + thread2.getName() + "状态是 " + thread2.getState());
            lock.notify(); // 随机唤醒一个在等待状中的线程
        }
        try {
            Thread.sleep(1000);//主线程睡眠让线程t1或t2执行
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("唤醒后" + thread1.getName() + "状态是" + thread1.getState());
        System.out.println("唤醒后" + thread2.getName() + "状态是" + thread2.getState());
    }

    @Override
    public void run() {
        synchronized (lock) {
            System.out.println(Thread.currentThread().getName() + " 开始执行");
            try {
                lock.wait(); // 进入等待状态
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + " 线程结束");
        }
    }
}

运行结果
t1 开始执行
t2 开始执行
main 获得了锁
唤醒前t1状态是WAITING
唤醒前t2状态是 WAITING
t1 线程结束
唤醒后t1状态是TERMINATED
唤醒后t2状态是WAITING

运行结果不唯一由上面可以看出notify()随机唤醒了一个线程唤醒的是t1也有可能唤醒的是t2。然后没被唤醒的线程一直处于等待状态这样就会导致程序结束不了。
  • notifyAll() 唤醒在等待队列的所有线程进入就绪状态。
package com.navi.vpx;

public class TestNotify implements Runnable{
    static Object lock = new Object();

    public static void main(String[] args) {
        Thread thread1 = new Thread(new TestNotify(), "t1");
        Thread thread2 = new Thread(new TestNotify(), "t2");
        thread1.start();
        thread2.start();
        try {
            Thread.sleep(1000); //主线程睡眠让线程t1或t2执行
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        synchronized (lock) {
            System.out.println(Thread.currentThread().getName() + " 获得了锁");
            System.out.println("唤醒前" + thread1.getName() + "状态是" + thread1.getState());
            System.out.println("唤醒前" + thread2.getName() + "状态是 " + thread2.getState());
            lock.notifyAll(); // 唤醒全部在等待状态的线程
        }
        try {
            Thread.sleep(1000);//主线程睡眠让线程t1或t2执行
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("唤醒后" + thread1.getName() + "状态是" + thread1.getState());
        System.out.println("唤醒后" + thread2.getName() + "状态是" + thread2.getState());
    }

    @Override
    public void run() {
        synchronized (lock) {
            System.out.println(Thread.currentThread().getName() + " 开始执行");
            try {
                lock.wait(); // 进入等待状态
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + " 线程结束");
        }
    }
}
运行结果
t1 开始执行
t2 开始执行
main 获得了锁
唤醒前t1状态是WAITING
唤醒前t2状态是 WAITING
t2 线程结束
t1 线程结束
唤醒后t1状态是TERMINATED
唤醒后t2状态是TERMINATED


运行结果不唯一有上面可得notifyAll()唤醒的是所有在等待状态的线程使线程都进入就绪状态然后竞争锁最后全部执行完成程序结束。

3.wait()和sleep()的区别

① wait() 来自Objectsleep()来自Thread。

② wait()会释放锁sleep()不会释放锁。

③ wait()只能用在同步方法或代码块中sleep()可以用在任何地方。

④ wait()不需要捕获异常sleep()需要捕获异常。

4.为什么 wait()、notify()、notifyAll()方法定义在 Object 类里面而不是 Thread 类

锁可以是任何对象如果在Thread类中那只能是Thread类的对象才能调用上面的方法了。

② java中进入临界区(同步代码块或同步方法)线程只需要拿到锁就行而并不关心锁被那个线程持有。

③ 上面方法是java两个线程之间的通信机制如果不能通过类似synchronized这样的Java关键字来实现这种机制那么Object类中就是定义它们最好的地方以此来使任何Java对象都可以拥有实现线程通信机制的能力。

三.实现多线程的方式

1.继承Thread类(只能继承一个类)

public class MyThread extends Thread{
	
    public void run(){
        System.out.println("执行run()方法");
    }

    public static void main(String[] args) {
    	//创建对象
        MyThread myThread = new MyThread();

		//启动线程执行run()方法
        myThread.start(); 
    }
}

2.实现Runnable接口(可以实现多个接口)

public class MyRunable implements Runnable{
    
    //重写的run()方法
    @Override
    public void run() {
        System.out.println("执行run()方法");
    }

    public static void main(String[] args) {
        //创建对象
        MyRunable myRunable = new MyRunable();
        
        //创建线程对象
        Thread thread = new Thread(myRunable);
		
		//启动线程执行run()方法
        thread.start(); 
    }
}

3.使用Callable、FutureTask实现有返回结果的多线程

public class MyCallable implements Callable<String> {

    //重写的run()方法
    @Override
    public String call() {
        System.out.println("执行run()方法");
        return "call返回值";
    }

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        //创建对象
        MyCallable myCallable = new MyCallable();

        //创建futureTask
        FutureTask<String>  futureTask = new FutureTask(myCallable);

        //创建线程对象
        Thread thread = new Thread(futureTask);

        //开启线程执行run()方法
        thread.start();

        //接收call()的返回值
        String result = futureTask.get();
        System.out.println(result);

    }
}

4.使用线程池–下面会详细讲解

5.附加问题runnable和callable的区别

① runnable没有返回值callable有返回值。

②runnable 只能抛出异常不能捕获callable 能抛出异常也能捕获。

四.线程池(4大方法、7大参数、4种拒绝策略)

1.线程池的好处

① 线程是稀缺资源使用线程池可以减少线程的创建和销毁每个线程都可重复使用。

② 可以根据系统的需求调整线程池里面线程的个数防止了因为消耗内存过多导致服务器崩溃。

2.七大参数(用银行柜台举例)

public ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue<Runnable> workQueue
                          ThreadFactory threadFactory,
                          RejectedExecutionHandler handler)
  • corePoolSize核心线程数(最少开放的柜台数)

线程池维护的最小线程数创建后不会被回收(除非设置allowCoreThreadTimeout=true后空闲的核心线程超过存活时间(keepAliveTime)也会被回收)。当线程池使用 executor() 方法执行一个新任务时会先判断运行的线程是否超过核心线程数如果不超过就会创建一个新线程并执行任务。

    /** ThreadPoolExecutor 源码
     * If false (default), core threads stay alive even when idle.
     * If true, core threads use keepAliveTime to time out waiting
     * for work.翻译如下
     * 如果为false默认值则核心线程即使在空闲时也保持活动状态。
     * 如果为true则核心线程使用keepAliveTime超时等待工作。
     */
    private volatile boolean allowCoreThreadTimeOut;   //核心线程能否被回收
  • maximumPoolSize最大线程数(最大开方法的柜台数)

线程池允许创建的最大线程数量。

当有有新的任务时当核心线程满了还没到达最大线程数并且没有空闲线程工作队列已满那就创建一个新线程去执行任务。

  • keepAliveTime空闲线程存活时间(当柜台没有人时就关闭的时间)

当一个可被回收的线程空闲时间超过keepAliveTime就会被回收。

可被回收的线程
① 设置allowCoreThreadTimeout=true的核心线程。
② 大于核心线程数的线程(非核心线程)。

  • unit空闲线程存活时间

keepAliveTime的时间单位

可选项含义
NANOSECONDS纳秒(1000纳秒 = 1微秒)
MICROSECONDS微秒(1000微妙 = 1毫秒)
MILLISECONDS毫秒(1000毫秒 = 1秒)
SECONDS
MINUTES分钟
HOURS小时
DAYS
  • workQueue工作队列(等候区等候区满了的话就会开放最大柜台数)

用于存放任务的队列

  • handler拒绝策略(柜台满了等候区也满了不接待的策略)

超出线程范围和队列容量的任务的处理程序

3.四大方法

在这里插入图片描述

注意为什么不推荐使用jdk自带的executors的方式来创建线程池?

图片内容来自阿里开发手册
在这里插入图片描述

① ExecutorService executor = Executors.newCachedThreadPool()

创建一个可缓存线程池如果线程池长度超过处理需要可灵活回收空闲线程若无可回收则新建线程。
优点可以灵活的回收线程。
缺点如果创建任务过多会导致OOM(内存溢出)。

 	public static ExecutorService newCachedThreadPool() {
        return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                      60L, TimeUnit.SECONDS,
                                      new SynchronousQueue<Runnable>());
    }

② ExecutorService executor = Executors.newFixedThreadPool()

创建一个指定工作线程数量的线程池。
优点提高线程池的效率和创建线程时的开销。
缺点因为可能堆积大量请求(队列中)导致OOM。

	//如果核心线程满了那就存入队列中提高了线程池效率和创建的线程时的开销即使空闲也不会被回收
	public static ExecutorService newFixedThreadPool(int nThreads) {
        return new ThreadPoolExecutor(nThreads, nThreads,
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue<Runnable>());
    }
    
	public LinkedBlockingQueue() {
        this(Integer.MAX_VALUE);
    }

③ ExecutorService executor = Executors.newSingleThreadPool()

创建一个单线程
优点可以保证线程的有序执行如果出现异常会在创建一个新的线程。
缺点因为只能创建一个可能速度没有那么快。

    public static ExecutorService newSingleThreadExecutor() {
        return new FinalizableDelegatedExecutorService
            (new ThreadPoolExecutor(1, 1,
                                    0L, TimeUnit.MILLISECONDS,
                                    new LinkedBlockingQueue<Runnable>()));
    }

④ ExecutorService executor = Executors.newScheduleThreadPool()

创建一个定长的线程池支持定时及周期性任务执行

    public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
        return new ScheduledThreadPoolExecutor(corePoolSize);
    }

    public ScheduledThreadPoolExecutor(int corePoolSize) {
        super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
             new DelayedWorkQueue());
    }

因为这个线程池比较特殊所以咱们写一下示例增强理解
第一种
ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(5);

 scheduledThreadPool.schedule(new Runnable() {
 
      @Override
      public void run() {
         log.info("延长5秒后执行");
       }
 }, 5, TimeUnit.SECONDS);


第二种
ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(5);
 
 scheduledThreadPool.schedule(new Runnable() {
 
      @Override
      public void run() {
         log.info("延迟一秒每5秒执行一次");
       }
 }, 15, TimeUnit.SECONDS);

4.四大拒绝策略

  • new ThreadPoolExecutor.AbortPolicy()
    添加线程池被拒绝会抛出异常(默认策略)
  • new ThreadPoolExecutor.CallerRunsPolicy()
    添加线程池被拒绝不会放弃任务也不会抛出异常会让调用者线程去执行这个任务(就是不会使用线程池里的线程去执行任务会让调用线程池的线程去执行)
  • new ThreadPoolExecutor.DiscardPolicy()
    添加线程池被拒绝丢掉任务不抛异常。
  • new ThreadPoolExecutor.DiscardOldestPolicy()
    添加线程池被拒绝会把线程池队列中等待最久的任务放弃把拒绝任务放进去。

五.线程死锁

1.什么是死锁

各进程互相等待对方手里的资源导致各进程都阻塞无法向前推进的现象。死锁会浪费大量系统资源导致系统崩溃所以我们一定要避免死锁。

2.进程死锁、饥饿、死循环的区别

死锁 各进程互相等待对方手里的资源导致各进程都阻塞无法向前推进的现象。死锁会浪费大量系统资源导致系统崩溃所以我们一定要避免死锁。
饥饿 线程一直得不到想要的资源(比如在短进程优先SPF算法中若有源源不断的短进程到来则长进程将一直得不到处理机从而发生长进程“饥饿”。)
死循环 某进程执行过程中一直跳不出某个循环的现象。

3.造成死锁的四个必要条件

  • 互斥当资源被一个线程占用时别的线程不能使用。
  • 不可抢占进程阻塞时对占用的资源不释放。
  • 不剥夺进程获得资源未使用完不能被强行剥夺。
  • 循环等待若干进程之间形成头尾相连的循环等待资源关系。

3.什么时候会发生死锁

① 对系统资源的竞争
对不可剥夺资源(磁带机、打印机)的竞争可能导致死锁。像CPU这种可剥夺的资源就不会形成死锁。
② 进程推进顺序非法
请求和释放资源不当也会导致死锁。比如线程A和线程B并行占用了资源1和资源2然后线程A需要资源2线程B需要资源1因为资源被对方所占用而阻塞导致死锁。
③ 信号量不当
如在生产者-消费者问题中若实现互斥的P操作在实现同步的P操作之前就会导致死锁。

信号量 其实就是一个变量可以是一个整数也可以是更复杂的记录型变量可以用一个信号量来表示系统中某种资源的数量。
一对原语 wait(S) 原语和 signal(S) 原语简称为P(S)和V(S),可以把原语理解为我们自己写的函数函数名分别为 wait 和 signal括号里的信号量 S 其实就是函数调用时传入的一个参数。

生产者-消费者设置互斥和同步
生产者和消费者共享一个初始为空、大小为n的缓冲区
二者必须互斥的访问缓冲区互斥
只有缓冲区没满的时候生产者才能把产品放入缓冲区否则必须等待同步
只有缓冲区不空时消费这才能从中取走产品否则等待同步

4.怎么解决死锁

① 预防死锁

  • 破坏互斥
    只允许互斥使用的资源改造成共享使用 比如SPOOLing技术但是很多时候有些资源改不了互斥有的还要保护互斥。
  • 破坏不剥夺
    Ⅰ 当某个进程请求新的资源被拒绝那它就释放自己占有的资源等以后重新申请。就是资源没用完也得释放破了不剥夺条件。
    Ⅱ 当某个进程需要的资源被其他进程占用时可以由操作系统协调进行资源强行剥夺。这种方式一般要考虑每个进程的优先级。
    缺点
    (1).实现比较复杂。
    (2).释放已获得的资源可能造成前一阶段工作的失效。因此这种方法一般只适用于易保存和恢复状态的资源。
    (3).反复地申请和释放资源会增加系统开销降低系统吞吐量(吞吐量是指对网络、设备、端口、虚电路或其他设施单位时间内成功地传送数据的数量以比特、字节、分组等测量)。
    (4).如采用 Ⅰ 有可能一直会得不到自己想要的资源可能会导致饥饿。
  • 破坏请求和保持
    采用静态分配方法即进程在运行前一次申请完它所需要的全部资源在它的资源未满足前不让它投入运行。一旦投入运行后这些资源就一直归它所有该进程就不会再请求别的任何资源了。
    缺点
    因为会一直占有资源所以资源利用率极低严重浪费资源。还可能会导致饥饿。
  • 破坏循环等待
    采用顺序资源分配法给系统资源编号然后申请时只能按编号从小到大申请同类资源必须一次性申请完。
    缺点
    因为资源编号相对固定所以添加新资源不方便因为需要重新分配编号。

② 避免死锁

银行家算法 每一个新进程进入系统时必须声明需要每种资源的最大数目其数目不能超过系统所拥有的的资源总量。当进程请求一组资源时系统必须首先确定是否有足够的资源分配给该进程若有再进一步计算在将这些资源分配给进程后是否会使系统处于不安全状态如果不会才将资源分配给它否则让进程等待。
安全状态 指系统按照这种序列分配资源则每个进程都能顺利完成。只要能找出一个安全序列系统就是安全状态。当然安全序列可能有多个。

③ 死锁的检测和解决

死锁的检测

Ⅰ.用某种数据结构(图结构)来保存资源的请求和分配信息
Ⅱ.提供一种算法利用上述信息来检测系统是否已进入死锁状态。
在这里插入图片描述
(1在上图中找出既不阻塞又不是孤点的进程Pi即找出一条有向边与它相连且该有向边对应资源的申请数量小于等于系统中已有空闲资源数量。如下图中R1没有空闲资源R2有空闲资源。若所有连接该进程的边均满足上述条件则这个进程能继续运行直至完成然后释放它所占有的所有资源。消去它所有的请求边和分配边使之成为孤立的结点。在下图中P1是满足这一条件的进程结点于是将P1的所有边消去。
(2进程Pi所释放的资源可用唤醒某些某些因等待这些资源而阻塞的进程原来的阻塞进程可能变为非阻塞进程。在下图P2就满足这样的条件。根据1中的方法进行一系列简化后若能消区图中所有的边则称该图是完全简化的。
有向边若顶点Vi到Vj之间的边有方向则称这条边为有向边。是图结构的名词。

死锁的解决

Ⅰ资源剥夺法 挂起暂时放到外存上某些死锁进程并抢占它的资源将这样资源分配给其他的死锁进程。但是应该防止被挂起的进程长时间得不到资源而饥饿。
Ⅱ 撤销进程法或终止进程法强制撤销部分、甚至全部死锁进程并剥夺这些进程的资源。这种方式的优点是实现简单但所付出的代价可能会很大。如果有的进程已经运行了很长时间了被撤销还得从新执行。
Ⅲ 进程回退法 让一个或多个死锁进程回退到足以避免死锁的地步。这就要求系统要记录进程的历史信息设置还原点。

如何决定"对谁动手(选择进程解决死锁)"

  • 进程优先级
  • 已执行多长时间
  • 还要多久能完成
  • 进程已经使用了多少资源
  • 进程是交互式的还是批处理式的

五.线程安全

1.线程安全主要是三方面

  • 原子性 操作不可分割要么全部执行并且不能被打断否则全部不执行。
  • 可见性 可见性是指当多个线程访问同一个变量时一个线程修改了这个变量的值其他线程能够立即看得到修改的值。
  • 有序性 程序执行的顺序按照代码的先后顺序执行。

2.保证原子性

  • 使用锁 synchronized和 lock。
  • 使用CAS (compareAndSet比较并交换CAS是cpu的并发原语)。

3.保证可见性

  • 使用锁 synchronized和 lock。
  • 使用volatile关键字 。

4.保证有序性

  • 使用 volatile 关键字
  • 使用 synchronized 关键字。

5.volatile和synchronized的区别

① volatile仅能使用在变量级别的synchronized可以使用在变量、方法、类级别的
② volatile不具备原子性具备可见性synchronized有原子性和可见性。
③ volatile不会造成线程阻塞synchronized会造成线程阻塞。
④ volatile关键字是线程同步的轻量级实现所以volatile性能肯定比synchronized要好。

6.synchronized和lock的区别

① synchronized是关键字lock是java类默认是不公平锁源码。
② synchronized适合少量同步代码lock适合大量同步代码。
③ synchronized会自动释放锁lock必须放在finally中手工unlock释放锁不然容易死锁。

线程安全后面我会具体写一下原因和讲解。

阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6
标签: Java