关于java:Java并发编程之Lock同步锁死锁

33次阅读

共计 4102 个字符,预计需要花费 11 分钟才能阅读完成。

这篇文章是接着我上一篇文章来的。

上一篇文章

同步锁

为什么须要同步锁?

首先,咱们来看看这张图。

这是一个程序,多个对象进行抢票。

package MovieDemo;

public class ThM implements Runnable {
    private int count = 10;
    private int num = 0;
    @Override
    public void run() {while (true) {if (count <= 0) {break;}
                num++;
                count--;
                try {Thread.sleep(100);
                } catch (InterruptedException e) {e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + "抢到了第" + num + "个票"+"残余票数:"+count);
        }
    }
}

线程类中代码很简略,就是当对象抢到票,count就会记录,每被抢一张就少一张。

当咱们只有一个对象时,咱们这个程序时失常的,然而当咱们多个对象一起抢票时。因为线程是同时的,就像挤公交,多集体一起挤进去。

所以这里会呈现多集体抢了同一张票的问题。

所以,当咱们多个对象存在时,代码是这样运行的:

  1. 首先,多个线程的对象并发,也就是同时去抢票。
  2. 抢票的时候,多个对象都同时都抢到了票。
  3. 零碎将会认为,这些对象都抢到了票,然而票只有一张,此时零碎就呈现谬误了。
  4. 此时的关系就是 几个人共享一张票

那再现实生活中,必定不能这样,咱们须要排队,必定只能一个人对应一张票。

package MovieDemo;

public class Test {public static void main(String[] args) {ThM m = new ThM();
        Thread t = new Thread(m);
        Thread t1 = new Thread(m);
        Thread t2 = new Thread(m);
        t1.start();
        t2.start();
        t.start();}
}

这里有三个线程对象!

咱们运行程序看看后果。

Thread- 0 抢到了第 3 个票残余票数:7
Thread- 1 抢到了第 3 个票残余票数:7
Thread- 2 抢到了第 3 个票残余票数:7
    
Thread- 0 抢到了第 6 个票残余票数:4
Thread- 2 抢到了第 6 个票残余票数:4
Thread- 1 抢到了第 6 个票残余票数:4
    
Thread- 1 抢到了第 9 个票残余票数:1
Thread- 0 抢到了第 9 个票残余票数:1
Thread- 2 抢到了第 9 个票残余票数:1
    
Thread- 1 抢到了第 10 个票残余票数:0

很显著看进去,这个程序就不对劲,0、1、2 这三个人都抢到了同一张票。

那咱们如何解决这种问题呢?

同步锁的应用

咱们举个例子,一个公共厕所,一张门,你和一堆人都想进去上厕所,你此时进去了,然而其他人也要进来,你该怎么办?

此时,你理智的将厕所的门拉上(锁上),等你上完厕所,再开锁,下一位持续如此。

synchronize(Object)就是咱们所说的这把锁。

Object是对象。

咱们先看看这个“锁”的作用:

1.每个对象 都有一个与它相干的外部锁(intrinsic lock)或者叫监视器锁(monitor lock)
2. 第一个执行到同步语句的线程能够取得 obj 的外部锁,在 执行完 同步语句中的代码后 开释此锁
3. 只有一个线程持有了外部锁,那么 其它线程在同一时刻将无奈再取得此锁 ,当它们试图获取此锁时,将会 进入 BLOCKED 状态
4. 多个线程拜访同一个 synchronized(Object) 语句时,Object必须是 同一个对象,能力起到同步的作用。

锁办法

同步锁用法很多,锁办法咱们能够这样:

** 实例办法:synchronized (this)
静态方法:synchronized (Class 对象)**

留神的是,synchronized不能润饰构造方法!!!

锁语句

然而咱们个别不喜爱间接锁住办法,就像,你有一个宝箱,你只须要锁住箱子,没必要将箱子所在的房子锁上。

** 同步语句比同步办法更灵便一点
同步语句能够准确管制须要加锁的代码范畴,缩小处于 BLOCKED 状态的线程, 充分利用劳动力 **

实际操作

还是下面那个办法,我给它运行的局部加上锁!

package MovieDemo;

public class ThM implements Runnable {
    private int count = 10;
    private int num = 0;
    @Override
    public void run() {while (true) {synchronized (this) {if (count <= 0) {break;}
                num++;
                count--;
                try {Thread.sleep(100);
                } catch (InterruptedException e) {e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + "抢到了第" + num + "个票"+"残余票数:"+count);
            }
        }
    }
}

实际上我锁住的是这一部分。

synchronized (this) {if (count <= 0) {break;}
    num++;
    count--;
    try {Thread.sleep(100);
    } catch (InterruptedException e) {e.printStackTrace();
    }
    System.out.println(Thread.currentThread().getName() + "抢到了第" + num + "个票"+"残余票数:"+count);
}

this关键词指代以后线程的对象!

咱们运行一下,看看还会不会呈现之前的情况。

