语言基础Java(二)

目录

 

● 请你说一下java里内存泄漏和溢出的区别

参考回答:

● 请问你用过什么语言,用这些语言写过什么程序

参考回答:

● 你知道java里面的内存管理机制吗,比如创建和回收是怎么管理的?

参考回答:

● 请你说一下Java里integer和int的区别,以及如何比较相等

参考回答:

● 请你介绍下Java学习情况,学习一门新的语言需要多快

参考回答:

● 请你介绍一下gc,另外如果Java里写一个方法,这个方法里只有一条语句,即new一个对象,请问方法结束以后这个对象怎么回收的?

参考回答:

● 请你回答一下protected,public,private的区别

参考回答:

● 请你说一下抽象类和接口的区别

参考回答:

● 请你说一下List和ArrayList的区别,以及arrayList和HashSet区别

参考回答:

● 请你回答一下Java的内存结构是什么,全局变量,临时变量,静态变量分别存在哪里,堆分为哪几块,比如说新生代老生代,那么新生代又分为什么

参考回答:

● 手写代码:给出一个int类型123,写一个函数,返回反转的值321

参考回答:

● 请你回答一下c++和java的区别

参考回答:

● 请你回答一下怎么判断哪些对象是可以删除的,可达是什么意思

参考回答:

● 请你说一说接口有什么限制

参考回答:

● 请问Java中线程如何实现,如何实现多线程,线程安全在Java中是如何实现的,线程的工作区是哪里

参考回答:

● 请你说一说内存溢出和内存泄漏是怎么回事

参考回答:

● 请你介绍一下HashMap,HashTable,ConcurrentHashMap

参考回答:

● 请你说一下Hashset有什么特性,以及hashset判断存入的对象是否重复是如何比较的

参考回答:

● 请你说一下Java的反射,你目前主要用他做什么,以及Java的泛型,他的主要作用是什么

参考回答:

● 请问类加载器你了解吗

参考回答:

● ReentranceLock 和 synchronized 有什么区别

参考回答:


● 请你说一下java里内存泄漏和溢出的区别

参考回答:

1、内存泄漏memory leak :是指程序在申请内存后,无法释放已申请的内存空间,一次内存泄漏似乎不会有大的影响,但内存泄漏堆积后的后果就是内存溢出。

2、内存溢出 out of memory :指程序申请内存时,没有足够的内存供申请者使用,或者说,给了你一块存储int类型数据的存储空间,但是你却存储long类型的数据,那么结果就是内存不够用,此时就会报错OOM,即所谓的内存溢出。

● 请问你用过什么语言,用这些语言写过什么程序

参考回答:

用Java写过一个高并发秒杀系统,用Python搭建过自动化测试框架

● 你知道java里面的内存管理机制吗,比如创建和回收是怎么管理的?

参考回答:

java虚拟机在执行Java程序的过程中会把它所管理的内存划分为若干个不同的数据区域。这些区域存储不同类型的数据,这些区域的内存分配和销毁的时间也不同,有的区域随着虚拟机进程的启动而存在,有些区域则是依赖用户线程的启动和结束而建立和销毁。根据《Java虚拟机规范(第2版)》的规定,Java虚拟机管理的内存包括五个运行时数据区域,

1、方法区

方法区(Method Area)是各个线程共享的内存区域,它用于存储已被虚拟机加载的类信息(包括类的名称、方法信息、成员变量信息)、常量、静态变量、以及编译器编译后的代码等数据

2、虚拟机栈和本地方法栈

虚拟机栈是线程私有的内存空间,每个线程都有一个线程栈,本地方法栈也是线程私有的内存空间,本地方法栈与Java栈所发挥的作用是非常相似的,它们之间的区别不过是Java栈执行Java方法,本地方法栈执行的是本地方法,有的虚拟机直接把本地方法栈和虚拟机栈合二为一。

3、堆

Java堆是Java虚拟机所管理的内存中最大的一块,在虚拟机启动时创建,此内存区域的目的就是存放对象实例,几乎所有的对象实例都在这里分配内存。从内存分配的角度来看,线程共享的Java堆中可能划分出多个线程私有的分配缓冲区(TLAB)。Java堆可以处于物理上不连续的内存空间,只要逻辑上连续即可,在实现上,既可以实现固定大小的,也可以是扩展的。如果堆中没有足够的内存分配给实例,并且堆也无法再拓展时,将会抛出OutOfMemeryError异常。

Java内存回收:

