当前位置: 首页 > 后端技术 > Java

Java并发编程之锁(同步锁,死锁)

时间:2023-04-01 17:43:36 Java

这篇文章是我上一篇文章的续篇。上一篇同步锁为什么需要同步锁?首先,让我们看一下这张图。这是一个多对象抢票的程序。包MovieDemo;公共类ThM实现Runnable{privateintcount=10;私人整数=0;@Overridepublicvoidrun(){while(true){if(count<=0){中断;}数++;数数-;尝试{Thread.sleep(100);}catch(InterruptedExceptione){e.printStackTrace();}System.out.println(Thread.currentThread().getName()+"抢到"+num+"票数"+"剩余票数:"+count);}}}线程类中的代码很简单,就是当对象抢到一张票时,会记录计数,每抢到一张就丢一张。当我们只有一个对象时,我们的程序是正常的,但是当我们有多个对象一起抢票时。因为线程是并发的,就像挤公车一样,多人挤在一起。所以会出现多人抢同一张票的问题。因此,当我们有多个对象时,代码是这样运行的:首先,多个线程的对象是并发的,即同时抢票。抢票时,多个对象同时抢票。系统会认为这些对象抢到了票,但是票只有一张,这时候系统就会出错。此时的关系是几个人共用一张票。那么在现实生活中,一定不是这样的。我们需要排队,而且必须只有一个人一张票。packageMovieDemo;publicclassTest{publicstaticvoidmain(String[]args){ThMm=newThM();线程t=新线程(m);线程t1=新线程(m);线程t2=新线程(m);t1.开始();t2.开始();t.开始();}}这里有三个线程对象!让我们运行程序看看结果。线程0抢到第3票剩余票数:7线程1抢到第3票剩余票数:7线程2抢到第3票剩余票数:7线程0抢到第6票剩余票数:4线程-2抢到第6票剩余票数:4Thread-1抢到第6票剩余票数:4Thread-1抢到第9票剩余票数:1Thread-0抢到第9票剩余票数:1Thread-2抢到第9票剩余票数:1Thread-1抢到第10票剩余票数:0很明显这个程序是不对的,0、1、2三个人都拿到了同一张票。那么我们如何解决这个问题呢?同步锁的使用举个例子,一个公共厕所,一扇门,你和一堆人想进去厕所,这时候你进去了,但是其他人也进来了,怎么办?这时候你明智的拉(锁)厕所的门,等你用完厕所再开锁,下一个人会继续这样做。synchronize(Object)就是我们所说的这个锁。对象是对象。我们先来看看这个“锁”的作用:1.每个对象都有一个与之关联的内在锁(intrinsiclock)或监视器锁(monitorlock)2.第一个执行同步语句的线程可以获得内部锁obj,并在执行完同步语句3中的代码后释放锁。只要一个线程持有内部锁,其他线程将无法再同时获取锁。当它们试图获取锁时,就会进入BLOCKED状态4.当多个线程访问同一个synchronized(Object)语句时,Object必须是同一个对象才能实现同步。锁的方法同步锁的用法有很多。我们可以这样使用锁方法:**实例方法:synchronized(this)静态方法:synchronized(Classobject)**注意synchronized不能修改构造方法!!!Lock语句但是我们一般不喜欢直接的锁法,比如,你有一个宝箱,你只需要锁箱子,不需要锁箱子所在的房子。**同步语句比同步方法更灵活。同步语句可以精确控制需要加锁的代码范围,减少BLOCKED状态的线程,充分利用人工**实际操作还是上面的方法。我锁定它运行的部分!包MovieDemo;公共类ThM实现Runnable{privateintcount=10;私人整数=0;@Overridepublicvoidrun(){while(true){synchronized(this){if(count<=0){break;}数++;数数-;尝试{Thread.sleep(100);}catch(InterruptedExceptione){e.printStackTrace();}System.out.println(Thread.currentThread().getName()+"抢到第一"+num+"票数"+"剩余票数:"+count);}}}}其实这部分是我加锁的。同步(这个){如果(计数<=0){中断;}数++;数数-;尝试{线程。睡觉(100);}catch(InterruptedExceptione){e.打印堆栈跟踪();}系统。出去。println(Thread.currentThread().getName()+"抢先"+num+"投票"+"剩余票数:"+count);}这个关键字指的是当前线程的对象!我们运行一下,看看是否还会出现之前的情况。可能第二个对象运气好,第一张票没有被对象2抢到。不过,现在已经不存在两个人抢同一张票的故障了。特别注意运行类代码。现在我会改变它。packageMovieDemo;publicclassTest{publicstaticvoidmain(String[]args){ThMt=newThM();ThMt1=newThM();ThMt2=newThM();t.开始();t2。开始();t1.开始();}}这个时候我再运行,就会出现第一个失败。因为此时,你的锁并没有锁定同一个对象。和之前。ThMm=newThM();Threadt=newThread(m);Threadt1=newThread(m);Threadt2=newThread(m);虽然是三个thread对象,但是它们新建的对象都是m,也就是ThM类的对象是一样的!线程同步的优缺点使用线程同步技术后:虽然解决了线程安全问题,但是降低了程序的执行效率。因为加了锁之后,就会有等待的线程,加锁和解锁的操作也比较多。所以只有在真正需要的时候才使用线程同步技术。死锁什么是死锁:两个或多个线程永远阻塞,等待对方的锁是并发的。下一组争夺资源的线程互相等待导致永久阻塞。CSDN上一个大佬的例子很好理解。:线程a占用对象锁1,线程b占用对象锁2,线程a需要继续占用对象锁2才能继续。因此线程a需要等待线程b释放对象锁2,线程b需要继续占用对象锁1才能继续执行,线程a也需要释放对象锁1由于这两个线程都没有释放自己已经占有的锁,所以这两个线程会处于无限等待状态,却无人能上去!这是那个博主的例子,很有意思。为什么会出现死锁?mutexpossession和waitingfornon-preemptibleloopwaiting呢?互斥-->共享资源只能被一个线程占用。例如,一个座位只能容纳一个人。拥有并等待-->假设此时你有一个玩具,其他孩子某处有一个玩具,你想要两个玩具,你拿着自己的玩具不松手,然后等另一个孩子停下来玩耍时,你会得到两个玩具。非抢占-->资源只能由持有它的线程主动释放,其他线程不能强行占用资源——对方的资源不能释放。说白了就是不能抢别人的东西,(除非别人让你抢)。循环等待-->这个用上面的玩具解释了。假设此时你有一个玩具,其他小朋友在某处也有一个玩具。玩完就去拿,可是另一个小孩也是一样,等你不玩了再拿。此时双方僵持不下。如何先解决死锁情况!不能勉强!死锁无法直接解除,线程安全无法保证。那么该怎么办?找到原因!麻烦该结束了吧。换句话说,我们必须打破以上4个原因中的任何一个。博主说的很好,直接搬过来了!老板博客锁定在这个线程8。如果一个对象中有多个synchronized方法,只要某个线程在某个时刻调用了其中一个synchronized方法,其他线程就只能等待了。也就是说,某个时刻,只有一个线程可以访问这些synchronized方法。当前对象已锁定。被锁定后,其他线程无法进入当前对象的其他synchronized方法。添加一个常用的方法来查找和同步锁Irrelevant?更改为两个对象后,锁不相同,情况立即发生变化。?换成静态同步方法后,情况又变了?所有非静态同步方法都使用同一个锁——实例对象本身,也就是说,如果一个实例对象的非静态同步方法获取了锁,该实例对象的其他非静态同步方法必须等待获取锁的方法释放锁后才能获取锁,但其他实例对象的非静态同步方法使用的锁与非静态同步不同实例对象的方法,所以不需要等待已经获取锁的实例对象的非静态synchronized方法释放锁来获取自己的锁。?所有静态同步方法也使用相同的锁——类对象本身。这两个锁是两个不同的对象,所以静态同步方法和非静态同步方法之间不会出现竞争条件。但是一旦一个静态同步方法获取了锁,其他静态同步方法必须等待该方法释放锁后才能获取锁,无论是在同一个实例对象的静态同步方法之间,还是不同实例对象的静态同步方法之间之间,只要是同一个类的实例对象,就一定是这样的!!!线程8锁可以说是一个概念!我们记住以下两点:①非静态方法默认的锁是this,静态方法默认的锁是class②在某个时刻,无论有多少方法,都只能有一个线程持有锁

猜你喜欢