zoukankan      html  css  js  c++  java
  • JUC之Lock接口以及Synchronized回顾

    Lock接口

    Synchronized关键字回顾:

    多线程编程步骤(上):

    1. 创建资源类,在资源类创建属性和操作方法
    2. 创建多个线程,调用资源类的操作方法

    创建线程的四种方式:

    1. 继承Thread
    2. 实现Runnable接口
    3. 使用Callable接口
    4. 使用线程池

    使用synchronized同步实现售票问题:

    只有当资源是空闲的时候,线程才能访问。

     /**
      * 创建资源
      */
     class ticket{
         private  int number = 30;
         public synchronized void sell(){
             if(number >0){
                 System.out.println(Thread.currentThread().getName()+":卖出"+number--+"剩余:"+number);
             }
     ​
         }
     }
     ​
     /**
      * 创建多个线程,调用资源类的操作方法
      */
     public class sellTicket {
         public static void main(String[] args) {
             /**
              * 创建资源
              */
             ticket ticket = new ticket();
             /**
              * 创建三个线程(售票员)
              */
             new Thread(new Runnable() {
                 public void run() {
                     for (int i = 0; i < 40; i++) {
                         ticket.sell();
                     }
                 }
             }, "aaa").start();
             new Thread(new Runnable() {
                 public void run() {
                     for (int i = 0; i < 40; i++) {
                         ticket.sell();
                     }
                 }
             }, "bbb").start();
             new Thread(new Runnable() {
                 public void run() {
                     for (int i = 0; i < 40; i++) {
                         ticket.sell();
                     }
                 }
             }, "ccc").start();
         }
     }
     ​
    

    学习《Java并发编程的艺术》一节是synchronized的实现原理与应用

    Java中的每一个对象都可以作为锁:

    1. 对于同步方法,锁的是当前的对象。
    2. 对于静态方法,锁的是当前类的class对象。
    3. 对于静态代码块,Synchonized括号里配置的对象。

    当一个线程试图访问同步代码块时,它首先必须得到锁,退出或抛出异常时必须释放锁。

    那么锁到底存在哪里呢?锁里面会存储什么信息呢?

    这个问题是该书中的一个问题,对个人来说有点抽象,主要点是:JVM基于进入和退出Monitor(监视器)对象来实现方法同步和代码块同步,但两者的实现细节不一样。

    代码块同步是使用monitorenter 和monitorexit指令实现的,而方法同步是使用另外一种方式实现的,细节在JVM规范里并没有 详细说明。但是,方法的同步同样可以使用这两个指令来实现。

    monitorenter指令是在编译后插入到同步代码块的开始位置,而monitorexit是插入到方法结 束处和异常处,JVM要保证每个monitorenter必须有对应的monitorexit与之配对。任何对象都有 一个monitor与之关联,当且一个monitor被持有后,它将处于锁定状态。线程执行到monitorenter 指令时,将会尝试获取对象所对应的monitor的所有权,即尝试获得对象的锁。

    其中monitorenter主要是获取监视器锁,monitorexit主要是释放监视器锁。

    synchonized中的一个概念是Java对象头:

    synchronized用的锁是存放在Java对象头里面的。

    非数组类型 数组类型
    虚拟机 3个字宽 2个字宽

    需要注意的是:在32位虚拟机中:1字宽=4字节,即:32bit

    Java对象头里的Mark Word里面默认存储对象HashCode、分代年龄和锁标记位。

    在运行期间,Mark Word里面存储的数据会随着锁标志位的变化而变化。

    32位JVM的Mark Word的默认存储结构与在64位JVM不相同

    Lock接口并创建实例:

    Java并发编程的艺术

    在JavaSE5之前使用的是synchronized关键字实现锁功能,5v之后并发包中新增Lock接口以及相关实现类用来实现锁功能,提供与synchronized类似的同步关系,但是比其更具有灵活性和扩展性(拥有了锁获取与释放的可操作性、可中断的获取锁以及超时获取锁)。

    查看Lock的文档得知:

    使用 lock 块来调用 try,在之前/之后的构造中,最典型的代码如下:

      class X {
        private final ReentrantLock lock = new ReentrantLock();
        // ...
     ​
        public void m() { 
          lock.lock();  // block until condition holds
          try {
            // ... method body
          } finally {
            lock.unlock()
          }
        }
      }
      
    

    根据上述的代码,实现买票代码:

     import java.util.concurrent.locks.ReentrantLock;
     class ticket2{
         private int number = 30;
     ​
         //创建一个重入锁
         private ReentrantLock lock = new ReentrantLock();
     ​
         public void sale(){
             //在内容之前加锁
             lock.lock();
             if(number >0){
                 System.out.println(Thread.currentThread().getName()+":卖出"+number--+"剩余:"+number);
             }
             //在内容之后解锁
             lock.unlock();
         }
     }
     ​
     public class lockCase {
         public static void main(String[] args) {
             ticket2 tk = new ticket2();
             //创建线程
             new Thread(()->{
                 for (int i = 0; i < 40; i++) {
                     tk.sale();
                 }
             },"aaa").start();
             new Thread(()->{
                 for (int i = 0; i < 40; i++) {
                     tk.sale();
                 }
             },"bbb").start();
             new Thread(()->{
                 for (int i = 0; i < 40; i++) {
                     tk.sale();
                 }
             },"bbb").start();
         }
     }
    

    上述代码中,在ticket2类中存在一个问题就是,当lock中间内容如果出现报错,那么后面的代码无法执行,也就是锁无法释放。所以我们需要将unlock()放到finally中。

    目的是保证在获取到锁之后,最终能够被释放。

     public void sale(){
             //在内容之前加锁
             lock.lock();
             try{
                 if(number >0){
                     System.out.println(Thread.currentThread().getName()+":卖出"+number--+"剩余:"+number);
                 }
                 //在内容之后解锁
             }finally{
                 lock.unlock();
             }
     ​
         }
    
  • 相关阅读:
    查看git submodule更改
    /var/lib/docker空间占用过大迁移
    docker -修改容器
    docker重命名镜像repository和tag
    方法的重写、重载。
    方法的声明与使用。
    二维数组。
    标准输入输出流概述和输出语句。
    冒泡排序法。
    IO流,对象操作流优化。
  • 原文地址:https://www.cnblogs.com/xbhog/p/15703110.html
Copyright © 2011-2022 走看看