JavaSE进阶(三)——线程同步

JavaSE进阶(三)——线程同步,第1张

JavaSE进阶(三)——线程同步 JavaSE进阶(三)——线程同步 前言

本笔记记录线程同步,线程中比较重要的逻辑,未来在工作中 *** 作线程的时候回基于这个方面(面试也会有问到)。

文章目录
  • JavaSE进阶(三)——线程同步
    • 前言
    • 概述
    • 线程同步
      • 线程同步要点
      • synchronized
        • 同步块
      • 死锁
      • Lock锁(可重入锁)
    • 总结

概述

线程同步主要是为了出现多个线程 *** 作同一个资源(并发),也可以理解为同一个对象被多个线程同时 *** 作。我们需要让他们尽然有序的去执行相关 *** 作。

比如春运买票的时候,一万人抢票,同时抢一张票,如果系统没有相关的保护措施的话,后台很有可能会出现**-9999**张票的情况(系统崩了)。又或者我们同时在同一家银行使用无存折取款,都选择全部取出,如果银行没有相关线程保护措施,那么很有可能两个机器都可以取到钱(嗯,弱势群体了)。

线程同步就是为了解决这个问题,就比如大家在学校食堂吃放,同一个窗口只有一个食堂阿姨,我们需要排队打饭,而且在一个人打饭的时候,另一位同学无法打饭。

所以我们在程序中执行多线程的 *** 作时,会对资源进行一个上锁的 *** 作,在一个线程访问资源的时候,其他线程需要排队进行等待。

这些排队等待的线程所处的位置就是对象(线程)等待池。等待前面的线程执行完 *** 作,后在线程池中找到下一个线程来 *** 作。

线程同步

我们来做一个更加详细的说明(市面上每一个教程或者视频都会举这么一个有味道的例子)。比如:我们排队去厕所,厕所只有一个坑位。第一个人进入厕所后把门锁上,后面的人在你没出来之前无法进入厕所。只有你解决完私人问题打开厕所门离开才可以让别人使用。

【注】:在此补充一个知识点:sleep休眠不会开锁。可以理解为一只老八进入了厕所,他进去之后陷入休眠,在做别的事情,这个时候外面的人依旧没办法进入厕所。只有老八完成了 *** 作才可以供他人使用。

上述描述中,我们把排队的线程结构称为队列;把厕所门称为锁。线程同步是由这两个对象组成的。

线程同步要点

在做线程同步的时候,我们会在被多个线程访问的临界资源中如锁机制——synchronized,即当一个线程在在访问资源的时候会有一个排他锁,这个时候会独占资源,其他线必须等待,使用后释放锁即可。

  • 一个线程持有排他锁的时候回导致其他所有需要此所得线程挂起;
  • 在多线程竞争下,加锁、释放锁等 *** 作会导致性能缓慢等问题;
  • 如果一个优先级较高的线程等待一个优先级较低的线程释放线程锁会导致性能问题。

上面的性能问题可能文字没有直观的描述出来,你可以理解为优先级越高上厕所越急,频繁上锁的话可能会造成很糟糕的后果(毕竟括约肌陷入人民的海洋中出现失控的现象而贻误战机)。

接下来以买票来演示一下代码:

public class User{
    public static void main(){
        Ticket ticket = new Ticket();
        new Thread(ticket,"一号").start();
        new Thread(ticket,"二号").start();
        new Thread(ticket,"三号").start();
    }
}

public class Ticket implements Runnable{
    private int ticketNum = 10;
    boolean flag = true;
    @Override
    public void run(){
        while(flag){
            buy();
        }
    }
    private void buy(){
        if(ticketNum <= 0){
            flag = false;
            return;
        }
        // 模拟延时
        Thread.sleep(100);        
        System.out.println(Thread.currentThread.getName() + "买到了" + "第" + ticketNum -- + "张票");
    }
}

上述代码中, 我们在买票的时候很有可能会因为CPU调度线程的时候出现售出超过十张票的情况,我们极力避免的就是这种情况。

synchronized

我们可以将执行线程同步的方法加上关键字就可以实现。该关键字会将修饰的方法控制对“对象的访问”。

其实这个锁相当于每个对象都有一把锁,对象在调用该方法的时候,这个锁会被方法独占,直到该方法执行完毕的时候才会释放锁,同时队列中阻塞或者等待的线程才可以获得这个锁从而继续执行。

但是这个方法有一个缺陷,如果我们将一个很大的方法或者一个类加入该关键字进行修饰,则会影响效率。所以我们会对业务进行分类:一般来说,只有涉及到修改 *** 作时才会进行上锁的方式,读取代码是不需要上锁的。

private synchronized void buy(){
    if(ticketNum <= 0){
        flag = false;
        return;
    }
    // 模拟延时
    Thread.sleep(100);        
    System.out.println(Thread.currentThread.getName() + "买到了" + "第" + ticketNum -- + "张票");
}

上述代码在执行的时候,会对当前方法所在类的实例访问方法时进行独占锁的 *** 作,保证大家不会抢厕所。

同步块

我们在方法上加入synchronized关键字进行修饰的时候,默认上锁的是该方法所在的类的实例(即,默认为this),如果我们需要其他类的对象访问这个方法的时候,我们就需要加入同步块来通知这个方法对设定好的对象进行上锁 *** 作。

synchronized(obj){
    //对对象执行的一些 *** 作
}
  • obj可以是任何对象;
  • 当我们执行方法时,修改的不是当前类的对象的时候,使用同步块可以对指定对象上锁;
  • 使用同步块时锁住的对象是临界资源。
死锁

死锁就是多个线程各自占有一定的资源,而且需要对方手中的资源才可以继续运行,并且系统中没有多余的资源供这些线程使用。各个线程之间都死死地守住自己的资源且想抢夺对方的资源,最终导致所有县城都陷入等待。

举一个形象一点的例子,比如影视剧里的剧情:劫持了人质的劫匪与前来救援的警察对峙中……。

警察:你把人质放开!

劫匪:你放我们走!

警察:你先放了人质!

劫匪:你先放我们走!

在计算机中,一个同步块中同时拥有了两个以上对象的锁的时候,就可能发生死锁问题。死锁形成的原因以及避免或解决思索的逻辑在 *** 作系统概述有详细解释,大家可以去看看。

Lock锁(可重入锁)

Java1.5开始提供了一个更加强大的线程同步机制——通过电视定义同步锁对象来实现同步。

private final ReentrantLock lock = new ReentrantLock();
@Override
public void run(){
    try{
        // 加锁
        lock.lock();
        // 接下来执行方法
    }finally{
        // 解锁
        lock.unlock();
    }
}

上述方法中可以直接在方法中对已经获取到lock锁的对象许可执行,关于lock锁还有一些要点如下:

  • Lock锁是显式锁,需要手动开启和关闭;
  • Lock只有代码块锁;
  • 使用Lock锁,JVM会花费较少的时间来调整线程,性能更好。
总结

本篇笔记依旧只是记录了代码中常使用、出现的问题和逻辑,如果需要了解线程、死锁等相关概念,请移步 *** 作系统概述中,这篇笔记会有较为详细的解释。

欢迎分享,转载请注明来源:内存溢出

原文地址: https://outofmemory.cn/zaji/5606697.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-12-15
下一篇 2022-12-15

发表评论

登录后才能评论

评论列表(0条)

保存