《Java面试 基础篇》

目录

 

1、Java语言特点

2、JDK、JRE、JVM

3、字节码,采用字节码的好处:

4、Java程序执行过程

5、成员变量与局部变量的区别

6、重载和重写的区别:

7、包装类和基本数据类型:

8、深拷贝、浅拷贝、引用拷贝

9、==和equals() 

10、hashCode()

11、Java反射

12、SPI


 

1、Java语言特点

Java是面向对象语言,具备简单性、平台无关性、安全性、可靠性、多线程等特点。

2、JDK、JRE、JVM

JDK(Java Development Kit),Java开发工具包,包括了Java运行环境(Java Runtime Environment),一些Java工具包和编译器。

JRE(JAVA Runtime Environment)Java运行时环境,包括 Java 虚拟机(JVM),Java 类库,java 命令和其他的一些基础构件。

JVM(Java Virtual Machine),Java虚拟机,用于运行Java字节码,JVM 有针对不同系统的特定实现(Windows,Linux,macOS),目的是使用相同的字节码,它们都会给出相同的结果,实现跨平台。

3、字节码,采用字节码的好处:

JVM可以理解的代码叫做字节码,即扩展名为.class的文件,字节码只面向虚拟机,Java 语言通过字节码的方式,解决了传统解释型语言执行效率低的问题,同时具备可移植性。所以, Java 程序运行时相对来说还是高效的(不过,和 C++,Rust,Go 等语言还是有一定差距的),通过字节码,Java代码可以在不同的操作系统上不需要重复编译就可以运行。

4、Java程序执行过程

83c861f3a19441a4973ef670fa6f8158.png

 (图源自JavaGuide)

可以分为编译器和运行期,编译器是指将编写好.java文件,通过java编译器编译成字节码文件(.class文件),运行期是指通过类加载器加载字节码文件,将其加载到内存(JVM)中,然后通过解释器将其解释为机器可理解的代码,并最终运行。而且,有些方法和代码块是经常需要被调用的(也就是所谓的热点代码),所以后面引进了 JIT(just-in-time compilation) 当 JIT 编译器完成第一次编译后,其会将字节码对应的机器码保存下来,下次可以直接使用。

5、成员变量与局部变量的区别

1.语法形式:成员变量是属于类的,而局部变量是在代码块或方法中定义的变量或是方法的参数;成员变量可以被 public,private,static 等修饰符所修饰,而局部变量不能被其修饰;但是,成员变量和局部变量都能被 final 所修饰。

2.存储方式:从变量在内存中的存储方式来看,如果成员变量是使用 static 修饰的,那么这个成员变量是属于类的,如果没有使用 static 修饰,这个成员变量是属于实例的。而对象存在于堆内存,局部变量则存在于栈内存。

3.生存时间:从变量在内存中的生存时间上看,成员变量是对象的一部分,它随着对象的创建而存在,而局部变量随着方法的调用而自动生成,随着方法的调用结束而消亡。

4.默认值:从变量是否有默认值来看,成员变量如果没有被赋初始值,则会自动以类型的默认值而赋值(一种情况例外:被 final 修饰的成员变量也必须显式地赋值),而局部变量则不会自动赋值。

6、重载和重写的区别:

重载发生在同一个类中,方法名相同,但是参数类型、个数、顺序不同,方法返回值和访问修饰符可以不同。

重写就是当子类继承父类的相同方法,方法名、参数列表必须相同,子类做出和父类不同的响应,就要重写。

方法的重写要遵循“两同两小一大”

  • “两同”即方法名相同、形参列表相同;
  • “两小”指的是子类方法返回值类型应比父类方法返回值类型更小或相等,子类方法声明抛出的异常类应比父类方法声明抛出的异常类更小或相等;
  • “一大”指的是子类方法的访问权限应比父类方法的访问权限更大或相等。

7、包装类和基本数据类型:

1.成员变量包装类型默认值 null ,而基本类型有各自的默认值,并且不是null

