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();
             }
     ​
         }
    
  • 相关阅读:
    Azkaban的使用
    Azkaban安装
    Kafka 启动失败,报错Corrupt index found以及org.apache.kafka.common.protocol.types.SchemaException: Error reading field 'version': java.nio.BufferUnderflowException
    Kafka 消费者设置分区策略及原理
    Kafka利用Java API自定义生产者,消费者,拦截器,分区器等组件
    zookeeper群起总是有那么几个节点起不来的问题解决
    flume 启动agent报No appenders could be found for logger的解决
    Flume 的监控方式
    Flume 自定义 组件
    Source r1 has been removed due to an error during configuration java.lang.IllegalArgumentException: Required parameter bind must exist and may not be null & 端口无法连接
  • 原文地址:https://www.cnblogs.com/xbhog/p/15703110.html
Copyright © 2011-2022 走看看