可能第二个对象运气好哈,就第一张票不是对象 2 抢到的。

然而,当初就是齐全不会呈现两个人抢到同一张票的故障了。

特地留神

运行类代码当初我改一下。

package MovieDemo;

public class Test {public static void main(String[] args) {ThM t = new ThM();
        ThM t1 = new ThM();
        ThM t2 = new ThM();
        t.start();
        t2.start();
        t1.start();}
}

此时我运行一下,会产生先去的故障。

因为此时,你的锁,锁的不是同一个对象。

而之前。

ThM m = new ThM();
Thread t = new Thread(m);
Thread t1 = new Thread(m);
Thread t2 = new Thread(m);

尽管是三个线程对象,然而他们 new 的对象都是 m,也就是ThM 类的对象,是同一个!

线程同步的优缺

应用了线程同步技术后:

  1. 尽管解决了线程平安问题,然而 升高了程序的执行效率
  2. 因为加了锁就会有处于期待的线程,多了加锁解锁的操作
  3. 所以在真正有必要的时候,才应用线程同步技术。

死锁

什么是死锁:

两个或者多个线程永远阻塞,互相期待对方的锁

是并发下一组相互竞争资源的线程因相互期待导致永恒阻塞的景象

CSDN下面一个大佬的举例就很好了解:

线程 a 占用对象锁 1,线程 b 占用对象锁 2

线程 a 须要持续占用对象锁 2 能力往下执行,所以线程 a 须要期待线程 b 开释对象锁 2

线程 b 须要持续占用对象锁 1 能力往下执行,同样也须要线程 a 开释对象锁 1

因为这 2 个线程都不开释本人曾经占用的锁,所以这 2 个线程会处于有限期待状态

我说得比拟艰深,就是,挤公交车,两个人互挤,然而谁也上不去!

这是那位博主的举例,很有意思哈。

为何会产生死锁?

  1. 互斥
  2. 占有且期待
  3. 不可抢占
  4. 循环期待

怎么说呢?

  • 互斥——> 共享资源只能被一个线程占用,比方一个座位,只能包容一个人,两个人都想做,谁也不让谁,那就都坐不了!
  • 占有且期待——> 假如你此时有一个玩具,别的小朋友哪儿也有一个玩具,你想要两个玩具,你就拿着本人玩具不撒手,而后等另一个小朋友不玩了,你就取得了两个玩具。
  • 不可抢占——> 资源只能由持有它的线程被迫开释,其它线程不可强行占有该资源 - 无奈开释对方资源。说白了,你不能抢他人的货色,(除非他人让你抢)。
  • 循环期待——> 这个就拿下面的玩具解释,假如你此时有一个玩具,别的小朋友哪儿也有一个玩具,你想要两个玩具,你就拿着本人玩具不撒手,而后等另一个不玩了再去拿,然而另一个小朋友也是一样,等你不玩了再去拿。此时就僵持了。

如何解决锁死的状况

首先!不能强制!不能间接去去掉死锁,这样不能保障线程平安。
那怎么办?找起因!解铃还须系铃人。也就是说,咱们要突破下面 4 种起因中的任意一种。

大佬博客说的很好,我就间接搬过去了!

大佬博客在这

线程 8 锁

• 一个对象外面如果有 多个 synchronized 办法 ,某一个时刻内, 只有一个线程 调用其中的一个 synchronized 办法 了,其它的线程都只能期待,换句话说,某一个时刻内,只能有惟一一个线程去拜访这些 synchronized 办法

锁的是以后对象this,被锁定后,其它的线程都不能进入到以后对象的其它的 synchronized 办法

• 加个一般办法后发现和同步锁无关

• 换成两个对象后,不是同一把锁了,状况立即变动。

• 都换成动态同步办法后,状况又变动

所有的非动态同步办法用的都是同一把锁 ——实例 对象 自身,也就是说如果一个实例对象的非动态同步办法获取锁后,该实例对象的 其余非动态同步办法必须期待获取锁的办法开释锁后能力获取锁,可是别的实例对象的非动态同步办法因为跟该实例对象的非动态同步办法用的是不同的锁,所以毋须期待该实例对象已获取锁的非动态同步办法开释锁就能够获取他们本人的锁。

所有的动态同步办法用的也是同一把锁 ——对象自身,这两把锁是两个 不同的对象 ,所以 动态同步办法与非动态同步办法之间是不会有竞态条件 的。然而一旦 一个动态同步办法获取锁后 其余的动态 同步 办法 必须期待 该办法开释锁后 能力获取 锁,而不论是同一个实例对象的动态同步办法之间,还是不同的实例对象的动态同步办法之间,只有它们同一个类的实例对象,都得这样!!!

线程 8 锁能够说是个 概念

咱们记住以下两点:

① 非静态方法的默认锁是 this,静态方法的默认锁是 class

②某一时刻内,只能有一个线程有锁,无论几个办法

正文完
 0