2.包装类型可用于泛型,而基本类型不可以

3.存放位置

(图源自网络)

f592c9938e4444bd807367b9b0202b8c.png

8、深拷贝、浅拷贝、引用拷贝

1.浅拷贝,浅拷贝会在堆上创建一个新的对象(区别于引用拷贝的一点),但是如果原对象内部的属性是引用类型的话,浅拷贝会直接复制内部对象的引用地址,也就是说拷贝对象和原对象共用同一个内部对象。

2.深拷贝:深拷贝会完全复制整个对象,包括这个对象所包含的内部对象。

3.引用拷贝:引用拷贝就是两个不同的引用指向同一个对象。

(图源自JavaGuide)

62cfd82851e2480ebf2cc8a968fc8d3c.png

9、==和equals() 

== 对于基本类型和引用类型的作用效果是不同的:

  • 对于基本数据类型来说,== 比较的是值。
  • 对于引用数据类型来说,== 比较的是对象的内存地址。'

equals() 不能用于判断基本数据类型的变量,只能用来判断两个对象是否相等。equals()方法存在于Object类中,而Object类是所有类的直接或间接父类,因此所有的类都有equals()方法。

对于equals()方法的使用,分为重写和没有重写两种情况。

  • 类没有重写 equals()方法 :通过equals()比较该类的两个对象时,等价于通过“==”比较这两个对象,使用的默认是 Objectequals()方法。
  • 类重写了 equals()方法 :一般我们都重写 equals()方法来比较两个对象中的属性是否相等;若它们的属性相等,则返回 true(即,认为这两个对象相等)。

比如String就重写了equals()方法,比较的是对象的值。

10、hashCode()

hashCode() 定义在Object类中,它的作用是获取哈希码(int 整数),也称为散列码。这个哈希码的作用是确定该对象在哈希表中的索引位置。

当把对象加入 HashSet 时,HashSet 会先计算对象的 hashCode 值来判断对象加入的位置,同时也会比较已加入对象的hashCode ,如果没有相等的,则会认为不重复,将其加入到相应位置。如果发现有相同 hashCode 值的对象,这时会调用 equals() 方法来检查 hashCode 相等的对象是否真的相同。如果两者相同,HashSet 就不会让其加入操作成功。如果不同的话,就会重新散列到其他位置。这样我们就大大减少了 equals 的次数,相应就大大提高了执行速度。

11、Java反射

反射指的是在运行时可以动态获取一个类的所有信息,并操作这些属性和方法等。常用的动态代理就是基于反射实现的,在spring框架中,AOP就是基于动态代理的。它能够将那些与业务无关,却为业务模块所共同调用的逻辑或责任(例如事务处理、日志管理、权限控制等)封装起来,减少重复代码,降低耦合度,并有利于未来的可拓展性和可维护性。

我们要想获取这些信息,就必须借助Class 对象,获取Class对象的四种方法:

1. 知道具体类的情况下可以使用:

Class alunbarClass = TargetObject.class;

但是我们一般是不知道具体类的,基本都是通过遍历包下面的类来获取 Class 对象,通过此方式获取 Class 对象不会进行初始化

2. 通过 Class.forName()传入类的全路径获取:

Class alunbarClass1 = Class.forName("cn.javaguide.TargetObject");

3. 通过对象实例instance.getClass()获取:

TargetObject o = new TargetObject();
Class alunbarClass2 = o.getClass();

4. 通过类加载器ClassLoader.loadClass()传入类路径获取:

ClassLoader.getSystemClassLoader().loadClass("cn.javaguide.TargetObject");

通过类加载器获取 Class 对象不会进行初始化,意味着不进行包括初始化等一系列步骤,静态代码块和静态对象不会得到执行。

常用的方法:

//获取包名、类名
clazz.getPackage().getName()//包名
clazz.getSimpleName()//类名
clazz.getName()//完整类名
 
