天天看点

【操作系统】——线程(三)

1.死锁——在多线程(两个或者两个以上的线程)编程中,因为资源抢占而造成的线程无限等待的问题。

2.线程和锁的关系:

一个线程可以拥有多个锁,一个锁只能被一个线程拥有;

3.死锁代码

synchronized(lockA){
    System.out.println("线程1 得到锁A");
        synchronized(lockB){
        }
}

synchronized(lockB){
    System.out.println("线程2 得到锁B");
        synchronized(lockA){
        }
}
           

4.排查死锁的工具 jconsole、 jmc、 vm

5.死锁产生的4个条件(必须同时满足)

(1)互斥条件——一个资源只能被一个线程持有,当被一个线程持有之后,就不能被其它线程持有;——不可修改

(2)请求拥有条件——一个线程持有了一个资源之后又试图请求另一个资源;——可以修改

(3)不可剥夺条件——一个资源在被一个线程持有之后,如果这个线程不释放此资源,那么其它线程不能强制获得此资源;——不可修改

(4)环路等待条件——多个线程在获取资源时形成了环形链;——可以修改

 6.如何解决死锁问题

从请求拥有条件和环形等待条件入手,修改任意一个:

(1)请求拥有条件

(2)环形等待条件——通过控制获取锁的顺序来解决——破坏了环路等待条件;

7.线程通讯机制

线程通讯:一个线程的动作可以让另一个线程感知到就是线程通讯。

sleep()——线程休眠,必须传递一个明确的结束数据

wait()——线程休眠

notify()——唤醒线程

notifyAll()——唤醒全部线程

8.wait() 为什么要加锁?

wait()在使用的时候需要释放锁,在释放锁之前必须要有一把锁,所有要加锁;

wait()为什么要释放锁?

wait() 默认是不传递任何数据值的,当不传递任何值的时候表示永久等待,这样就造成一把锁被一个线程一直持有,为了避免这种问题的方式,所以在使用wait时要释放锁。 

 9.注意事项

(1)使用wait() 、notify() 、notifyAll()等方法时必须加锁;

(2)加锁对象和wait() 、notify() 、notifyAll()的对象必须保持一致;

(3)一组wait() 、notify() 、notifyAll()必须是同一对象;

(4)notifyAll()只能唤醒当前对象的所有等待线程;

10.Thread.sleep(0)和Object.wait(0)的区别

(1)sleep是Thread的静态方法;wait() 是Object的方法;

(2)sleep(0)——表示立即触发一次CPU资源的抢占;wait(0)——永久的等待下去

(3)都需要处理throws InterruptedException异常;

 11.wait和sleep的区别

(1)相同点:
  • 都可以让当前线程休眠;
  • 都必须处理Interrupt 异常;
(2)不同点:
  • wait是Object 的方法,而sleep是Thread的静态方法;
  • 传参不同:wait可以无参;sleep必须是一个大于等于0的参数;
  • wait使用时必须加锁;sleep使用时不用加锁
  • wait使用时会释放锁,而sleep不会释放锁;
  • wait默认不传参情况会进入WAITING状态;sleep默认不传参情况会进入TIME_WAITING状态;

 12.为什么wait放到Object而不是Thread中?

wait操作必须要加锁和释放锁,而锁是属于对象级别,而不是线程级别(线程和锁是一对多的关系,即一个线程可以拥有多把锁),为了灵活起见(一个线程当中会有多把锁),就把wait放入Object。

13. 线程休眠和唤醒对象——LockSupport(wait的升级)

不需要加锁和释放锁,也不需要处理异常;

LockSupport.park()——线程休眠——进入WAITING状态;

LockSupport.unpark(线程名称)——唤醒线程

14.wait和LockSupport的区别

(1)相同点
  • 两者都可以使线程进行休眠;
  • 两者都可以传参或者不传参,并且二者线程状态一致;
(2)不同点:
  • wait需要与Synchronized一起使用(必须加锁),而LockSupport不需要加锁;
  • wait只能唤醒全部和随机的一个线程;而LockSupport可以唤醒指定的线程;