对于栈空间,当方法调用结束后,基本类型变量,引用类型变量,形参占据的空寂会被自动释放,但引用类型指向的对象在堆中,堆中的无用内存有垃圾回收线程回收,GC线程优先级最低,只有当没有工作线程存在时GC线程才会执行,或者堆空间不足时会自动出发GC线程工作,

● 请你说一下Java里integer和int的区别,以及如何比较相等

参考回答:

Integer和int的区别:

1、integer是int的包装类,int是Java的一种基本数据结构

2、integer变量必须实例化后才能使用,int变量不需要

3、integer实际是对象的引用,int是直接存储数据值

4、integer的默认值是null,int的默认值是0

如何比较相等,首先要明白equals和==的区别

Equals通常用来比较两个对象的内容是否相等,==用来比较两个对象的地址是否相等,Object类中的equals方法定义为判断两个对象的地址是否相等(可以理解成是否是同一个对象),地址相等则认为是对象相等。这也就意味着,我们新建的所有类如果没有复写equals方法,那么判断两个对象是否相等时就等同于“==”,也就是两个对象的地址是否相等。但在我们的实际开发中,通常会认为两个对象的内容相等时,则两个对象相等,equals返回true。对象内容不同,则返回false。

所以可以总结为两种情况

1、类未复写equals方法,则使用equals方法比较两个对象时,相当于==比较,即两个对象的地址是否相等。地址相等,返回true,地址不相等,返回false。

2、类复写equals方法,比较两个对象时,则走复写之后的判断方式。通常,我们会将equals复写成:当两个对象内容相同时,则equals返回true,内容不同时,返回false。

● 请你介绍下Java学习情况,学习一门新的语言需要多快

参考回答:

如实回答即可,体现出一个学习能力的地方。

● 请你介绍一下gc,另外如果Java里写一个方法,这个方法里只有一条语句,即new一个对象,请问方法结束以后这个对象怎么回收的?

参考回答:

GC(garbage collection)垃圾收集,是指JVM用于释放那些不再使用的对象所占用的内存,常用的JVM都有GC,而且大多数gc都使用类似的算法管理内存和执行手机操作,GC可以通过确定对象是否被活动对象引用来确定是否收集该对象,常用的方法有引用计数和对象引用遍历

● 请你回答一下protected,public,private的区别

参考回答:

作用域     当前类  同一package  子孙类  其它package

public       √                     √              √            √

protected    √                √                 √                  ×

friendly     √                     √                  ×                 ×

private    √               ×                  ×                 ×

● 请你说一下抽象类和接口的区别

参考回答:

从语法层面来说,

1、抽象类可以提供成员方法的实现细节,而接口中只能存在抽象方法

2、抽象类中成员变量可以是多种类型,接口中成员变量必须用public,static,final修饰

3、一个类只能继承一个抽象类,但可以实现多个接口

4、抽象类中允许含有静态代码块和静态方法,接口不能

从设计层面而言

1.抽象类是对整一个类的属性,行为等方面进行抽象,而接口则是对行为抽象。就好比飞机和鸟,抽象类抽象出的是飞行物类。而接口则是抽闲出飞行方法。

2.抽象类是一个模板式的设计,当在开发过程中出现需求更改的情况,只需要更改抽象类而不需要更改它的子类。接口是一种辐射性设计,当接口的内容发生改变时,需要同时对实现它的子类进行相应的修改。

3.抽象类可以类比为模板,而接口可以类比为协议

● 请你说一下List和ArrayList的区别,以及arrayList和HashSet区别

参考回答:

List:是一个有序的集合,可以包含重复的元素。提供了按索引访问的方式。它继承 Collection。

List有两个重要的实现类:ArrayList 和 LinkedList
ArrayList:我们可以将其看作是能够自动增长容量的数组。
利用ArrayList的toArray()返回一个数组。
Arrays.asList()返回一个列表。
1.ArrayList底层采用数组实现,当使用不带参数的构造方法生成ArrayList对象时,实际上会在底层生成一个长度为10的Object类型数组
2.如果增加的元素个数超过了10个,那么ArrayList底层会新生成一个数组,长度为原数组的1.5倍+1,然后将原数组的内容复制到新数组当中,并且后续增加的内容都会放到新数组当中。当新数组无法容纳增加的元素时,重复该过程。
3.对于ArrayList元素的删除操作,需要将被删除元素的后续元素向前移动,代价比较高。
4.集合当中只能放置对象的引用,无法放置原生数据类型,我们需要使用原生数据类型的包装类才能加入到集合当中。 
5.集合当中放置的都是Object类型,因此取出来的也是Object类型,那么必须要使用强制类型转换将其转换为真正的类型(放置进去的类型)