//获取成员变量定义信息
getFields()//获取所有公开的成员变量,包括继承变量
getDeclaredFields()//获取本类定义的成员变量,包括私有,但不包括继承的变量
getField(变量名)
getDeclaredField(变量名)
 
//获取构造方法定义信息
getConstructor(参数类型列表)//获取公开的构造方法
getConstructors()//获取所有的公开的构造方法
getDeclaredConstructors()//获取所有的构造方法,包括私有
getDeclaredConstructor(int.class,String.class)
 
//获取方法定义信息
getMethods()//获取所有可见的方法,包括继承的方法
getMethod(方法名,参数类型列表)
getDeclaredMethods()//获取本类定义的的方法,包括私有,不包括继承的方法
getDeclaredMethod(方法名,int.class,String.class)
 
//反射新建实例
clazz.newInstance();//执行无参构造创建对象
clazz.newInstance(222,"韦小宝");//执行有参构造创建对象
clazz.getConstructor(int.class,String.class)//获取构造方法
 
//反射调用成员变量
clazz.getDeclaredField(变量名);//获取变量
clazz.setAccessible(true);//使私有成员允许访问
f.set(实例,值);//为指定实例的变量赋值,静态变量,第一参数给null
f.get(实例);//访问指定实例变量的值,静态变量,第一参数给null
 
//反射调用成员方法
Method m = Clazz.getDeclaredMethod(方法名,参数类型列表);
m.setAccessible(true);//使私有方法允许被调用
m.invoke(实例,参数数据);//让指定实例来执行该方法

当我们调用私有属性和私有方法时,需要取消安全检查。

//为了调用private方法我们取消安全检查
        privateMethod.setAccessible(true);

12、SPI

SPI 即 Service Provider Interface,专门提供给服务提供者去使用的一个接口。

SPI 将服务接口和具体的服务实现分离开来,将服务调用方和服务实现者解耦,能够提升程序的扩展性、可维护性。修改或者替换服务实现并不需要修改调用方。

很多框架都使用了 Java 的 SPI 机制,比如:Spring 框架、数据库加载驱动、日志接口、以及 Dubbo 的扩展实现等等。

当实现方提供了接口和实现,我们可以通过调用实现方的接口从而拥有实现方给我们提供的能力,这就是 API ,这种接口和实现都是放在实现方的。

当接口存在于调用方这边时,就是 SPI ,由接口调用方确定接口规则,然后由不同的厂商去根据这个规则对这个接口进行实现,从而提供服务。

13、异常:

异常是指在程序运行时可能出现的错误或不正常情况的事件。所有的异常都有一个共同的祖先 java.lang 包中的 Throwable 类,有两个子类,分别是ExceptionError。其中,Error表示严重的错误,通常是程序无法处理的错误,如虚拟机内存不够等;而Exception则是程序可以处理的,其中运行时异常可以不用try、catch捕获,程序也可以编译,例如空指针异常和数组越界异常;而受检查异常就必须用try、catch进行捕获,或者throws向上抛出,否则无法编译,如IO异常。

  • Exception :程序本身可以处理的异常,可以通过 catch 来进行捕获。Exception 又可以分为 Checked Exception (受检查异常,必须处理) 和 Unchecked Exception (不受检查异常,可以不处理)。
  • ErrorError 属于程序无法处理的错误 ,如虚拟机内存不够,虚拟机运行错误等,Java 虚拟机(JVM)一般会选择线程终止

Checked Exception 即 受检查异常 ,Java 代码在编译过程中,如果受检查异常没有被 catch或者throws 关键字处理的话,就没办法通过编译。

Unchecked Exception 即 不受检查异常 ,Java 代码在编译过程中 ,我们即使不处理不受检查异常也可以正常通过编译。

常见的不受检查异常 有:

  • NullPointerException(空指针错误)
  • ArrayIndexOutOfBoundsException(数组越界错误)
  • ArithmeticException(算术错误)

 

