• [java] Java有了synchronized,为什么还要提供Lock
    本文分享自华为云社区《[Java中提供了synchronized,为什么还要提供Lock呢?](https://bbs.huaweicloud.com/blogs/336889?utm_source=zhihu&utm_medium=bbs-ex&utm_campaign=other&utm_content=content)》,作者: 冰 河。 在Java中提供了synchronized关键字来保证只有一个线程能够访问同步代码块。既然已经提供了synchronized关键字,那为何在Java的SDK包中,还会提供Lock接口呢?这是不是重复造轮子,多此一举呢?今天,我们就一起来探讨下这个问题。 # 再造轮子? 既然JVM中提供了synchronized关键字来保证只有一个线程能够访问同步代码块,为何还要提供Lock接口呢?这是在重复造轮子吗?Java的设计者们为何要这样做呢?让我们一起带着疑问往下看。 # 为何提供Lock接口? 很多小伙伴可能会听说过,在Java 1.5版本中,synchronized的性能不如Lock,但在Java 1.6版本之后,synchronized做了很多优化,性能提升了不少。那既然synchronized关键字的性能已经提升了,那为何还要使用Lock呢? 如果我们向更深层次思考的话,就不难想到了:我们使用synchronized加锁是无法主动释放锁的,这就会涉及到死锁的问题。 # 死锁问题 如果要发生死锁,则必须存在以下四个必要条件,四者缺一不可。 ![image.png](https://bbs-img.huaweicloud.com/data/forums/attachment/forum/20223/18/1647586293676911967.png) - **互斥条件** 在一段时间内某资源仅为一个线程所占有。此时若有其他线程请求该资源,则请求线程只能等待。 - **不可剥夺条件** 线程所获得的资源在未使用完毕之前,不能被其他线程强行夺走,即只能由获得该资源的线程自己来释放(只能是主动释放)。 - **请求与保持条件** 线程已经保持了至少一个资源,但又提出了新的资源请求,而该资源已被其他线程占有,此时请求线程被阻塞,但对自己已获得的资源保持不放。 - **循环等待条件** 在发生死锁时必然存在一个进程等待队列{P1,P2,…,Pn},其中P1等待P2占有的资源,P2等待P3占有的资源,…,Pn等待P1占有的资源,形成一个进程等待环路,环路中每一个进程所占有的资源同时被另一个申请,也就是前一个进程占有后一个进程所深情地资源。 # synchronized的局限性 如果我们的程序使用synchronized关键字发生了死锁时,synchronized关键是是无法破坏“不可剥夺”这个死锁的条件的。这是因为synchronized申请资源的时候, 如果申请不到, 线程直接进入阻塞状态了, 而线程进入阻塞状态, 啥都干不了, 也释放不了线程已经占有的资源。 然而,在大部分场景下,我们都是希望“不可剥夺”这个条件能够被破坏。也就是说对于“不可剥夺”这个条件,占用部分资源的线程进一步申请其他资源时, 如果申请不到, 可以主动释放它占有的资源, 这样不可剥夺这个条件就破坏掉了。 如果我们自己重新设计锁来解决synchronized的问题,我们该如何设计呢? # 解决问题 了解了synchronized的局限性之后,如果是让我们自己实现一把同步锁,我们该如何设计呢?也就是说,我们在设计锁的时候,要如何解决synchronized的局限性问题呢?这里,我觉得可以从三个方面来思考这个问题。 ![image.png](https://bbs-img.huaweicloud.com/data/forums/attachment/forum/20223/18/1647586369240495968.png) (1)能够响应中断。 synchronized的问题是, 持有锁A后, 如果尝试获取锁B失败, 那么线程就进入阻塞状态, 一旦发生死锁, 就没有任何机会来唤醒阻塞的线程。 但如果阻塞状态的线程能够响应中断信号, 也就是说当我们给阻塞的线程发送中断信号的时候, 能够唤醒它, 那它就有机会释放曾经持有的锁A。 这样就破坏了不可剥夺条件了。 (2)支持超时。 如果线程在一段时间之内没有获取到锁, 不是进入阻塞状态, 而是返回一个错误, 那这个线程也有机会释放曾经持有的锁。 这样也能破坏不可剥夺条件。 (3)非阻塞地获取锁。 如果尝试获取锁失败, 并不进入阻塞状态, 而是直接返回, 那这个线程也有机会释放曾经持有的锁。 这样也能破坏不可剥夺条件。 体现在Lock接口上,就是Lock接口提供的三个方法,如下所示。 // 支持中断的API void lockInterruptibly() throws InterruptedException; // 支持超时的API boolean tryLock(long time, TimeUnit unit) throws InterruptedException; // 支持非阻塞获取锁的API boolean tryLock(); - lockInterruptibly() 支持中断。 - tryLock()方法 tryLock()方法是有返回值的,它表示用来尝试获取锁,如果获取成功,则返回true,如果获取失败(即锁已被其他线程获取),则返回false,也就说这个方法无论如何都会立即返回。在拿不到锁时不会一直在那等待。 - tryLock(long time, TimeUnit unit)方法 tryLock(long time, TimeUnit unit)方法和tryLock()方法是类似的,只不过区别在于这个方法在拿不到锁时会等待一定的时间,在时间期限之内如果还拿不到锁,就返回false。如果一开始拿到锁或者在等待期间内拿到了锁,则返回true。 也就是说,对于死锁问题,Lock能够破坏不可剥夺的条件,例如,我们下面的程序代码就破坏了死锁的不可剥夺的条件。 public class TansferAccount{ private Lock thisLock = new ReentrantLock(); private Lock targetLock = new ReentrantLock(); //账户的余额 private Integer balance; //转账操作 public void transfer(TansferAccount target, Integer transferMoney){ boolean isThisLock = thisLock.tryLock(); if(isThisLock){ try{ boolean isTargetLock = targetLock.tryLock(); if(isTargetLock){ try{ if(this.balance >= transferMoney){ this.balance -= transferMoney; target.balance += transferMoney; } }finally{ targetLock.unlock } } }finally{ thisLock.unlock(); } } } } 例外,Lock下面有一个ReentrantLock,而ReentrantLock支持公平锁和非公平锁。 在使用ReentrantLock的时候, ReentrantLock中有两个构造函数, 一个是无参构造函数, 一个是传入fair参数的构造函数。 fair参数代表的是锁的公平策略, 如果传入true就表示需要构造一个公平锁, 反之则表示要构造一个非公平锁。如下代码片段所示。 //无参构造函数: 默认非公平锁 public ReentrantLock() { sync = new NonfairSync(); } //根据公平策略参数创建锁 public ReentrantLock(boolean fair){ sync = fair ? new FairSync() : new NonfairSync(); } 锁的实现在本质上都对应着一个入口等待队列, 如果一个线程没有获得锁, 就会进入等待队列, 当有线程释放锁的时候, 就需要从等待队列中唤醒一个等待的线程。 如果是公平锁, 唤醒的策略就是谁等待的时间长, 就唤醒谁, 很公平; 如果是非公平锁, 则不提供这个公平保证, 有可能等待时间短的线程反而先被唤醒。 而Lock是支持公平锁的,synchronized不支持公平锁。 最后,值得注意的是,在使用Lock加锁时,一定要在finally{}代码块中释放锁,例如,下面的代码片段所示。 try{ lock.lock(); }finally{ lock.unlock(); } 注:其他synchronized和Lock的详细说明,小伙伴们自行查阅即可。
  • [区域初赛赛题问题] 【Java语言】本地build_and_run运行没有问题,提交之后显示选手程序运行失败
    【功能模块】本地build_and_run运行没有问题,提交之后显示选手程序运行失败希望能查一下什么原因。。。团队吾辈当自强,最近一次提交时间:2022-03-17 21:06:10【操作步骤&问题现象】1、Java语言,本地build_and_run运行没有问题2、提交显示选手程序运行失败【截图信息】【日志信息】(可选,上传日志内容或者附件)
  • [区域初赛赛题问题] java提交运行失败
    【功能模块】本地提交没问题,提交后一直显示 运行失败【操作步骤&问题现象】1、2、【截图信息】【日志信息】(可选,上传日志内容或者附件)CodeCraft-2022.zip00查看问题2022-03-17 11:31:58
  • [区域初赛赛题问题] java多次提示打开选手文件失败
    5. 打开选手输出文件失败未输出结果文件、输出文件位置不正确或者输出文件中包含中文等导致的编解码失败。在本地能正常输出文件,也无中文字符,怀疑是输出位置有误,应该输出到那个目录呢。
  • [区域初赛赛题问题] java语言提交显示程序编译异常
    请问Java提交压缩包需要提交哪些内容
  • [区域初赛赛题问题] java本地编译运行正确,上传程序运行失败,希望能查一下后台报错
    【功能模块】java在本地虚拟机上(系统为ubuntu18.04)编译运行没问题,上传后提示选手程序运行失败【操作步骤&问题现象】【截图信息】【日志信息】(可选,上传日志内容或者附件)
  • [区域初赛赛题问题] JAVA提交的目录 和 文件路径 一直有问题怎么办 有大佬出教程看一下怎么操作吗
    【功能模块】【操作步骤&问题现象】1、2、【截图信息】【日志信息】(可选,上传日志内容或者附件)
  • [区域初赛赛题问题] 【Javay语言】【本地build_and_run运行没有问题,可以打印出结果,上传代码显示选手程序运行失败】
    【功能模块】使用Java语言,本地build_and_run以及zip.sh运行均没有问题,提交之后显示选手程序运行失败,希望后台能够帮忙查找一下原因,十分感谢!【操作步骤&问题现象】使用Java语言,本地build_and_run以及zip.sh运行均没有问题,提交之后显示选手程序运行失败,希望后台能够帮忙查找一下原因,十分感谢!【截图信息】【日志信息】(可选,上传日志内容或者附件)
  • [区域初赛赛题问题] java编译完之后无法运行。错误:找不到或无法加载主类
  • [加速器] 【案例分享】JAVA如何使用KAE
    使用java的JNI包调用c语言库实现,例如:编写java:package JNI;public class sm3{static{ System.load("/home/minqian/java/sm3.so");    //使用绝对路径加载名称为sm3.so的库文件}public native static void sm3();   //native关键字public static void main(String[] args){   sm3 test = new sm3();   test.sm3();}}编译java文件为class: javac -d . sm3.java根据class文件生成.h文件:javah -classpath . -jni JNI.sm3编写c文件,实现引用第3步中生成的.h头文件,并实现其中声明的方法:#include "JNI_sm3.h"#include <openssl/bio.h>#include <openssl/ssl.h>#include <openssl/err.h>#include <openssl/engine.h>JNIEXPORT void JNICALL Java_JNI_sm3_sm3(JNIEnv *env, jclass jc){     /* Initializing OpenSSL */    SSL_load_error_strings();    ERR_load_BIO_strings();    OpenSSL_add_all_algorithms();        /*You can use ENGINE_by_id Function to get the handle of the Huawei Accelerator Engine*/    ENGINE *e = ENGINE_by_id("kae");    /*使能加速引擎异步功能,可选配置,设置为“0”表示不使能,设置为“1”表示使能,默认使能异步功能*/    ENGINE_ctrl_cmd_string(e, "KAE_CMD_ENABLE_ASYNC", "1", 0)        /*指定引擎用于SM3摘要算法*/ENGINE_init(e); /*The user code*/    ….     const EVP_MD *md_sm3 = EVP_sm3();     EVP_MD_CTX *md_ctx = EVP_MD_CTX_new();     EVP_DigestInit_ex(md_ctx,  md_sm3, e);     EVP_DigestUpdate(md_ctx, buf_in, buf_len);     EVP_DigestFinal_ex(md_ctx, buf_out, &out_len);     EVP_MD_CTX_free(md_ctx);    ….            ENGINE_free(e);}将.c文件编译生成so文件:gcc -shared -Wl,-soname,sm3 -D_REENTRANT -I$JAVA_HOME/include -I$JAVA_HOME/include/linux -L/usr/local/lib -lssl -lcrypto -o sm3.so -fPIC sm3.c注:$JAVA_HOME/include 是jni.h头文件所在的路径   $JAVA_HOME/include/linux是jni_md.h所在的路径6、编译生成的.class文件即可执行调用到.so库
  • [区域初赛赛题问题] java 打开选手输出文件失败
    【问题现象】编译也成功了,但是上传出现“打开选手输出文件失败”,相对路径和绝对路径都用过了,都是这个问题,。。【截图信息】
  • [区域初赛赛题问题] java在本地的Linux系统上编译出现异常
    【问题描述】编程语言为Java 本地idea上测试没问题,也能输出solution.txt文件 ,但是上传报编译错误。于是在本地的Linux系统上编译,也出现异常,然后拿什么都没动过的Demo也测试,发现还是这样。哭死。。。。。【截图信息】
  • [区域初赛赛题问题] 【XXX产品】【XXX功能】一句话描述问题 java可以引入jar包吗?
    【功能模块】【操作步骤&问题现象】1、2、【截图信息】【日志信息】(可选,上传日志内容或者附件) java可以引入jar包吗?
  • [区域初赛赛题问题] 请问java输出打包成的zip文件下需要包含什么?一个jar包吗
    请问java输出打包成的zip文件下需要包含什么?一个jar包吗
  • [新手课堂] 关于Java数组的那些事
    数组是相同数据类型的元素的有序集合。为什么说是有序的呢?访问数组元素是通过数组下标进行的,下标从0开始,依次递增直到 length - 1(length为数组长度),所以可以理解为有序的。首先,来谈谈如何初始化数组?两种初始化方法:动态初始化、静态初始化。(这里以一维数组为例)//动态初始化int[] arr1 = new int[9];//声明了一个数组长度为9的一维数组//除了上面的初始化方式,还可以拆分为两步int[] arr2;arr2 = new int [9];//这两种都属于动态初始化,只是方式存在差异//静态初始化,声明数组长度为5的一维数组int[] arr3 = new int[]{1,2,3,4,5};//静态初始化的标准格式//也可以分为两步初始化//int[] arr3;//arr3 = new int[]{1,2,3,4,5};int[] arr4 = {1,2,3,4},5;//静态初始化的省略格式,不能分为两步既然声明了一个数组,那么数组是储存在哪里的呢?当使用"new"关键字创建数组对象后,会在堆中为数组分配相应的内存空间。总的来说,就是当你使用"new"关键字创建一个对象时,就会在堆中为该对象分配相应的内存空间。数组初始化,那么如何访问数组元素进行赋值和获取元素值呢?int[] arr = new int[5];//数组元素的值默认为0//数组元素赋值for(int i = 0; i < 5; i++){arr = 1;//这里就将数组下标为i的元素赋值为1}//既然对数组赋值了,那么如何获取数组元素的值System.out.println(arr);//这样是否会输出数组元素的值呢//答案是不会,这样输出的是数组的内存地址哈希值[I@#########]//方括号代表是数组,大写字母I代表为整型,后面的#则是十六进制数//这里介绍一种循环:for each循环for(int x:arr){System.out.println(x);//这样就能输出数组元素的值了,当然你也可以使用普通的for循环来进行访问//for(int i = 0; i < 5; i++){//System.out.println(arr);//}}以上就是Java数组中关于一维数组的简单介绍。
总条数:1653 到第
上滑加载中