● 请你回答一下Java的内存结构是什么,全局变量,临时变量,静态变量分别存在哪里,堆分为哪几块,比如说新生代老生代,那么新生代又分为什么

参考回答:

代码区:是编译器生成的一个exe区段,存放函数体的二进制代码

栈区:存放函数的参数,局部变量的值等,其操作方式类似于数据结构中的栈,const局部变量也是放在栈里

堆区:就是malloc和new之类的内存所在区段,一般由程序员分配释放,分配方式类似于链表

静态数据区:是编译器生成的一个exe区段,初始和未初始化的全局变量和局部变量都放在这里,

常量区:是编译器生成的一个exe区段,const全局变量也放在常量区。

全局变量,临时变量,静态变量分别存在哪里

局部变量保存在栈中,全局变量和静态变量存储在静态数据区。

堆分为哪几块,比如说新生代老生代,那么新生代又分为什么?

java垃圾收集管理器的主要区域,因此很多时候被称为“GC堆”。

分为新生代和老年代;

新生代分为:Eden和Survivor。

● 手写代码:给出一个int类型123,写一个函数,返回反转的值321

参考回答:

void my_itoa(int x,char *s,char radix)

{
char zm[37]="0123456789abcdefghijklmnopqrstuvwxyz"; 
int i=0; 
int sum=x;
while(sum>0) 

s[i++]=zm[sum%radix]; 
sum/=radix; 

}
int atoi_my( char *str) 

int s=0; 
while(*str>='0'&&*str<='9') 

s=s*10+*str-'0'; 
str++; 
if(s<0) 

s=2147483647; 
break; 


return s; // 
}

● 请你回答一下c++和java的区别

参考回答:

1、C++创建对象后需要在使用结束后调用delete方法将其销毁,Java有垃圾回收机制,用来监视new出来的所有对象,辨别不会再被引用的对象,然后释放内存空间

2、当变量作为类的成员使用时,Java才确保给定默认值,以确保那些基本类型的成员变量得到初始化,但是C++没有此功能,

3、c++引入了操作符重载机制,Java不支持

4、Java中没有sizeof(),在C++中sizeof()操作符能够告诉我们为数据项分配的字节数,因为C++中不同的数据类型在不同的机器上可能有不同的大小,但是在Java中所有的数据类型在所有机器中大小都是相同的。

5、Java的运行速度比C++慢,因为Java是半解释和半编译的。

6、C++中有多继承,Java中只有单一继承,但是java可以通过接口实现多继承

7、在C++中,数组定义时,已经分配存储空间,并且可以使用,在Java中,数组定义时只定义了数组变量,数组是不可以使用的,只有数组new之后才会创建数组,并分配存储空间。

8、C++有指针,Java没有。

● 请你回答一下怎么判断哪些对象是可以删除的,可达是什么意思

参考回答:

判断方法有两种,一种是引用计数器,给对象添加一个引用计数器,每当有一个地方引用他时,计数器就加一,引用失败计数器减一,计数器为零的对象就不可能再被使用,

第二种方法是可达性分析算法。这个算法的基本思路就是通过一系列的称为"GC Roots"的对象作为起始点,从这些节点开始向下搜索,搜索走过的路径称为引用链。当一个对象到GC Roots没有任何引用链相连的时候,则证明此对象是不可用的。否则即是可达的。在java语言中,可作为GC Roots的对象包括下面几种:虚拟机栈中引用的对象、方法区中类静态属性引用的对象、方法区中常量引用的对象和本地方法栈中JNI(一般说的Native方法)引用的对象。

● 请你说一说接口有什么限制

参考回答:

1、变量会被隐式地指定为public static final变量,并且只能是public static final变量,用private修饰会报编译错误

2、方法会被隐式地指定为public abstract方法且只能是public abstract方法(用其他关键字,比如private、protected、static、 final等修饰会报编译错误)

3、接口中所有的方法不能有具体的实现,也就是说,接口中的方法必须都是抽象方法

● 请问Java中线程如何实现,如何实现多线程,线程安全在Java中是如何实现的,线程的工作区是哪里

参考回答:

Java中实现线程有三种方式:

1、1.继承Thread类

public class Thread extends Object implements Runnable

定义Thread类的子类,并重写Thread类的run()方法,创建子类对象(即线程对象),调用线程对象的start()方法来启动该线程。

