锁对象
什么是锁对象?
每个java对象都有一个锁对象.而且只有一把钥匙.
如何创建锁对象:
可以使用this关键字作为锁对象,也可以使用所在类的字节码文件对应的Class对象作为锁对象
1. 类名.class
2. 对象.getClass()
注意:非静态同步函数的锁对象是 this 对象,静态的同步函数的锁对象是当前函数所属的类的字节码文件(class对象)
Java中的每个对象都有一个内置锁,只有当对象具有同步方法代码时,内置锁才会起作用,当进入一个同步的非静态方法时,就会自动获得与类的当前实例(this)相关的锁,该类的代码就是正在执行的代码。获得一个对象的锁也成为获取锁、锁定对象也可以称之为监视器来指我们正在获取的锁对象。
因为一个对象只有一个锁,所有如果一个线程获得了这个锁,其他线程就不能获得了,直到这个线程释放(或者返回)锁。也就是说在锁释放之前,任何其他线程都不能进入同步代码(不可以进入该对象的任何同步方法)。释放锁指的是持有该锁的线程退出同步方法,此时,其他线程可以进入该对象上的同步方法。
1:只能同步方法(代码块),不能同步变量或者类
2:每个对象只有一个锁
3:不必同步类中的所有方法,类可以同时具有同步方法和非同步方法
4:如果两个线程要执行一个类中的一个同步方法,并且他们使用的是了类的同一个实例(对象)来调用方法,那么一次只有一个线程能够执行该方法,另一个线程需要等待,直到第一个线程完成方法调用,总结就是:一个线程获得了对象的锁,其他线程不可以进入该对象的同步方法。
5:如果类同时具有同步方法和非同步方法,那么多个线程仍然可以访问该类的非同步方法。
同步会影响性能(甚至死锁),优先考虑同步代码块。
6:如果线程进入sleep() 睡眠状态,该线程会继续持有锁,不会释放。
死锁
经典的“哲学家就餐问题”,5个哲学家吃中餐,坐在圆卓子旁。每人有5根筷子(不是5双),每两个人中间放一根,哲学家时而思考,时而进餐。每个人都需要一双筷子才能吃到东西,吃完后将筷子放回原处继续思考,如果每个人都立刻抓住自己左边的筷子,然后等待右边的筷子空出来,同时又不放下已经拿到的筷子,这样每个人都无法得到1双筷子,无法吃饭都会饿死,这种情况就会产生死锁:每个人都拥有其他人需要的资源,同时又等待其他人拥有的资源,并且每个人在获得所有需要的资源之前都不会放弃已经拥有的资源。
当多个线程完成功能需要同时获取多个共享资源的时候可能会导致死锁。
1:两个任务以相反的顺序申请两个锁,死锁就可能出现
2:线程T1获得锁L1,线程T2获得锁L2,然后T1申请获得锁L2,同时T2申请获得锁L1,此时两个线程将要永久阻塞,死锁出现
如果一个类可能发生死锁,那么并不意味着每次都会发生死锁,只是表示有可能。要避免程序中出现死锁。
例如,某个程序需要访问两个文件,当进程中的两个线程分别各锁住了一个文件,那它们都在等待对方解锁另一个文件,而这永远不会发生。
3:要避免死锁
public class DeadLock { public static void main(String[] args) { new Thread(new Runnable() { // 创建线程, 代表中国人 public void run() { synchronized ("刀叉") { // 中国人拿到了刀叉 System.out.println(Thread.currentThread().getName() + ": 你不给我筷子, 我就不给你刀叉"); try { Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } synchronized ("筷子") { System.out.println(Thread.currentThread() .getName() + ": 给你刀叉"); } } } }, "中国人").start(); new Thread(new Runnable() { // 美国人 public void run() { synchronized ("筷子") { // 美国人拿到了筷子 System.out.println(Thread.currentThread().getName() + ": 你先给我刀叉, 我再给你筷子"); try { Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } synchronized ("刀叉") { System.out.println(Thread.currentThread() .getName() + ": 好吧, 把筷子给你."); } } } }, "美国人").start(); } }