详解 锁

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

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


@


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


同步代码块:

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

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

格式

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

posted @ 2020-03-05 10:54  在下右转,有何贵干  阅读(255)  评论(0编辑  收藏  举报