2.实现Runnable接口

public interface Runnable

定义Runnable接口的实现类,并重写该接口的run()方法,该run()方法同样是该线程的执行体。创建该Runnable实现类的实例,并将此实例作为Thread的target(即构造函数中的参数)来创建Thread对象(该Thread对象才是真正的线程对象,只是该Threa

3.使用Callable和Future

创建Callable接口的实现类,并实现call()方法,该方法有返回值;创建Callable实现类的实例,使用FutureTask来包装Callable对象,并且也封装了call()方法的返回值;使用FutureTask作为Thread类的target创建并启动线程;调用FutureTask对象的get()方法返回子线程执行结束后的返回值。

如何实现多线程:

首先是继承Thread类并重写run()方法

package com.csu.multiThread;

public class MultiThreadExtendsThread extends Thread{

String name;

public MultiThreadExtendsThread(String name) {

this.name = name;

}

public void run() {

for(int i=0;i<5;i++) {

System.out.println(name+"运行:"+i);

}

}

public static void main(String[] args) {

MultiThreadExtendsThread thread1 = new MultiThreadExtendsThread("A");

MultiThreadExtendsThread thread2 = new MultiThreadExtendsThread("B");

thread1.start();

thread2.start();

}

}

二是实现Runnable接口,然后重写run()方法,

package com.csu.multiThread;

public class MultiThreadImplRunnable implements Runnable{

String name;

public MultiThreadImplRunnable(String name) {

this.name = name;

}

@Override

public void run() {

for(int i=0;i<5;i++) {

System.out.println(name+"运行:"+i);

}

}

public static void main(String[] args) {

MultiThreadExtendsThread thread1 = new MultiThreadExtendsThread("A");

MultiThreadExtendsThread thread2 = new MultiThreadExtendsThread("B");

new Thread(thread1).start();

new Thread(thread2).start();

}

}

线程安全的实现:

最基本的:synchronized关键字。这个方法是最常用的,它通过互斥的方式保证同步。我们知道java中有几个操作是可以保证原子性的,其中lock/unlock就是一对。虽然java没有提供这两个字节码的接口,但是我们可以通过monitorenter/monitorexit,而synchronized会在块的前后调用两个字节码指令。同时synchronize对于同一条线程来说是可重入的;其次它也是阻塞的。我们知道java线程是映射到操作系统上的,而且是混用的内核态线程和用户态线程(N:M),而将线程从阻塞/唤醒,需要将线程从用户态转换到内核态,这样会消耗太亮的资源,所以synchronize是一个重量级锁

另外一种和synchronize类似的方法:ReentrantLock。它们两个的区别:(1)synchronize是隐式的,只要块内的代码执行完,就会释放当前的锁;而后者需要显式的调用unlock()方法手动释放,所以经常搭配try/finally方法(忘记在finally中unlock是非常危险的) (2)后者可以选择等待中断——即在当前持有锁线程长期不释放锁的情况下,正在等待的线程可以选择放弃等待选择处理其他的事情。 (3) 后者可以选择公平锁(虽然默认是非公平的,因为公平锁的吞吐量很受影响)即先来后到,按申请的顺序获得锁。 (4)可以绑定多个条件

前面提到的两种方式都是通过互斥来达到同步的目的,这其实是悲观锁的一种。下面介绍的是乐观锁,基于冲突检测的并发策略,不需要将线程挂起,因此又被成为非阻塞同步。

典型:CAS(Compare And Swap),通过Unsafe类提供。有三个操作数,内存位置、旧的预期值、和新的值;当且仅当内存地址V符合预期值A时,执行将值更新为新的预期值B。  存在的问题:“ABA”情况,即原值为A,但在检测之前发生了改变,变成了B,同时也在检测时变回了A;即不能保证这个值没有被其他线程更改过。

接下来是无同步方案:

可重入代码(纯代码):是一种无同步方案,在执行的任何时候去中断,转而执行其他的代码;在重新返回代码后,不会出现任何的错误。可重入性->线程安全,充分不必要条件。即可重入性的代码都是线程安全的,但反之不一定。简单判断原则:一个方法的返回结果是可预测的,只要输入了相同的数据,就都能返回相同的结果。

线程本地存储:即利用ThreadLocal类;每个Thread类中都有一个变量ThreadLocalMap,默认是为null的。它将为每一个线程创立一个该变量的副本。这样线程之间就不存在数据征用的问题了。适用情况:(1)数据库的Connection连接 (2)WEB中的“一个请求对应一个服务器线程”,在知乎上看到一个回答,解释的蛮清晰的。(3)Spring中创建的默认模式是Singleton单例 (4)“生产者-消费者问题”

ThreadLocal就是变量在不同线程上的副本,不同线程不共享,所以对变量改动时就不需要考虑线程间同步的问题了

ThreadLocal在web应用开发中是一种很常见的技巧,当web端采用无状态写法时(比如stateless session bean和spring默认的singleton),就可以考虑把一些变量放在ThreadLocal中

举个简单例子,以理解意思为主:你有两个方法A和B都要用到变量userId,又不想传来传去,一个很自然的想法就是把userId设为成员变量,但是在无状态时,这样做就很可能有问题,因为多个request在同时使用同一个instance,userId在不同request下值是不一样的,就会出现逻辑错误
但由于同一个request下一般都是处于同一个线程,如果放在ThreadLocal的话,这个变量就被各个方法共享了,而又不影响其他request,这种情况下,你可以简单把它理解为是一种没有副作用的成员变量(作者:卡斯帕尔)

线程栈线程的每个方法被执行的时候,都会同时创建一个帧(Frame)用于存储本地变量表、操作栈、动态链接、方法出入口等信息。每一个方法的调用至完成,就意味着一个帧在VM栈中的入栈至出栈的过程。如果线程请求的栈深度大于虚拟机所允许的深度,将抛出StackOverflowError异常;如果VM栈可以动态扩展(VM Spec中允许固定长度的VM栈),当扩展时无法申请到足够内存则抛出OutOfMemoryError异常。

● 请你说一说内存溢出和内存泄漏是怎么回事

参考回答:

内存溢出out of memory,是指程序在申请内存时,没有足够的内存空间供其使用,出现out of memory;比如申请了一个integer,但给它存了long才能存下的数,那就是内存溢出。

内存泄露memory leak,是指程序在申请内存后,无法释放已申请的内存空间,一次内存泄露危害可以忽略,但内存泄露堆积后果很严重,无论多少内存,迟早会被占光。

内存泄漏可以分为四类:

1、常发性内存泄漏,发生内存泄漏的代码会被多次执行到,每次执行都会导致内存泄漏

2、偶发性内存泄漏:发生内存泄漏的代码只有在某些特定环境或操作过程下才会发生,

3、一次性内存泄漏,发生内存泄漏的代码只会被执行一次,或者由于算法上的缺陷,导致总会有一块仅且一块内存发生泄漏。

4、隐式内存泄漏。程序在运行过程中不停的分配内存,但是直到结束的时候才释放内存。

从用户使用程序的角度来看,内存泄漏本身不会产生什么危害,真正有危害的是内存泄漏的堆积,这会最终消耗尽系统所有的内存。

内存溢出常见原因:

1.内存中加载的数据量过于庞大,如一次从数据库取出过多数据;
2.集合类中有对对象的引用,使用完后未清空,使得JVM不能回收;
3.代码中存在死循环或循环产生过多重复的对象实体;
4.使用的第三方软件中的BUG;
5.启动参数内存值设定的过小

解决方案:

1、修改JVM参数,直接增加内存

2、检查错误日志,查看内存溢出错误前是否有其他异常错误

3、对代码进行走查分析,找出可能发生内存溢出的位置

● 请你介绍一下HashMap,HashTable,ConcurrentHashMap

参考回答:

1、HashTable与HashMap

(1)HashTable和HashMap都实现了Map接口,但是HashTable的实现是基于Dictionary抽象类。

(2)在HashMap中,null可以作为键,这样的键只有一个;可以有一个或多个键所对应的值为null。当get()方法返回null值时,既可以表示HashMap中没有该键,也可以表示该键所对应的值为null。因此,在HashMap中不能由get()方法来判断HashMap中是否存在某个键,而应该用containsKey()方法来判断。而在HashTable中,无论是key还是value都不能为null。

(3)HashTable是线程安全的,它的方法是同步了的,可以直接用在多线程环境中。HashMap则不是线程安全的。在多线程环境中,需要手动实现同步机制。

2、更好的选择:ConcurrentHashMap Java 5中新增了ConcurrentMap接口和它的实现类ConcurrentHashMap。 ConcurrentHashMap提供了和HashTable以及SynchronizedMap中所不同的锁机制。HashTable中采用的锁机制是一次锁住整个hash表,从而同一时刻只能有一个线程对其进行操作;而ConcurrentHashMap中则是一次锁住一个桶。ConcurrentHashMap默认将hash表分为16个桶,诸如get,put,remove等常用操作只锁住当前需要用到的桶。这样,原来只能一个线程进入,现在却能同时有16个写线程执行,并发性能的提升是显而易见的。 上面说到的16个线程指的是写线程,而读操作大部分时候都不需要用到锁。只有在size等操作时才需要锁住整个hash表。 在迭代方面,ConcurrentHashMap使用了一种不同的迭代方式。在这种迭代方式中,当iterator被创建后集合再发生改变就不再是抛出ConcurrentModificationException,取而代之的是,在改变时new新的数据从而不影响原有的数据,iterator完成后再将头指针替换为新的数据,这样iterator可以使用原来老的数据,而写线程也可以并发的完成改变。

● 请你说一下Hashset有什么特性,以及hashset判断存入的对象是否重复是如何比较的

参考回答:

HashSet是Set接口的实现类,因此,HashSet中的元素也是不能重复的。HashCode判断元素重复的标准时,首先计算新添加元素的hashCode值,当不重复是,则直接加入到该集合中,若发生重复,也称发生了碰撞,则进一步调用equals判断元素是否在逻辑上相同。

● 请你说一下Java的反射,你目前主要用他做什么,以及Java的泛型,他的主要作用是什么

参考回答:

JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意方法和属性;这种动态获取信息以及动态调用对象方法的功能称为java语言的反射机制。Java反射可以用来获取一个class对象或实例化一个class表示的类的对象,还可以获取构造方法,成员变量,成员方法。

java中泛型的引入主要是为了解决两个方面的问题:1.集合类型元素在运行期出现类型装换异常,增加编译时类型的检查,2. 解决的时重复代码的编写,能够复用算法。下面通过例子来说明编译器的类型检查。

● 请问类加载器你了解吗

参考回答:

类加载器就是把类加载阶段中的”通过一个类的全限定名来获取描述此类的二进制字节流”这个动作放到java虚拟机外部来实现的代码模块。

类加载器的分类:

启动类加载器、扩展类加载器、应用类加载器(系统类加载器)、用户自定义类加载器。 
启动类加载器:这个类负责将存放在JAVA_HOME/lib目录或者被-Xbootclasspath参数所指定的路径中的并且是虚拟机内存中。 
扩展类加载器:负责加载JAVA_HOME/lib/ext目录中或者被java.ext.dirs系统变量指定路径中的所有类库,开发者可以直接使用扩展类加载器。 
应用程序类加载器:负责加载用户类路径上指定的类加载器,一般情况下就是程序中默认的类加载器。

● ReentranceLock 和 synchronized 有什么区别

参考回答:

1.可重入性

字面的意思就是可以再次进入的锁,synchronized其实也是可重锁,同一线程每进入一次,锁的计数器都会加一,在释放锁是计数器都会减一,只有当计数器为0 时才能释放锁

2.锁的实现

ReentrantLock是JDK实现的 Synchronized 是JVM实现 
前者可以直接看到源码,后者实现难以看到

3.性能的区别

在Synchronized优化以前,synchronized的性能是比ReenTrantLock差很多的,但是自从Synchronized引入了偏向锁,轻量级锁(自旋锁)后,两者的性能就差不多了,在两种方法都可用的情况下,官方甚至建议使用synchronized,其实synchronized的优化我感觉就借鉴了ReenTrantLock中的CAS技术。都是试图在用户态就把加锁问题解决,避免进入内核态的线程阻塞。

4.功能的区别

便利性:很明显Synchronized的使用比较方便简洁,并且由编译器去保证锁的加锁和释放,而ReenTrantLock需要手工声明来加锁和释放锁,为了避免忘记手工释放锁造成死锁,所以最好在finally中声明释放锁。 
锁的细粒度和灵活度:很明显ReenTrantLock优于Synchronized

当你需要时候一下三种功能是需要使用ReentrantLock

ReentranLock 可以指定公平锁还是非公平锁 
(公共锁就是先等待的线程先获得锁) 
实现自旋锁,通过循环调用CAS操作来实现加锁,性能比较好,避免进入内核态的线程阻塞。

提供了Condition类,可以分组唤醒需要唤醒的线程

提供能够中断等待锁的线程的机制,lock.lockInterruptibly()

具体使用场景要根据实际的业务进行分析 
使用Synchronized时不需要释放锁,jvm会帮助我们做释放锁的操作

猜你喜欢

转载自blog.csdn.net/u012369559/article/details/89494347