运行时异常(RuntimeException)是不受检查异常的子类,

检查异常,也就是常说的编译异常,如IOException、SQLException;

 

接口和抽象类的区别:

两者都不能够实例化,

1、抽象类可以有构造方法,而接口不能

2、抽象类中的成员变量可以是各种类型的,而接口中的成员变量只能是public static final类型的;

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

4、抽象方法可以有public、protected和default这些修饰符,接口方法默认修饰符是public。你不可以使用其它修饰符。

  • 抽象类用于表示一种"是什么"的关系,它是对实际类的抽象,可以包含共有的属性和方法。
  • 接口用于表示一种"能做什么"的关系,它定义了一组需要被实现的方法,类通过实现接口来获得某种能力。

 

14、乐观锁和悲观锁

悲观锁:假设多个线程会同时访问一个共享资源,并且这些线程会试图修改它。悲观锁的策略是假设最坏情况,即认为在任何时候都有可能有另一个线程来修改这个共享资源,因此在访问前会先锁定这个资源,以防止其他线程修改。悲观锁的典型实现是数据库中的行锁或表锁。

乐观锁:相反,乐观锁的策略是认为多个线程同时访问共享资源的概率很小,因此不需要在访问前锁定资源。每次访问共享资源时,先获取一个版本号或时间戳,并在更新数据时检查这个版本号或时间戳是否被其他线程修改过。如果检查到冲突,则放弃当前操作。乐观锁的典型实现是在Java中使用的CAS(Compare And Swap)操作。

 

版本号机制

一般是在数据表中加上一个数据版本号 version 字段,表示数据被修改的次数。当数据被修改时,version 值会加一。当线程 A 要更新数据值时,在读取数据的同时也会读取 version 值,在提交更新时,若刚才读取到的 version 值为当前数据库中的 version 值相等时才更新,否则重试更新操作,直到更新成功。

 

CAS 涉及到三个操作数:

  • V:要更新的变量值(Var)
  • E:预期值(Expected)
  • N:拟写入的新值(New)

当且仅当 V 的值等于 E 时,CAS 通过原子方式用新值 N 来更新 V 的值。如果不等,说明已经有其它线程更新了 V,则当前线程放弃更新。

举一个简单的例子:线程 A 要修改变量 i 的值为 6,i 原值为 1(V = 1,E=1,N=6,假设不存在 ABA 问题)。

  1. i 与 1 进行比较,如果相等, 则说明没被其他线程修改,可以被设置为 6 。
  2. i 与 1 进行比较,如果不相等,则说明被其他线程修改,当前线程放弃更新,CAS 操作失败。

synchronized 

在 Java 6 之后, synchronized 引入了大量的优化如自旋锁、适应性自旋锁、锁消除、锁粗化、偏向锁、轻量级锁等技术来减少锁操作的开销,这些优化让 synchronized 锁的效率提升了很多。因此, synchronized 还是可以在实际项目中使用的,像 JDK 源码、很多开源框架都大量使用了 synchronized

# 如何使用 synchronized?

synchronized 关键字的使用方式主要有下面 3 种:

  1. 修饰实例方法
  2. 修饰静态方法
  3. 修饰代码块

synchronized 同步语句块的实现使用的是 monitorentermonitorexit 指令,其中 monitorenter 指令指向同步代码块的开始位置,monitorexit 指令则指明同步代码块的结束位置。

在执行monitorenter时,会尝试获取对象的锁,如果锁的计数器为 0 则表示锁可以被获取,获取后将锁计数器设为 1 也就是加 1。

1e3aa193a2ad43a2b55b013b155f7d61.png

在执行 monitorexit 指令后,将锁计数器设为 0,表明锁被释放,其他线程可以尝试获取锁。

 

MySQL的锁

MySQL里面的锁大致可以分成全局锁、表级锁和行锁三类

