精华内容
下载资源
问答
  • SELECT Min(id),Min(date) FROM a GROUP by (UNIX_TIMESTAMP(date) DIV 120)该语句是将时间分为每2分钟为一个区间,并查询每区间的最小id及date

    SELECT Min(id),Min(date) FROM a GROUP by (UNIX_TIMESTAMP(date) DIV 120)

    该语句是指将时间分为每2分钟为一个区间,并查询每个区间的最小id及date

    展开全文
  • 时间戳转换为自定义时间显示格式,时间戳转换为分钟前、几小时前 结论先行 自定义时间显示格式 转换为分钟前、几小时前 实现方法 明确两概念: 时间戳:时间戳是格林威治时间1970年01月...

    时间戳转换为自定义时间显示格式,时间戳转换为几分钟前、几小时前

    结论先行

    • 自定义时间显示格式
      在这里插入图片描述
    • 转换为几分钟前、几小时前
      在这里插入图片描述

    实现方法

    在这里插入图片描述

    明确两个概念:

    • 时间戳:时间戳是指格林威治时间1970年01月01日00时00分00秒(北京时间1970年01月01日08时00分00秒)起至现在的总秒数。
    • 时间毫秒:或者叫毫秒时间戳,反正就知道是时间戳*1000。也就是毫秒数
    // util.js
    /* 可能自带的(我们这里用不上),Date格式转换为yyyy/MM/dd hh:mm:ss,输出格式为2019/04/11 21:19:30 */
    const formatTime = date => {
      const year = date.getFullYear()
      const month = date.getMonth() + 1
      const day = date.getDate()
      const hour = date.getHours()
      const minute = date.getMinutes()
      const second = date.getSeconds()
    
      return [year, month, day].map(formatNumber).join('/') + ' ' + [hour, minute, second].map(formatNumber).join(':')
    }
    
    /**
    * 时间戳转化为年 月 日 时 分 秒
    * number: 传入时间戳
    * format:返回格式,支持自定义,但参数必须与formateArr里保持一致
    */
    function formatTime2(number, format) {
    
      var formateArr = ['Y', 'M', 'D', 'h', 'm', 's'];
      var returnArr = [];
    
      var date = new Date(number * 1000);
      returnArr.push(date.getFullYear());
      returnArr.push(formatNumber(date.getMonth() + 1));
      returnArr.push(formatNumber(date.getDate()));
    
      returnArr.push(formatNumber(date.getHours()));
      returnArr.push(formatNumber(date.getMinutes()));
      returnArr.push(formatNumber(date.getSeconds()));
    
      for (var i in returnArr) {
        format = format.replace(formateArr[i], returnArr[i]);
      }
      return format;
    }
    
    //数据转化
    const formatNumber = n => {
      n = n.toString()
      return n[1] ? n : '0' + n
    }
    
    
    //将时间戳转换为几分钟前、几小时前
    function timeago(dateTimeStamp, format) {	//这里融合了上面的自定义时间格式,“format”就是干这个用的
      // dateTimeStamp是一个时间毫秒,注意时间戳是秒的形式,在这个毫秒的基础上除以1000,就是十位数的时间戳。13位数的都是时间毫秒。
      var minute = 1000 * 60;      //把分,时,天,周,半个月,一个月用毫秒表示
      var hour = minute * 60;
      var day = hour * 24;
      var week = day * 7;
      var halfamonth = day * 15;
      var month = day * 30;
    
      var now = new Date().getTime();   //获取当前时间毫秒
      var diffValue = now - dateTimeStamp;//时间差
    
      if (diffValue < 0) { return; }
    
      var minC = diffValue / minute;  //计算时间差的分,时,天,周,月
      var hourC = diffValue / hour;
      var dayC = diffValue / day;
      var weekC = diffValue / week;
      var monthC = diffValue / month;
      var result = '';
      
      // if (monthC >= 1) {
      //   result = "" + parseInt(monthC) + "月前";
      // } else if (weekC >= 1) {
      //   result = "" + parseInt(weekC) + "周前";
      // } else 
      if (dayC >= 1 && dayC <= 3) {
        result = "" + parseInt(dayC) + "天前";
      } else if (hourC >= 1 && hourC <= 24) {
        result = "" + parseInt(hourC) + "小时前";
      } else if (minC >= 1 && minC <= 60) {
        result = "" + parseInt(minC) + "分钟前";
      } else if (minC < 1) {
        result = "刚刚";
      } else
        result = formatTime2(new Date(dateTimeStamp) / 1000, format)		//否则输出“format”(自定义格式)的时间
      return result;
    }
    
    module.exports = {
      formatTime: formatTime,
      formatTime2: formatTime2,
      timeago: timeago,
    }
    

    调用

    console.log('时间毫秒 ' + new Date().getTime())
    console.log('自定义格式 ' + util.formatTime2(new Date().getTime() / 1000, 'Y年M月D日 h:m:s'))
    console.log('几分钟前 ' + util.timeago(new Date().getTime(), 'Y年M月D日 h:m:s'))
    

    在这里插入图片描述

    参考

    自定义日期格式
    几分钟前、几小时前

    展开全文
  • 个synchronized跟面试官扯了半个小时

    万次阅读 多人点赞 2020-03-27 14:50:59
    个HashMap跟面试官扯了半个小时》 前言 ​ 话说上回HashMap跟面试官扯了半个小时之后,二面迎来了没有削弱前的钟馗,法师的钩子让安琪拉有点绝望。钟馗穿着有些微微泛黄的格子道袍,站在安琪拉对面,开始发难,...

    《安琪拉与面试官二三事》系列文章
    一个HashMap能跟面试官扯上半个小时
    一个synchronized跟面试官扯了半个小时

    《安琪拉教鲁班学算法》系列文章

    安琪拉教鲁班放技能之动态规划

    前言

    ​ 话说上回HashMap跟面试官扯了半个小时之后,二面迎来了没有削弱前的钟馗,法师的钩子让安琪拉有点绝望。钟馗穿着有些微微泛黄的格子道袍,站在安琪拉对面,开始发难,其中让安琪拉印象非常深刻的是法师的synchronized 钩子。

    开场

    面试官: 你先自我介绍一下吧!

    安琪拉: 我是安琪拉,草丛三婊之一,最强中单(钟馗冷哼)!哦,不对,串场了,我是**,目前在–公司做–系统开发。

    面试官: 刚才听一面的同事说你们上次聊到了synchronized,你借口说要回去补篮,现在能跟我讲讲了吧?

    安琪拉: 【上来就丢钩子,都不寒暄几句,问我吃没吃】嗯嗯,是有聊到 synchronized。

    面试官: 那你跟我说说为什么会需要synchronized?什么场景下使用synchronized?

    安琪拉: 这个就要说到多线程访问共享资源了,当一个资源有可能被多个线程同时访问并修改的话,需要用到,还是画个图给您看一下,请看👇图:

    在这里插入图片描述

    安琪拉: 如上图所示,比如在王者荣耀程序中,我们队有二个线程分别统计后裔和安琪拉的经济,A线程从内存中read 当前队伍总经济加载到线程的本地栈,进行 +100 操作之后,这时候B线程也从内存中取出经济值 + 200,将200写回内存,B线程刚执行完,后脚A线程将100 写回到内存中,就出问题了,我们队的经济应该是300, 但是内存中存的却是100,你说糟不糟心。

    面试官: 那你跟我讲讲用 synchronized 怎么解决这个问题的?

    安琪拉: 在访问竞态资源时加锁,因为多个线程会修改经济值,因此经济值就是竞态资源,给您show 一下吧?下图是不加锁的代码以及控制台的输出,请您过目:

    二个线程,A线程让队伍经济 +1 ,B线程让经济 + 2,分别执行一千次,正确的结果应该是3000,结果得到的却是 2845。

    在这里插入图片描述

    安琪拉: 👇这个就是加锁之后的代码和控制台的输出。

    (img-6NwdhDEz-1585279691724)(/Users/zw/Library/Application Support/typora-user-images/image-20200321210555529.png)]

    面试官: 我看你👆用synchronized 锁住的是代码块,synchronized 还有别的作用范围吗?

    安琪拉: 嗯嗯,synchronized 有以下三种作用范围:

    1. 在静态方法上加锁;

    2. 在非静态方法上加锁;

    3. 在代码块上加锁;

      示例代码如下

      public class SynchronizedSample {
      
          private final Object lock = new Object();
      
          private static int money = 0;
      		//非静态方法
          public synchronized void noStaticMethod(){
              money++;
          }
      		//静态方法
          public static synchronized void staticMethod(){
              money++;
          }
      		
          public void codeBlock(){
            	//代码块
              synchronized (lock){
                  money++;
              }
          }
      }
      

    面试官: 那你了解 synchronized 这三种作用范围的加锁方式的区别吗?

    安琪拉: 了解。首先要明确一点:锁是加在对象上面的,我们是在对象上加锁。

    重要事情说三遍:在对象上加锁 ✖️ 3 (这也是为什么wait / notify 需要在锁定对象后执行,只有先拿到锁才能释放锁)

    这三种作用范围的区别实际是被加锁的对象的区别,请看下表:

    作用范围锁对象
    非静态方法当前对象 => this
    静态方法类对象 => SynchronizedSample.class (一切皆对象,这个是类对象)
    代码块指定对象 => lock (以上面的代码为例)

    面试官: 那你清楚 JVM 是怎么通过synchronized 在对象上实现加锁,保证多线程访问竞态资源安全的吗?

    安琪拉: 【天啦撸, 该来的还是要来】(⊙o⊙)…额,这个说起来有点复杂,我怕时间不够,要不下次再约?

    面试官: 别下次了,今天我有的是时间,你慢慢讲,我慢慢👂你说。

    安琪拉: 那要跟您好好说道了。分二个时间段来跟您讨论,先说到盘古开天辟地,女娲造石补天,咳咳,不好意思扯远了。。。。。。

    1. 先说在JDK6 以前,synchronized 那时还属于重量级锁,相当于关二爷手中的青龙偃月刀,每次加锁都依赖操作系统Mutex Lock实现,涉及到操作系统让线程从用户态切换到内核态,切换成本很高;
    2. 到了JDK6,研究人员引入了偏向锁和轻量级锁,因为Sun 程序员发现大部分程序大多数时间都不会发生多个线程同时访问竞态资源的情况,每次线程都加锁解锁,每次这么搞都要操作系统在用户态和内核态之间来回切,太耗性能了。

    面试官: 那你分别跟我讲讲JDK 6 以前 synchronized为什么这么重? JDK6 之后的偏向锁和轻量级锁是怎么回事?

    安琪拉: 好的。首先要了解 synchronized 的实现原理,需要理解二个预备知识:

    1. 第一个预备知识:需要知道 Java 对象头,锁的类型和状态和对象头的Mark Word息息相关;

      synchronized 锁 和 对象头息息相关。我们来看下对象的结构:
      在这里插入图片描述
      对象存储在堆中,主要分为三部分内容,对象头、对象实例数据和对齐填充(数组对象多一个区域:记录数组长度),下面简单说一下三部分内容,虽然 synchronized 只与对象头中的 Mard Word相关。

      1. 对象头:

        对象头分为二个部分,Mard Word 和 Klass Word,👇列出了详细说明:

        对象头结构存储信息-说明
        Mard Word存储对象的hashCode、锁信息或分代年龄或GC标志等信息
        Klass Word存储指向对象所属类(元数据)的指针,JVM通过这个确定这个对象属于哪个类
      2. 对象实例数据:

        如上图所示,类中的 成员变量data 就属于对象实例数据;

      3. 对齐填充:

        JVM要求对象占用的空间必须是8 的倍数,方便内存分配(以字节为最小单位分配),因此这部分就是用于填满不够的空间凑数用的。

    2. 第二个预备知识:需要了解 Monitor ,每个对象都有一个与之关联的Monitor 对象;Monitor对象属性如下所示( Hospot 1.7 代码) 。

      //👇图详细介绍重要变量的作用
      ObjectMonitor() {
          _header       = NULL;
          _count        = 0;   // 重入次数
          _waiters      = 0,   // 等待线程数
          _recursions   = 0;
          _object       = NULL;
          _owner        = NULL;  // 当前持有锁的线程
          _WaitSet      = NULL;  // 调用了 wait 方法的线程被阻塞 放置在这里
          _WaitSetLock  = 0 ;
          _Responsible  = NULL ;
          _succ         = NULL ;
          _cxq          = NULL ;
          FreeNext      = NULL ;
          _EntryList    = NULL ; // 等待锁 处于block的线程 有资格成为候选资源的线程
          _SpinFreq     = 0 ;
          _SpinClock    = 0 ;
          OwnerIsThread = 0 ;
        }
      

      对象关联的 ObjectMonitor 对象有一个线程内部竞争锁的机制,如下图所示:
      在这里插入图片描述

    面试官: 预备的二个知识我大体看了,后面给我讲讲 JDK 6 以前 synchronized具体实现逻辑吧。

    安琪拉: 好的。【开始我的表演】

    1. 当有二个线程A、线程B都要开始给我们队的经济 money变量 + 钱,要进行操作的时候 ,发现方法上加了synchronized锁,这时线程调度到A线程执行,A线程就抢先拿到了锁。拿到锁的步骤为:
      - 1.1 将 MonitorObject 中的 _owner设置成 A线程;
      - 1.2 将 mark word 设置为 Monitor 对象地址,锁标志位改为10;
      - 1.3 将B 线程阻塞放到 ContentionList 队列;

    2. JVM 每次从Waiting Queue 的尾部取出一个线程放到OnDeck作为候选者,但是如果并发比较高,Waiting Queue会被大量线程执行CAS操作,为了降低对尾部元素的竞争,将Waiting Queue 拆分成ContentionList 和 EntryList 二个队列, JVM将一部分线程移到EntryList 作为准备进OnDeck的预备线程。另外说明几点:

      • 所有请求锁的线程首先被放在ContentionList这个竞争队列中;

      • Contention List 中那些有资格成为候选资源的线程被移动到 Entry List 中;

      • 任意时刻,最多只有一个线程正在竞争锁资源,该线程被成为 OnDeck;

      • 当前已经获取到所资源的线程被称为 Owner;

      • 处于 ContentionList、EntryList、WaitSet 中的线程都处于阻塞状态,该阻塞是由操作系统来完成的(Linux 内核下采用 pthread_mutex_lock 内核函数实现的);

    3. 作为Owner 的A 线程执行过程中,可能调用wait 释放锁,这个时候A线程进入 Wait Set , 等待被唤醒。

    以上就是我想说的 synchronized 在 JDK 6之前的实现原理。

    面试官: 那你知道 synchronized 是公平锁还是非公平锁吗?

    安琪拉: 非公平的。主要有以下二点原因:

    • Synchronized 在线程竞争锁时,首先做的不是直接进ContentionList 队列排队,而是尝试自旋获取锁(可能ContentionList 有别的线程在等锁),如果获取不到才进入 ContentionList,这明显对于已经进入队列的线程是不公平的;
    • 另一个不公平的是自旋获取锁的线程还可能直接抢占 OnDeck 线程的锁资源。

    面试官: 你前面说到 JDK 6 之后synchronized 做了优化,跟我讲讲?

    安琪拉: 不要着急! 容我点个治疗,再跟你掰扯掰扯。前面说了锁跟对象头的 Mark Word 密切相关,我们把目光放到对象头的 Mark Word 上, Mark Word 存储结构如下图和源代码注释(以32位JVM为例,后面的讨论都基于32位JVM的背景,64位会特殊说明)。
    Mard Word会在不同的锁状态下,32位指定区域都有不同的含义,这个是为了节省存储空间,用4 字节就表达了完整的状态信息,当然,对象某一时刻只会是下面5 种状态种的某一种。

    在这里插入图片描述
    下面是简化后的 Mark Word
    在这里插入图片描述

    hash: 保存对象的哈希码
    age: 保存对象的分代年龄
    biased_lock: 偏向锁标识位
    lock: 锁状态标识位
    JavaThread*: 保存持有偏向锁的线程ID
    epoch: 保存偏向时间戳
    

    安琪拉: 由于 synchronized 重量级锁有以下二个问题, 因此JDK 6 之后做了改进,引入了偏向锁和轻量级锁:

    • 依赖底层操作系统的 mutex 相关指令实现,加锁解锁需要在用户态和内核态之间切换,性能损耗非常明显。

    • 研究人员发现,大多数对象的加锁和解锁都是在特定的线程中完成。也就是出现线程竞争锁的情况概率比较低。他们做了一个实验,找了一些典型的软件,测试同一个线程加锁解锁的重复率,如下图所示,可以看到重复加锁比例非常高。早期JVM 有 19% 的执行时间浪费在锁上。

    在这里插入图片描述

    Thin locks are a lot cheaper than inflated locks, but their performance suffers from the fact that every compare-and-swap operation must be executed atomically on multi-processor machines, although most objects are locked and unlocked only by one particular thread.

    It was reported that 19% of the total execution time was wasted by thread synchronization in an early version of Java virtual machine。

    面试官: 你跟我讲讲 JDK 6 以来 synchronized 锁状态怎么从无锁状态到偏向锁的吗?

    安琪拉: OK的啦!,我们来看下图对象从无锁到偏向锁转化的过程(JVM -XX:+UseBiasedLocking 开启偏向锁):

    在这里插入图片描述

    1. 首先A 线程访问同步代码块,使用CAS 操作将 Thread ID 放到 Mark Word 当中;
    2. 如果CAS 成功,此时线程A 就获取了锁
    3. 如果线程CAS 失败,证明有别的线程持有锁,例如上图的线程B 来CAS 就失败的,这个时候启动偏向锁撤销 (revoke bias);
    4. 锁撤销流程:
      - 让 A线程在全局安全点阻塞(类似于GC前线程在安全点阻塞)
      - 遍历线程栈,查看是否有被锁对象的锁记录( Lock Record),如果有Lock Record,需要修复锁记录和Markword,使其变成无锁状态。
      - 恢复A线程
      - 将是否为偏向锁状态置为 0 ,开始进行轻量级加锁流程 (后面讲述)
      下图说明了 Mark Word 在这个过程中的转化
      在这里插入图片描述
      面试官: 不错,那你跟我讲讲偏向锁撤销怎么到轻量级锁的? 还有轻量级锁什么时候会变成重量级锁?
      安琪拉: 继续上面的流程,锁撤销之后(偏向锁状态为0),现在无论是A线程还是B线程执行到同步代码块进行加锁,流程如下:
      • 线程在自己的栈桢中创建锁记录 LockRecord。
      • 线程A 将 Mark Word 拷贝到线程栈的 Lock Record中,这个位置叫 displayced hdr,如下图所示:
        图A 无锁 -> 加锁
      • 将锁记录中的Owner指针指向加锁的对象(存放对象地址)。
      • 将锁对象的对象头的MarkWord替换为指向锁记录的指针。这二步如下图所示:
        在这里插入图片描述
    5. 这时锁标志位变成 00 ,表示轻量级锁

    面试官: 看来对synchronized 很有研究嘛。我钟馗不信难不倒你,那轻量级锁什么时候会升级为重量级锁, 请回答?
    安琪拉: 当锁升级为轻量级锁之后,如果依然有新线程过来竞争锁,首先新线程会自旋尝试获取锁,尝试到一定次数(默认10次)依然没有拿到,锁就会升级成重量级锁。
    面试官: 为什么这么设计?
    安琪拉: 一般来说,同步代码块内的代码应该很快就执行结束,这时候线程B 自旋一段时间是很容易拿到锁的,但是如果不巧,没拿到,自旋其实就是死循环,很耗CPU的,因此就直接转成重量级锁咯,这样就不用了线程一直自旋了。
    这就是锁膨胀的过程,下图是Mark Word 和锁状态的转化图
    在这里插入图片描述
    主要👆图我标注出来的,锁当前为可偏向状态,偏向锁状态位置就是1,看到很多网上的文章都写错了,把这里写成只有锁发生偏向才会置为1,一定要注意。
    面试官: 既然偏向锁有撤销,还会膨胀,性能损耗这么大,还需要用他们呢?
    安琪拉: 如果确定竞态资源会被高并发的访问,建议通过-XX:-UseBiasedLocking 参数关闭偏向锁,偏向锁的好处是并发度很低的情况下,同一个线程获取锁不需要内存拷贝的操作,免去了轻量级锁的在线程栈中建Lock Record,拷贝Mark Down的内容,也免了重量级锁的底层操作系统用户态到内核态的切换,因为前面说了,需要使用系统指令。另外Hotspot 也做了另一项优化,基于锁对象的epoch 批量偏向和批量撤销偏向,这样可以大大降低了单次偏向锁的CAS和锁撤销带来的损耗,👇图是研究人员做的压测:
    在这里插入图片描述

    Eliminating Synchronization-Related Atomic Operations with
    Biased Locking and Bulk Rebiasing

    安琪拉: 他们在几款典型软件上做了测试,发现基于epoch 批量撤销偏向锁和批量加偏向锁能大幅提升吞吐量,但是并发量特别大的时候性能就没有什么特别大的提升了。
    面试官:可以可以,那你看过synchronized 底层实现源码没有?
    安琪拉: 那当然啦,源码是我的二技能,高爆发的伤害能不能打出来就看它了,我们一步一步来。
    我们把文章开头的示例代码编译成class 文件,然后通过javap -v SynchronizedSample.class 来看下synchronized 到底在源码层面如何实现的?
    如下图所示:
    在这里插入图片描述
    安琪拉: synchronized 在代码块上是通过 monitorenter 和 monitorexit指令实现,在静态方法和 方法上加锁是在方法的flags 中加入 ACC_SYNCHRONIZED 。JVM 运行方法时检查方法的flags,遇到同步标识开始启动前面的加锁流程,在方法内部遇到monitorenter指令开始加锁。

    monitorenter 指令函数源代码在 InterpreterRuntime::monitorenter

    IRT_ENTRY_NO_ASYNC(void, InterpreterRuntime::monitorenter(JavaThread* thread, BasicObjectLock* elem))
    #ifdef ASSERT
      thread->last_frame().interpreter_frame_verify_monitor(elem);
    #endif
      if (PrintBiasedLockingStatistics) {
        Atomic::inc(BiasedLocking::slow_path_entry_count_addr());
      }
      Handle h_obj(thread, elem->obj());
      assert(Universe::heap()->is_in_reserved_or_null(h_obj()),
             "must be NULL or an object");
     //是否开启了偏向锁
      if (UseBiasedLocking) {
        // 尝试偏向锁
        ObjectSynchronizer::fast_enter(h_obj, elem->lock(), true, CHECK);
      } else {
        // 轻量锁逻辑
        ObjectSynchronizer::slow_enter(h_obj, elem->lock(), CHECK);
      }
      assert(Universe::heap()->is_in_reserved_or_null(elem->obj()),
             "must be NULL or an object");
    #ifdef ASSERT
      thread->last_frame().interpreter_frame_verify_monitor(elem);
    #endif
    IRT_END
    

    偏向锁代码

    // -----------------------------------------------------------------------------
    //  Fast Monitor Enter/Exit
    // This the fast monitor enter. The interpreter and compiler use
    // some assembly copies of this code. Make sure update those code
    // if the following function is changed. The implementation is
    // extremely sensitive to race condition. Be careful.
    
    void ObjectSynchronizer::fast_enter(Handle obj, BasicLock* lock, bool attempt_rebias, TRAPS) {
    //是否使用偏向锁
     if (UseBiasedLocking) {
        // 如果不在全局安全点
        if (!SafepointSynchronize::is_at_safepoint()) {
          // 获取偏向锁
          BiasedLocking::Condition cond = BiasedLocking::revoke_and_rebias(obj, attempt_rebias, THREAD);
          if (cond == BiasedLocking::BIAS_REVOKED_AND_REBIASED) {
            return;
          }
        } else {
          assert(!attempt_rebias, "can not rebias toward VM thread");
          // 在全局安全点,撤销偏向锁
          BiasedLocking::revoke_at_safepoint(obj);
        }
        assert(!obj->mark()->has_bias_pattern(), "biases should be revoked by now");
     }
    // 进轻量级锁流程
     slow_enter (obj, lock, THREAD) ;
    }
    

    偏向锁的实现具体代码在 BiasedLocking::revoke_and_rebias 中,因为函数非常长,就不贴出来,有兴趣的可以在Hotspot 1.8-biasedLocking.cpp去看。
    轻量级锁代码流程

    void ObjectSynchronizer::slow_enter(Handle obj, BasicLock* lock, TRAPS) {
    //获取对象的markOop数据mark
      markOop mark = obj->mark();
      assert(!mark->has_bias_pattern(), "should not see bias pattern here");
    
    //判断mark是否为无锁状态 & 不可偏向(锁标识为01,偏向锁标志位为0) 
      if (mark->is_neutral()) {
        // Anticipate successful CAS -- the ST of the displaced mark must
        // be visible <= the ST performed by the CAS.
        // 保存Mark 到 线程栈 Lock Record 的displaced_header中
        lock->set_displaced_header(mark);
        // CAS 将  Mark Down 更新为 指向 lock 对象的指针,成功则获取到锁  
        if (mark == (markOop) Atomic::cmpxchg_ptr(lock, obj()->mark_addr(), mark)) {
          TEVENT (slow_enter: release stacklock) ;
          return ;
        }
        // Fall through to inflate() ...
      } else
      // 根据对象mark 判断已经有锁  & mark 中指针指的当前线程的Lock Record(当前线程已经获取到了,不必重试获取)
      if (mark->has_locker() && THREAD->is_lock_owned((address)mark->locker())) {
        assert(lock != mark->locker(), "must not re-lock the same lock");
        assert(lock != (BasicLock*)obj->mark(), "don't relock with same BasicLock");
        lock->set_displaced_header(NULL);
        return;
      }
    
     lock->set_displaced_header(markOopDesc::unused_mark());
       // 锁膨胀
      ObjectSynchronizer::inflate(THREAD, obj())->enter(THREAD);
    

    做个假设,现在线程A 和B 同时执行到临界区if (mark->is_neutral()):
    1、线程A和B都把Mark Word复制到各自的_displaced_header字段,该数据保存在线程的栈帧上,是线程私有的;
    2、Atomic::cmpxchg_ptr 属于原子操作,保障了只有一个线程可以把Mark Word中替换成指向自己线程栈 displaced_header中的,假设A线程执行成功,相当于A获取到了锁,开始继续执行同步代码块;
    3、线程B执行失败,退出临界区,通过ObjectSynchronizer::inflate方法开始膨胀锁;

    面试官: synchronized 源码这部分可以了,👂不下去了。你跟我讲讲Java中除了synchronized 还有别的锁吗?

    安琪拉: 还有ReentrantLock也可以实现加锁。

    面试官: 那写段代码实现之前加经济的同样效果。

    安琪拉: coding 如👇图:
    在这里插入图片描述
    面试官: 哦,那你跟我说说ReentrantLock 的底层实现原理?

    安琪拉: 天色已晚,我们能改日再聊吗?

    面试官: 那你回去等通知吧。

    安琪拉: 【内心是崩溃的】,看来这次面试就黄了,😔,心累。
    在这里插入图片描述

    未完,下一篇介绍ReentrantLock相关的底层原理,看安琪拉如何大战钟馗面试官三百回合。

    补充说明:
    在代码中查看对象头信息方法:

    1. maven 添加以下依赖:
      	<dependency>
              <groupId>org.openjdk.jol</groupId>
              <artifactId>jol-core</artifactId>
          </dependency>
      
    2. 示例代码和输出
          public static void main(String[] args) {
      
           Test obj = new Test();
           ClassLayout layout = ClassLayout.parseInstance(obj);
           //打印空对象大小   
           System.out.println(layout.instanceSize());
           System.out.println(layout.toPrintable());
      
           synchronized (obj){
               System.out.println("after lock");
               System.out.println(layout.toPrintable());
           }
           System.out.println("after re-lock");
           System.out.println(layout.toPrintable());
       }
      

    控制台输出如下:
    在这里插入图片描述
    这个是反着的,这里是高地址位表示低位数据,低地址位表示高位数据, 👆可以看出对象后三位是001,0代表不可偏向状态,01代表无锁状态,第二个000,表示轻量级锁状态,最后释放锁,变回01状态无锁状态。

    展开全文
  •  前几天在某论坛看到这样篇帖子,说的是“大家第一个项目,都是从网址导航开始?”,浏览半天回复的内容发现都是大佬啊,有做了浏览器插件的,有做了博客的,这—>“片段”<—更不错,手撸出来的多...

    写在最前面

     项目完整源码(欢迎star):github项目源码
     项目临时预览地址(指不定什么时候就挂了):短网址生成项目预览

    引入主题

     前几天在某论坛看到这样一篇帖子,说的是“大家第一个项目,都是从网址导航开始?”,浏览半天回复的内容发现都是大佬啊,有做了个浏览器插件的,有做了个博客的,这个—>“片段”<—更不错,手撸出来的多厉害。当然也有不少做的网址导航的,于是我想了想之前搞的一个项目,默默的评论了一句。
    my
     翻到上面看到一位老哥说的是短网址项目。
    短网址
     于是我脑瓜一热,百度了下短网址的原理。发现还是很简单的,说时迟那时快,我已经打开了IDEA,创建项目手撸开干。项目架构选择了springboot,持久层还是用的mybatis,前端依然是bootstrap。
    用了不到一个下午的时间项目已经出来了。整个项目中,最浪费时间的还是前端调样式。前端菜的一批。
    短网址

    实现原理

    下面说下实现的原理
     首先在用户输入一条长链接传到后端的时候,我先生成了数字和字母随机组成的6位字符,然后把这个字符和长链接保存到数据库。保存成功后,把这6位字符拼上我的网址,返回给用户,就像是这样的 https://wjup.top/HtN3Gc
     当用户拿到这个短网址访问的时候。我在后台进行获取这个短网址的6个字符,然后根据这个字符到数据库查询原来的链接,再进行301永久重定向到原网址就可以了。整体实现非常简单。当然我还增加了对短网址加密的功能,只有输入正确的密码才能访问原始链接

    逻辑代码

    下面放出主要的实现代码。项目完整版可以到github中去克隆岛到本地研究

    package com.wjup.shorturl.controller;
    
    import com.alibaba.fastjson.JSONObject;
    import com.wjup.shorturl.entity.UrlEntity;
    import com.wjup.shorturl.service.UrlService;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Controller;
    import org.springframework.ui.Model;
    import org.springframework.web.bind.annotation.PathVariable;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.ResponseBody;
    import org.thymeleaf.util.DateUtils;
    
    import javax.servlet.ServletException;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    import java.io.IOException;
    import java.util.Date;
    import java.util.Locale;
    import java.util.Random;
    import java.util.UUID;
    
    /**
     * Create by wjup on 2019/9/29 11:33
     * <p>
     * 短网址生成项目
     */
    
    @Controller
    public class UrlController {
    
        @Autowired
        private UrlService urlService;
    
        @RequestMapping("/")
        public String index() {
            return "index";
        }
    
        /**
         * 创建短链接
         *
         * @param longUrl 原地址
         * @param viewPwd 访问密码
         * @param request 请求
         * @return json
         */
        @RequestMapping("/create")
        @ResponseBody
        public String creatShortUrl(String longUrl, String viewPwd, HttpServletRequest request) {
            JSONObject json = new JSONObject();
            String[] split = longUrl.split("\n|\r");
            StringBuffer msg = new StringBuffer();
    
            for (int i = 0; i < split.length; i++) {
                UrlEntity urlEntity = new UrlEntity();
    
                if (!split[i].contains("https://") && !split[i].contains("http://")) {
                    split[i] = "http://" + split[i];
                }
    
                String shortUrlId = getStringRandom(6);
                urlEntity.setShortUrlId(shortUrlId);
                urlEntity.setUuid(UUID.randomUUID().toString());
                urlEntity.setLongUrl(split[i]);
                urlEntity.setCreateTime(DateUtils.format(new Date(), "yyyy-MM-dd HH-mm-ss", Locale.SIMPLIFIED_CHINESE));
                urlEntity.setViewPwd(viewPwd);
    
                int flag = urlService.createShortUrl(urlEntity);
    
                String toUrl = "/";
                int serverPort = request.getServerPort();
                if (serverPort == 80 || serverPort == 443) {
                    toUrl = request.getScheme() + "://" + request.getServerName();
                } else {
                    toUrl = request.getScheme() + "://" + request.getServerName() + ":" + serverPort;
                }
    
                if (flag > 0) {
                    msg.append(toUrl + "/" + shortUrlId + "<br>");
                }
            }
    
            json.put("shortUrl", msg);
            return json.toJSONString();
        }
    
        /**
         * 访问短链接
         *
         * @param shortUrlId 短网址id
         * @param response   响应
         * @param request    请求
         * @throws ServletException 异常捕获
         * @throws IOException      异常捕获
         */
        @RequestMapping(value = "/{shortUrlId}")
        public void view(@PathVariable("shortUrlId") String shortUrlId, HttpServletResponse response, HttpServletRequest request) throws ServletException, IOException {
    
            UrlEntity urlEntity = urlService.findByShortUrlId(shortUrlId);
            if (urlEntity != null) {
                if (urlEntity.getViewPwd() != null && !"".equals(urlEntity.getViewPwd())) {
                    request.setAttribute("shortUrlId", shortUrlId);
                    request.getRequestDispatcher("/viewPwd").forward(request, response);
                } else {
                    urlService.updateShortUrl(shortUrlId);
                    response.setStatus(HttpServletResponse.SC_MOVED_PERMANENTLY);
                    response.setHeader("Location", urlEntity.getLongUrl());
                }
            } else {
                request.getRequestDispatcher("/noPage").forward(request, response);
            }
        }
    
        /**
         * 没有该请求跳转到指定页面
         *
         * @return page
         */
        @RequestMapping("/noPage")
        public String noPage() {
    
            return "noPage";
        }
    
        /**
         * 有密码打开输入密码页面
         *
         * @return html
         */
        @RequestMapping("/viewPwd")
        public String viewPwd(HttpServletRequest request, Model model) {
            String shortUrlId = request.getAttribute("shortUrlId").toString();
            model.addAttribute("shortUrlId", shortUrlId);
            return "viewPwd";
        }
    
        /**
         * 验证密码是否正确
         *
         * @param viewPwd    密码
         * @param shortUrlId 短址id
         */
        @RequestMapping("/VerifyPwd")
        @ResponseBody
        public String VerifyPwd(String viewPwd, String shortUrlId) {
            UrlEntity urlEntity = urlService.findByPwd(viewPwd, shortUrlId);
    
            JSONObject jsonObject = new JSONObject();
            if (urlEntity != null) {
                urlService.updateShortUrl(shortUrlId);
                jsonObject.put("longUrl", urlEntity.getLongUrl());
                jsonObject.put("flag", true);
            } else {
                jsonObject.put("flag", false);
            }
            return jsonObject.toJSONString();
        }
    
    
        /**
         * 生成随机数字和字母
         *
         * @param length 生成长度
         * @return shortUrlId
         */
        private String getStringRandom(int length) {
    
            String val = "";
            Random random = new Random();
    
            //参数length,表示生成几位随机数
            for (int i = 0; i < length; i++) {
    
                String charOrNum = random.nextInt(2) % 2 == 0 ? "char" : "num";
                //输出字母还是数字
                if ("char".equalsIgnoreCase(charOrNum)) {
                    //输出是大写字母还是小写字母
                    int temp = random.nextInt(2) % 2 == 0 ? 65 : 97;
                    val += (char) (random.nextInt(26) + temp);
                } else if ("num".equalsIgnoreCase(charOrNum)) {
                    val += String.valueOf(random.nextInt(10));
                }
            }
            return val;
        }
    
    }
    
    展开全文
  • 絮叨 img 因为更新文章和视频,丙丙已经半年多的周末没休息了,都是在公司那个工位冲冲冲,一直想找时间出去玩,想着年假一天没用,就请了两天出去玩一下。 这样五一就可以早点回来,准备恢复视频的更新,你在看的...
  • loser的一天

    2013-11-03 16:12:55
    不等,一个小时等什么呀,我刚下楼时在电梯附近发现个像是港式茶餐厅的地方,我们去那吃吧。嗯,好,你带我们去吧。嗯嗯,我们于是去了茶餐厅,哎这才是我比较熟悉的地方。于是等了二十分钟,坐下,点菜,个三杯鸡...
  • 【老罗笔记】小时天才理论

    千次阅读 2014-03-31 12:55:55
    精深练习小时是习得技能回路的必经之路,而激情能激发并保持持久进行精深练习的动力,伯乐在触发和维持激情以及精深练习中都有着非常重要的引导与指导作用。在成长路上遗传作用的影响其实没那么大,而精深练习是...
  • [摘要]高层的住宅层好,这是很多的购房者在买房子的过程中会纠结不已的事情。住高了就觉得眩晕,住低了就觉得太吵闹,高层住宅到底哪些楼层是好呢?高层住宅的楼层选择要考虑到采光、视野等因素。 高层的...
  • MySQL 是最流行的关系型数据库管理系统,在 WEB 应用方面 MySQL 是最好的 RDBMS (Relational Database Management System:关系数据库管理系统) 应用软件之
  • 使用TCP时序图解释BBR拥塞控制算法的几个细节

    万次阅读 多人点赞 2017-05-14 18:26:44
    周六,由于要赶一个月底的Deadline,因此选择了在家VPN加班,大半夜就爬起来跑用例,抓数据...自然也就没有时间写文章和外出耍了...不过利用周日的午夜时间(不要问我为什么可以连续24小时不睡觉,因为我觉得吃饭睡觉...
  • 亲密接触Redis-第一天

    万次阅读 多人点赞 2016-02-03 17:10:24
    从这章开始我们将开始进入到真正的SOA、PAAS、SAAS、互联网的领域,因此每篇我都会加入小段业务的基础知识,让大家在学习技术的同时也可以了解一些业务,这边的业务不是的business logic不是让大家去做业务...
  • 一天之内有多少次时针分针秒针回重合?分别是什么时候,说出计算方法。 学C#前后不到一月 今天去面试第一题目就是这 不评最佳答案,看重处理方式 纯为交流 更多 分享到: 相关主题推荐: 面试 c# 相关...
  • 高可用性的几个级别

    千次阅读 2018-07-11 10:56:58
    高可用性的几个级别大家常说高可用,High Availablility,但是一般说到这个词的时候,具体的什么方案呢?级别:FT (Fault Tolerance) 双击热备通过创建与主实例保持虚拟同步的虚拟机,使应用在服务器发生故障的...
  • 程序员写在猝死的前一天

    千次阅读 多人点赞 2017-11-01 12:39:48
    因为我昨晚做了一梦,梦到一白胡子老头儿,他说我的阳寿已尽,只剩下一天的寿命。怕我有什么心愿未了,他就提前托梦给我,让我在这一天把自己想做的做完,然后他就带我走了。 我心想真是可笑,一要带我去死的...
  • 简而言之,数据挖掘(Data Mining)是有组织有目的地收集数据,通过分析数据使之成为信息,从而在大量数据中寻找潜在规律以形成...本节通过几个数据挖掘实际案例来诠释如何通过数据挖掘解决商业中遇到的问题。下面关
  • 软件测试的几个性能指标

    万次阅读 2016-07-31 22:33:48
    通过对软件测试中性能测试的初步了解,总结软件性能指标中的几个术语:响应时间、并发用户数,吞吐量,性能计数器,TPS,HPS。在使用性能测试工具进行测试时,还会接触到“思考时间(Think Time)”的概念。供以后...
  • 如何用Python投机倒把几天“暴富”

    千次阅读 多人点赞 2018-11-01 17:54:15
    如何用Python投机倒把几天“暴富” 标签: 2018 本文涉及到的技术点有(多图预警!): 1.Packet Capture和Charles抓包; 2.安卓AccessibilityService无障碍服务实现自动下注; 3.Python爬虫模拟下注; 4.Python ...
  • 里面有用visio画的JVM内部结构图,每部分的作用都有详细讲解,希望能有帮助。网址:https://edu.csdn.net/lecturer/board/10494 1.java自动管理堆(heap)和(栈),程序员不能直接的设置堆和栈。 3.操作系统...
  • 一小时学懂阻抗匹配

    万次阅读 多人点赞 2018-04-16 15:47:35
    转载自 https://blog.csdn.net/jamestaosh/article/details/4436203基本概念 信号传输过程中负载阻抗...对电子设备互连来说,例如信号源连放大器,前级连后级,只要后级的输入阻抗大于前级的输出阻抗5-10倍以上...
  • 国外的FLAG是什么?

    万次阅读 2018-09-22 12:36:16
    名字大概是3,4年前开始叫的吧,那时大概是 “Facebook,LinkedIn,Amazon,Google”。 首先这只是咱们中国人自己叫着玩的,而且我也是回国才知道有FLAG这叫法,所以不算正事。主要是这四家在那个年代是最受...
  • Zookeeper的几个应用场景

    万次阅读 2016-09-22 15:07:49
    有这样一个场景:系统中有大约100w的用户,每用户平 均有3邮箱账号,每隔5分钟,每邮箱账需要收取100封邮件,最多3亿份邮件需要下载到服务器中(不含附件和正文)。用20台机器划分计算的压力,从 多不同的网路...
  • Linux是一个多用户多任务的操作系统

    万次阅读 2016-04-24 15:25:01
    多任务是Linux可以同时执行几个任务,它可以在还未执行完个任务时又执行另项任务。  操作系统管理多个用户的请求和多个任务。大多数系统都只有个CPU和个主存,但个系统可能有多个二级存储磁盘和多个...
  • 你这一生还能陪妈妈几天?来看看

    千次阅读 2016-05-08 10:48:40
    祝天下妈妈母亲节快乐~~你究竟还能陪妈妈几天?看了文章的人都找到了答案
  • 导出和评价供选择的解法 分析员应该从系统逻辑模型出发,研究问题的几个组成部分,细化各功能点,导出若干个较高层次的物理解法供比较和选择 推荐行动方针 草拟开发计划 任务分解 进度规划 财务预算 风险分析及对策...
  • 元宵节要到了,给大家猜几个谜语

    万次阅读 2020-02-07 16:07:32
    1.一直和党心连心(打一字) 忠 2、 永久太平(打中国地名) 长宁 3、 贸易协定(打数学名词) 交换律 4、 直上重霄九(打成语) 平步青云 5、 环境幽静,生活安逸。...9、 拍一个巴掌(打中国地名)...
  • 我是一个黑客

    千次阅读 2004-08-16 19:39:00
    呵呵,对个整天15个小时以上坐在显示器面前,距离不超过30厘米的人来说。那个CRT显示器绝对是个祸害!眼睛红仲,布满血丝。头发脱落,食欲不振,出门还特别怕光,这些都和那个CRT显示器绝对脱不了关系。 分析这...
  • java设置job时间 例:每小时一

    千次阅读 2019-05-29 15:24:01
    每天每小时(整点)执行次:0 0 0/1 * * ? 例1:每隔5秒执行次:*/5 * * * * ? 例2:每隔5分执行次:0 */5 * * * ? 在26分、29分、33分执行次:0 26,29,33 * * * ? 例3:每天半夜12点30分执行次:0 30...
  • 如以当前时间为基准,自己某个时间在微博上发表个动态,发表时间提示有多种显示,如刚刚、几分钟前、几个小时前、昨天、前天、日期等等。自己之前做过类似的时间换算,虽然不是最优,但最终效果还是达到了,下面...
  • 突然有一天,我老无所依

    千次阅读 2012-07-19 01:56:55
    博主:感人至深。字里行间看着,慢慢就想起了妈妈。 转自:http://blog.sina.com.cn/s/blog_3dc01172010145q7.html?tj=1 ... 两长时间登录的QQ账号藏在电脑屏幕的两侧,左是儿子,右是母亲。
  • 正常的计算机处理4g数据需要4分钟的时间,处理1TB需要3个小时的时间,而达到1PB的数据需要4个月零3的时间,起始计量单位只有达到PB的数据才可以被称之为大数据。 沃尔玛是当今最早开始投资和部署大数据应用的传统...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 98,553
精华内容 39,421
关键字:

一天指的是几个小时