zoukankan      html  css  js  c++  java
  • 详解 锁

    (有关线程的基本知识,请观看本人博文 —— 《详解 线程》

    在上篇博文中,本人通过一个例子,展示了 线程安全问题的现象 以及 出现的原因。
    那么,在本篇博文中,本人就来讲解下线程安全的处理手段之一的


    @


    说到锁,本人就不得不说说 同步代码块


    同步代码块:

    为什么本人说 锁 和 同步代码块有关系呢?

    本人仅拿同步代码块的格式来展示下这两者之间的关系:

    格式

    synchronized(对象){ //不能在括号了直接new 对象,new 了 就没效果
    要被同步的代码 ;
    }


    那么,对于上述的格式,本人要做以下说明

    说明

    这个同步代码块保证数据的安全性的一个主要因素就是这个对象
    注意这个对象 要定义为静态成员变量 才能被所有线程共享
    需要这个对象被所有的线程对象所共享
    这个对象其实就是一把锁
    这个对象习惯叫做 监视器临界资源


    本人现在来讲解下 同步的优缺点

    优缺点

    • 同步的好处:
      同步的出现解决了多线程的安全问题
    • 同步的弊端:
      当线程相当多时,因为每个线程都会去判断同步上的锁,这是很耗费资源的,无形中会降低程序的运行效率

    那么,现在,本人来介绍下 锁的类别

    锁的类别:

    1. 内置锁
    • 内置锁
      每个java对象都可以用做一个实现同步的锁,这些锁被称为内置锁
      线程进入同步代码块或方法的时候会自动获得该锁
      退出同步代码块或方法时会释放该锁

    那么,对于内置锁,本人有两点说明

    说明

    • 获得内置锁的唯一途径就是进入这个锁的保护的同步代码块或方法
    • java内置锁是一个互斥锁,这就意味着最多只有一个线程能够获得该锁
      当线程A尝试去获得线程B持有的内置锁时,线程A必须等待或者阻塞
      直到线程B释放这个锁,如果B线程不释放这个锁,那么A线程将永远等待下去

    1. 对象锁类锁
    • 对象锁和类锁
      java的对象锁和类锁锁的概念上基本上和内置锁是一致的

    那么,现在,本人来展示下这两种锁之间的区别

    区别

    • 对象锁用于对象实例方法,或者一个对象实例上的,
    • 类锁是用于类的静态方法或者一个类的class对象上的。

    可能有的同学看了上述的区别,会有如下疑惑:
    类的实例,不就是对象吗?那这两种锁有差别吗?

    那么,本人再来细讲下这两种锁的区别

    我们知道,类的对象实例可以有很多个,但是每个类只有一个class对象
    所以不同对象实例的对象锁是互不干扰的,但是每个类只有一个类锁
    但是有一点必须注意的是:
    其实类锁只是一个概念上的东西,并不是真实存在的
    它只是用来帮助我们理解锁定实例方法和静态方法的区别的


    现在,本人来根据应用场景,来介绍下 不同应用场景锁的对象:

    不同应用场景锁的对象:

    • 同步代码块的锁对象:
      任意一个对象
    • 同步方法的锁对象:
      this
    • 静态同步方法的锁对象:
      当前类对应的字节码文件对象(.class文件对象)

    本人还要再提的一点就是 synchronized锁是一个 “悲观锁
    (至于 悲观锁 的知识点,将在本人 多线程专题下的后续博文《详解 volatile关键字 与 CAS算法》中进行讲解)


    那么,说了这么多,本人现在来 通过上述知识点,来展示下对于 上篇博文的 卖票问题的解决:

    首先,本人来给出一个售票线程类

    package edu.youzg.about_synchronized.core;
    
    public class MyRunnable implements Runnable{
        static int piao = 100;
        static boolean goon = true;
        static Object obj = new Object();
        int i=1;
        @Override
        public void run() {
            while (goon) {
                goon = cellTickets();
            }
        }
    
    
        //方法上加有一个synchronized关键字我们叫做同步方法
        //同步方法使用的所对象不是任意对象,他用的锁是this
        private synchronized boolean cellTickets() {
            if (piao > 0) {
                System.out.println(Thread.currentThread().getName() + "正在出售" + (piao--) + "张票");
                return true;
            }
            return false;
        }
    
    }
    

    那么,现在,本人来给出测试类

    package edu.youzg.about_synchronized.core;
    
    public class Test {
    
        public static void main(String[] args) {
            MyRunnable myRunnable = new MyRunnable();
            Thread th1 = new Thread(myRunnable);
            Thread th2 = new Thread(myRunnable);
            Thread th3 = new Thread(myRunnable);
            th1.setName("窗口1");
            th2.setName("窗口2");
            th3.setName("窗口3");
            th1.start();
            th2.start();
            th3.start();
        }
    
    }
    

    那么,现在,本人来展示下运行结果
    在这里插入图片描述
    由于输出结果太长,本人仅展示部分结果。
    这次的运行结果没有出现线程安全问题!


    本人在之前的博文中曾说过 StringBuffer 和 Vector 这个两个类都是线程安全的,那么,是为什么呢?
    本人现在来展示下这两个类的部分原码:
    首先是 StringBuffer类:
    在这里插入图片描述

    然后是 Vector类:
    在这里插入图片描述
    可以看到,这两个类的好多方法,都用了synchronized锁,这就保证了线程的安全性!


    其实,在JDK5之后,专门有一个类来处理锁的基本操作 —— Lock类

    Lock 接口:

    首先,本人来介绍下这个类:

    概述

    虽然我们可以理解同步代码块和同步方法的锁对象问题,
    但是我们并没有直接看到在哪里加上了锁,在哪里释放了锁
    为了更清晰的表达如何加锁和释放锁,JDK5以后提供了一个新的锁对象 —— Lock


    那么,现在,本人来介绍下这个接口提供的API

    接口API

    • void lock()
      获取锁
    • void lockInterruptibly()
      获取该锁除非当前线程 interrupted
    • Condition newCondition()
      返回一个新的 Condition实例绑定到该 Lock实例
    • boolean tryLock()
      只有在调用时释放该锁,才能获取锁
    • boolean tryLock(long time, TimeUnit unit)
      获取锁,如果它是免费的在给定的等待时间和当前线程没有被 interrupted
    • void unlock()
      释放锁

    在这个接口的子实现类中,我们主要应用 ReentrantLock类

    ReentrantLock类:

    首先,本人先来介绍下这个类:

    概述

    一个可重入的互斥 Lock具有相同的基本行为和语义为隐式监控锁使用 synchronized方法和报表访问,但扩展功能。
    一个ReentrantLock是由线程最后成功锁定,但尚未解锁它。一个线程调用lock将返回,成功获取锁,当锁不是由另一个线程拥有。如果当前线程已经拥有锁,该方法将立即返回。这可以使用方法isHeldByCurrentThread()检查,并getHoldCount()。

    此类的构造函数接受一个可选的公平性参数。当设置true,争,锁青睐授予访问最长等待线程。否则,此锁不保证任何特定的访问顺序。使用许多线程访问的公平锁的程序可能会显示较低的整体吞吐量(即,比那些使用默认设置慢,往往要慢得多),但有较小的差异,在时间获得锁和保证缺乏饥饿。请注意,锁的公平性并不能保证线程调度的公平性。因此,使用一个公平锁的许多线程之一可能会获得它的连续多次,而其他活动线程不进展,而不是目前持有的锁。还要注意,不定时的tryLock()方法不尊重公平设置。如果锁是可用的,即使其他线程正在等待,它也会成功的。

    那么,本人再来展示下这个类的 构造方法

    构造方法

    • ReentrantLock()
      创建 ReentrantLock实例
    • ReentrantLock(boolean fair)
      创建具有给定的公平政策 ReentrantLock实例

    现在,本人来介绍下这个类的 常用API

    常用API

    • int getHoldCount()
      查询当前线程在这个锁上的数目
    • protected Thread getOwner()
      返回该线程当前拥有该锁
      如果不拥有,则返回null
    • protected Collection< Thread > getQueuedThreads()
      返回一个包含可能等待获取此锁的线程的集合
    • int getQueueLength()
      返回等待获取此锁的线程数的估计值
    • protected Collection< Thread > getWaitingThreads(Condition condition)
      返回一个集合,包含可能在与此锁关联的给定条件下等待的线程集合
    • int getWaitQueueLength(Condition condition)
      返回在与此锁关联的给定条件下等待的线程数的估计值
    • boolean hasQueuedThread(Thread thread)
      查询给定线程是否正在等待获取此锁
    • boolean hasQueuedThreads()
      查询是否有任何线程等待获取此锁
    • boolean hasWaiters(Condition condition)
      查询是否有任何线程在与此锁关联的给定条件下等待
    • boolean isFair()
      如果锁已经公平,则 返回 true
    • boolean isHeldByCurrentThread()
      查询如果这个锁是否由当前线程持有的
    • boolean isLocked()
      查询此锁是否由任何线程所持有
    • void lock()
      获取锁
    • void lockInterruptibly()
      获取该锁除非当前线程 interrupted
    • Condition newCondition()
      返回一个用于这 Lock实例 Condition实例
    • String toString()
      返回一个确定此锁的字符串,以及它的锁状态
    • boolean tryLock()
      只有在调用时,它不是由另一个线程持有的锁
    • boolean tryLock(long timeout, TimeUnit unit)
      如果这个锁 不是由另一个线程在等待时间当前线程没有被 interrupted,则返回true
    • void unlock()
      试图释放这个锁

    那么,现在,本人来展示下这个类的部分API 的使用:

    首先是 售票线程类

    package edu.youzg.about_synchronized.core;
    
    import java.util.concurrent.locks.ReentrantLock;
    
    public class MyRunnable implements Runnable{
        static int piao = 100;
        static Object obj = new Object();
        static ReentrantLock lock = new ReentrantLock();
        static boolean goon = true;
        @Override
        public void run() {
            while (goon) {
                //加锁
                try {
                    lock.lock();
                    if (piao > 0) {
                        System.out.println(Thread.currentThread().getName() + "正在出售" + (piao--) + "张票");
                    } else {
                        goon = false;
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                } finally {
                    //确保锁要释放掉
                    //释放锁
                    lock.unlock();
                }
            }
        }
    
    }
    

    那么,本人再来给出一个 测试类

    package edu.youzg.about_synchronized.core;
    
    public class Test {
    
        public static void main(String[] args) {
            MyRunnable myRunnable1 = new MyRunnable();
            Thread th1 = new Thread(myRunnable1);
            Thread th2 = new Thread(myRunnable1);
            Thread th3 = new Thread(myRunnable1);
            th1.setName("窗口1");
            th2.setName("窗口2");
            th3.setName("窗口3");
            th1.start();
            th2.start();
            th3.start();
        }
    
    }
    

    那么,本人来展示下运行结果:
    在这里插入图片描述


    在我们使用锁的过程中,可能会遇到一个很致命的问题 —— 死锁

    死锁:

    首先,本人来介绍下 死锁 是什么意思:

    概述

    死锁
    是指两个或者两个以上的线程在执行的过程中,因争夺资源产生的一种互相等待现象

    那么,看了上述的解释,可能还有同学不太明白发生的原因。
    那么,本人来举个例来展示下:

    举例
    中国人和美国人一起吃饭
    中国人使用的筷子
    美国人使用的刀和叉
    中国人获取到了美国人的刀
    美国人获取到了中国人的一根筷子
    这样的话,无论是中国人还是美国人,都无法运行到吃饭,就会一直等待对方将餐具归还

    那么,本人现在拿代码来展示下“死锁”状态的现象:

    首先,本人来创建一个提供两把锁的接口

    package edu.youzg.about_synchronized.core;
    
    public interface ObjectUtils {
        //创建两把锁对象
        public static final Object objA= new Object();
        Object objB=new Object();
    }
    

    那么,本人再来创建一个会产生死锁现象的线程类

    package edu.youzg.about_synchronized.core;
    
    public class MyThread extends Thread{
        boolean flag;
        public MyThread(boolean flag) {
            this.flag = flag;
        }
    
        @Override
        public void run() {
            if(flag) {
                synchronized (ObjectUtils.objA){
                    System.out.println("true====ObjA 进来了");
                    synchronized (ObjectUtils.objB){
                        System.out.println("true====ObjB进来了");
                    }
                } //objA 不释放
            } else {
                synchronized (ObjectUtils.objB) {
                    System.out.println("false====ObjB 进来了");
                    synchronized (ObjectUtils.objA) {
                        System.out.println("false====ObjA 进来了");
                    }
                } //objB不释放
            }
            System.out.println("后续代码... ...");
        }
    
    }
    

    现在,本人来给出一个测试类

    package edu.youzg.about_synchronized.core;
    
    public class Test {
    
        public static void main(String[] args) {
            MyThread th1= new MyThread(true);
            MyThread th2 = new MyThread(false);
            th2.start();
            th1.start();
        }
    
    }
    

    那么,本人现在来展示下运行结果
    在这里插入图片描述
    可以看到,线程仿佛 “卡死”在了那几行代码,
    这就是因为这两把锁都争夺走了对方所需的资源,造成了死锁现象


    现在,本人再来讲解一个在面试中,经常会问到的一个有关 多线程和锁 的问题:

    生产者与消费者问题:

    那么,本人直接来上代码:
    首先是 一个用于存储信息的Model类:

    package edu.youzg.about_synchronized.core;
    
    public class Model {
        String name;
        int age;
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        public int getAge() {
            return age;
        }
    
        public void setAge(int age) {
            this.age = age;
        }
    }
    

    现在,本人来创建一个能够创建线程的 生产者类:

    package edu.youzg.about_synchronized.core;
    
    public class ProducerThread extends Thread{
        Model model;
        int i = 0;
    
        public ProducerThread(Model model) {
            this.model = model;
        }
    
        @Override
        public void run() {
            while (true){
                synchronized (model){
                    if (i % 2 == 0) {
                        model.name = "生产者2号";
                        model.age = 23;
                        //等待...
                    } else {
                        model.name = "生产者1号";
                        model.age = 24;
                    }
    
                    i++;
                }
    
            }
    
        }
    
    }
    

    现在,本人来给出一个消费者线程类

    package edu.youzg.about_synchronized.core;
    
    public class ConsumerThread extends Thread {
        Model model;
    
        public ConsumerThread(Model model) {
            this.model = model;
        }
    
        @Override
        public void run() {
            while (true) {
                    System.out.println(model.name + " === " + model.age);
            }
        }
    
    }
    

    现在,本人再给出一个测试类

    package edu.youzg.about_synchronized.core;
    
    public class Test extends Thread{
        public static void main (String[] args) {
            Model model = new Model();
            ProducerThread producer = new ProducerThread(model);
            ConsumerThread consumer = new ConsumerThread(model);
            producer.start();
            consumer.start();
        }
    
    }
    

    那么,现在,本人来展示下运行结果
    在这里插入图片描述
    上图中我们能发现:即使我们用了锁,还是出现了线程安全问题

    那么,为什么我们明明都给代码块上了锁,还是出现了线程安全问题呢?
    这是因为,我们生产者每次进行的操作都是分两步的,而生产者自己能进自己的锁内,最后导致生产者所生产的信息是错误的。

    那么,我们该如何解决呢?
    本人现在来介绍两个方法:

    Object 类中

    • void wait ():
      在其他线程调用此对象的 notify () 方法或 notifyAll () 方法前,导致当前线程等待
    • void wait (long timeout):
      在其他线程调用此对象的 notify () 方法或 notifyAll () 方法,
      或者超过指定的时间量前,导致当前线程等待
    • void notify ():
      唤醒在此对象监视器上等待的单个线程
    • void notifyAll ():
      唤醒在此对象监视器上等待的所有线程

    那么,本人现在来对上述的四个类做下改变,来解决上述的问题:

    首先是用于存储数据的Model类:

    package edu.youzg.about_synchronized.core;
    
    public class Model {
        String name;
        int age;
        boolean flag;
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        public int getAge() {
            return age;
        }
    
        public void setAge(int age) {
            this.age = age;
        }
    }
    

    然后是生产者线程类

    package edu.youzg.about_synchronized.core;
    
    public class ProducerThread extends Thread{
        Model model;
        int i = 0;
    
        public ProducerThread(Model model) {
            this.model = model;
        }
    
        @Override
        public void run() {
            while (true){
                synchronized (model){
                    //作为生产者来说:有了资源,等着,通知消费线程来消费
                    if(model.flag){
                        try {
                            model.wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
    
                    if (i % 2 == 0) {
                        model.name = "生产者2号";
                        model.age = 23;
                        //等待...
                    } else {
                        model.name = "生产者1号";
                        model.age = 24;
                    }
    
                    //通知消费线程,去消费
                    model.flag = true; //修改标记
                    model.notify();//唤醒等待的线程,唤醒之后,多个线程还要再次争抢时间片
                    i++;
                }
    
            }
    
        }
    
    }
    

    接下来是消费者线程类

    package edu.youzg.about_synchronized.core;
    
    public class ConsumerThread extends Thread {
        Model model;
    
        public ConsumerThread(Model model) {
            this.model = model;
        }
    
        @Override
        public void run() {
            while (true) {
                synchronized (model) {
                    if (!model.flag) {
                        try {
                            model.wait();//没有资源等待,wait()方法一旦等待,就必须释放锁,从哪里等待,被唤醒后,就从这里醒来
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                    //消费了资源,通知生产线程去生产
                    System.out.println(model.name + " === " + model.age);
                    model.flag = false;
                    model.notify(); //唤醒等待的 线程
                }
            }
        }
    
    }
    

    现在,本人再来展示下测试类

    package edu.youzg.about_synchronized.core;
    
    public class Test extends Thread{
    
        public static void main (String[] args) {
            Model model = new Model();
            ProducerThread producer = new ProducerThread(model);
            ConsumerThread consumer = new ConsumerThread(model);
            producer.start();
            consumer.start();
        }
    
    }
    

    那么,本人来展示下运行中的某个结果片段:
    在这里插入图片描述


    (本人 《详解 线程》 博文 链接:https:////www.cnblogs.com/codderYouzg/p/12418954.html
    (本人 《详解 多线程》 博文 链接:https:////www.cnblogs.com/codderYouzg/p/12418935.html

  • 相关阅读:
    C语言的存储类别和动态内存分配
    C语言中复杂的声明
    C语言中typedef的解释_2
    C语言中类型限定符
    C语言文件I/O和标准I/O函数
    C语言中存储类别、链接与内存管理
    C++中static与const成员
    C++多态、虚函数、纯虚函数、抽象类
    sizeof结构体
    杂类
  • 原文地址:https://www.cnblogs.com/codderYouzg/p/12418970.html
Copyright © 2011-2022 走看看