全局锁就是对整个数据库实例加锁,让整个库处于只读状态。MySQL提供了一个加全局读锁的方法,命令是Flush tables with read lock。当需要

这样会导致在备份的时候,不能够进行更新,不能够同步binlog

表锁,对整张表加锁,效率高,消耗资源少,但是并发度低

行锁,对行记录加锁,锁冲突概率低,但是消耗更多的资源,会出现死锁

在InnoDB事务中,行锁是在需要的时候才加上的,但并不是不需要了就立刻释放,而是要等到事务结束时才释放。这个就是两阶段锁协议

读写锁,读读不冲突,其他情况都冲突

间隙锁,当我们在进行区间更新操作的话,就会触发间隙锁,这是MySQL为了避免

当我们插入几条数据,还没有被提交,是不存在的,如果这个时候有一个事务进行范围操作,就会触发间隙锁

这样做是为了防止幻读,

【补充】

CDN : 几个用户在不同的地点,同时访问同一个服务时,根据他的位置,将用户的请求重新导向离用户最近的服务节点上,用户可就近取得所需资源,提高响应速度。

怎样写联表查询?流程?

left join

Redis细节:key  key比较大 redis单线程  bug?

谈谈java的锁:

在Java中,锁是用于控制对共享资源的访问的机制,以确保在多线程环境下,保证对共享资源的安全访问。Java提供了多种锁的机制,主要包括以下几种:

Synchronized关键字:(属于可重入锁)

  • synchronized 是Java中最基本的锁机制,可以用来修饰方法或代码块。当线程进入 synchronized 修饰的方法或代码块时,会尝试获取锁,如果锁已被其他线程占用,则当前线程会被阻塞直到锁被释放。

ReentrantLock:(可重入锁)即允许单个线程对共享资源进行重复加锁,以保证当前线程对共享资源的访问是安全的。

读写锁(ReadWriteLock):读写锁是一种共享锁,允许多个线程同时读取共享资源,但只允许一个线程进行写操作。在读多写少的情况下,使用读写锁可以提高系统的并发性能。

但是由于synchronized效率较低,因此Java SE 5也引入了一个volatile关键字,保证对于某个变量的写操作对其他线程可见,在多线程并发访问同一个共享变量时,可以避免读取到过期值的情况,但是紧紧保证可见性,不能够保证原子性。在多个线程需要进行写操作时,仍然需要采用synchronized等机制来保证线程安全

1. 修饰一个代码块,被修饰的代码块称为同步语句块,作用的对象是调用这个代码块的对象,其作用的范围有以下两种情况;

  • synchronized(SynchronizedTest .class)为类锁,则无论是都相同对象调用都为同步阻塞.
  • synchronized(this)为对象锁,不通对象调用时不会阻塞,相同对象调用时为同步阻塞。
     

2. 修饰一个普通方法,被修饰的方法称为同步方法,其作用的范围是整个方法,作用的对象是调用这个方法的对象,不同对象调用不会阻塞,同一对象调用则会阻塞,此时为对象锁;

3. 修改一个静态的方法,其作用的范围是整个静态方法,作用的对象是这个类的所有对象,无论是是否为同一对象都会阻塞,此时为类锁;

4、总结:

  • 修饰普通方法,相同对象阻塞,不同对象不阻塞。
  • 修饰静态方法,无论是否为同一对象,均为阻塞。
  • 修饰代码块,如果为对象锁则相同对象阻塞,不同对象不阻塞。如果为类锁,无论是否为同一对象,均为阻塞。

Synchronized和Lock的比较

1、lock是一个接口,而synchronized是java的一个关键字。

2、synchronized在发生异常时会自动释放占有的锁,因此不会出现死锁;而lock发生异常时,不会主动释放占有的锁,必须手动来释放锁,可能引起死锁的发生。

3、是否响应中断
lock等待锁过程中可以用interrupt来中断等待,而synchronized只能等待锁的释放,不能响应中断;

4、是否知道获取锁
Lock可以通过trylock来知道有没有获取锁,而synchronized不能;

5、解锁操作
synchronized:不能指定解锁操作,执行完代码块的对象会自动释放锁
Lock:可调用ulock方法去释放锁比synchronized更灵活

AQS:抽象的队列同步器,用来构建锁和同步器。

线程首先尝试获取锁,即将暂时获取不到锁的线程加入到队列中。如果失败就将当前线程及等待状态等信息包装成一个node节点加入到队列中。 接着会不断的循环尝试获取锁,条件是当前节点为head的直接后继才会尝试。如果失败就会阻塞自己直到自己被唤醒。而当持有锁的线程释放锁的时候,会唤醒队列中的后继线程。

java的多线程:

java的支持多线程,我们可以通过继承Thread类,实现runnable接口、实现callable、线程池创建线程这四种方法来创建线程。Java多线程使得能够充分发挥计算机硬件的并行性,提高程序的性能、资源利用率,适应更多的应用场景。然而,多线程编程也需要谨慎处理同步、锁、线程安全等问题,以确保程序的正确性。

实现Runnable接口相比继承Thread类有如下好处:

  • 避免单点继承的局限,一个类可以继承多个接口
  • 适合于资源的共享

 

spring事务的传播机制


1、spring事务
指封装在数据库事务之上的一种事务处理机制。其管理方法有两种,分别是编程式事务以及声明式事务。一般我们使用@Transactional进行声明式事务。

2、Spring事务的传播机制
事务的传播,是指一个方法调用另一个方法并将事务传递给它,然后控制它是否被传播或者被怎样传播。事务的传播机制主要针对被调用者而言,。spring事务的传播机制有七种:

  1. Propagation.REQUIRED:默认的事务传播级别,它表示如果当前存在事务,则加⼊该事务;如果当前没有事务,则创建⼀个新的事务。(整体考虑,局部事务出问题,整体都回滚)
  2. Propagation.SUPPORTS:如果当前存在事务,则加⼊该事务;如果当前没有事务,则以⾮事务的⽅式继续运⾏。
  3. Propagation.MANDATORY:(mandatory:强制性)如果当前存在事务,则加⼊该事务;如果当前没有事务,则抛出异常。
  4. Propagation.REQUIRES_NEW:表示创建⼀个新的事务,如果当前存在事务,则把当前事务挂起。也就是说不管外部⽅法是否开启事务,Propagation.REQUIRES_NEW 修饰的内部⽅法会新开启⾃⼰的事务,且开启的事务相互独⽴,互不⼲扰。
  5. Propagation.NOT_SUPPORTED:以⾮事务⽅式运⾏,如果当前存在事务,则把当前事务挂起。
  6. Propagation.NEVER:以⾮事务⽅式运⾏,如果当前存在事务,则抛出异常。
  7. Propagation.NESTED:如果当前存在事务,则创建⼀个事务作为当前事务的嵌套事务来运⾏;如果当前没有事务,则该取值等价于 PROPAGATION_REQUIRED。(局部考虑,局部事务出问题,只回滚这个局部事务,这个局部事务不会导致整体事务回滚)

774a2cda4e9446dfb6e8e612a0254fc2.png

进程和线程:

进程是一个正在执行的程序的实例,是一次动态执行的过程。是操作系统进行资源分配的基本单位,多个进程之间相互独立,并发执行。而线程则被称为轻量级的进程,这是因为进程创建、切换的开销较大,线程之间的切换的开销非常小,因此线程是任务调度的基本单位,一个进程中可以包含多个线程,线程共享进程的资源。

进程的三种状态:

  • 就绪态:当进程获取出CPU外所有的资源后,只要再获得CPU就能执行程序,这时的状态叫做就绪态。在一个系统中处于就绪态的进程会有多个,通常把这些排成一个队列,这个就叫就绪队列。
  • 运行态:当进程已获得CPU操作权限,正在运行,这个时间就是运行态。在单核系统中,同一个时间只能有一个运行态,多核系统中,会有多个运行态。
  • 阻塞态:正在执行的进程,等待某个事件而无法继续运行时,便被系统剥夺了CPU的操作权限,这时就是阻塞态。引起阻塞的原因有很多,比如:等待I/O操作、被更高的优先级的进程剥夺了CPU权限等。
     

线程的状态:

  • 新建状态(NEW):线程已创建,尚未调用start()方法启动之前。
  • 运行状态(RUNNABLE):线程对象被创建后,调用该对象的start()方法,并获取CPU权限进行执行。
  • 阻塞状态(BLOCKED):线程在获取synchronized同步锁失败(因为锁被其它线程所占用),它会进入同步阻塞状态。
  • 等待状态(WAITING ):等待状态。正在等待另一个线程执行特定动作来唤醒该线程的状态。
  • 超时等待状态(TIME_WAITING):有明确结束时间的等待状态。
  • 终止状态(TERMINATED ):当线程结束完成之后就会变成此状态。
     

线程和进程的区别

  • 一个进程可以包含多个线程,它们共享进程的资源。
  • 进程是独立的执行环境,而线程是进程内的独立执行单元。
  • 线程之间的切换开销相对较小,因为它们共享相同的地址空间。
  • 进程之间通信需要特殊的机制,而线程之间可以通过共享内存等更简单的方式进行通信。
  • 进程的崩溃不会影响其他进程,而线程之间的错误可能会影响整个进程。

如何避免空指针?

空指针异常,就是一个指针是空指针,你还要去操作它,既然它指向的是空对象,它就不能使用这个对象的方法。

所有集合对象在声明时即进行实例化,返回集合类型时,如果没有数据,返回空集合对象;将变量设置默认值,

对可能为空的变量增加提示信息

  1. 增加 Spring 注解 @NonNull@Nullable,IDE 会作异常提示 ;
  2. 在注释中标明参数不可为空,提醒调用者小心 NP

线程安全的理解:

当多个线程访问共享资源时,我们需要保证在这多种线程调度顺序的情况下, 代码的执行结果都是正确的, 只要有一种情况下, 代码的结果没有达到预期, 就认为线程是不安全的, 

如何保证线程安全?

1,synchronized

synchronized关键字,就是用来控制线程同步的,保证我们的线程在多线程环境下,不被多个线程同时执行,确保我们数据的完整性,使用方法一般是加在方法上。

2,lock

说说它跟synchronized有什么区别吧,Lock是在Java1.6被引入进来的,Lock的引入让锁有了可操作性,什么意思?就是我们在需要的时候去手动的获取锁和释放锁,甚至我们还可以中断获取以及超时获取的同步特性,但是从使用上说Lock明显没有synchronized使用起来方便快捷。

用锁会带来什么问题?

在Java中,使用锁(Lock)可以确保线程安全,避免多个线程同时访问共享资源导致的竞态条件。然而,使用锁也可能会带来一些问题:

  1. 死锁(Deadlock):当两个或多个线程永远等待对方释放资源时,就会发生死锁。这通常发生在当线程尝试以不同的顺序获取锁,或者一个线程持有多个锁而另一个线程等待这些锁时。
  2. 性能问题:锁的获取和释放需要时间,这可能会导致线程阻塞,从而影响程序的性能。在高并发场景下,如果锁的使用不当,可能会导致大量的线程阻塞,降低程序的吞吐量。
  3. 锁的粒度:在Java中,我们可以使用synchronized关键字或者ReentrantLock来实现锁。synchronized关键字提供的锁是对象级别的,而ReentrantLock提供的锁是线程级别的。选择合适的锁粒度可以提高并发性能,但也可能导致过度同步,降低性能。
  4. 锁的公平性:ReentrantLock允许你选择是否公平地分配锁。如果所有线程都按照相同的顺序请求锁,那么使用公平锁可以提高性能。

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值