程序员求职经验分享与学习资料整理平台

网站首页 > 文章精选 正文

Java学习总结 2020/4/10 学java课程的心得体会和收获

balukai 2024-12-26 11:39:06 文章精选 25 ℃

21.什么是线程,怎么创建一个线程

线程(thread)是操作系统能够进行运算调度的最小单位,一个线程就是一个单一顺序的控制流。

注意与进程的区别:进程是指在系统中正在运行的一个应用程序,一个程序下至少有一个进程,一个进程下也可以有多个线程,每个进程有自己各自的变量,而线程间共享数据。

创建线程方式:

·继承Thread类并重新run方法

·实现Runnable接口,执行方法是run()

·实现Callable接口,执行方法是call()

Runnable和Callable区别:

·是否有返回值:Callable能返回执行结果;而Runnable不能返回结果

·是否会抛出异常:Callable接口的call()方法允许抛出异常;而Runnable接口的run()方法的异常只能在内部消化,不能继续上抛

22.线程的生命周期

新建:创建线程对象

就绪:有执行资格,没有执行权

运行:既有执行资格,又有执行权

阻塞:没有执行资格,没有执行权

死亡:线程对象变成垃圾,等待回收

注意sleep和wait的区别:

·来自的类不同:sleep来自Thread类,而wait来自Object类

·是否释放锁:sleep不释放锁,而wait方法释放了锁。sleep不出让系统资源,使用wait当前线程会进入线程等待池等待,出让系统资源,其他线程可以占用CPU。

·使用范围不同:sleep方法可以在任何地方使用,wait方法只能在同步方法和同步代码块中使用

23.保证多线程运行安全的方法

·尽量使用一些线程安全类和Atomic开头的原子类

·使用final修饰,不可变的对象一定是线程安全的

·使用ThreadLocal让变量在每个线程中都保存一份副本,避免数据在多线程中共享

·使用volatile修饰关键的需要共享的变量

·使用CAS比较比较并交换

·加锁:

·自动锁synchronize关键字

·手动锁ReentrantLock类

public static void main(String[] args) {

Lock myLock = new ReentrantLock();

myLock.lock();

...

myLock.unlock();

}

注意:CAS的缺点

·CAS失败时,会一直进行尝试。如果CAS长时间一直不成功,会给CPU带来很大的开销。

·只能保证一个共享变量的原子操作

·ABA问题:如果内存中的V和A相同,就更新V为新的预期值B,但这并不代表内存中V一直没变过。可能V的值曾经被其他线程改变为B,又变回了A,再被比较,CAS就会误认为它从来没有被改变过。Java并发包为了解决这个问题,提供了一个带有标记的原子引用类“AtomicStampedReference”,它可以通过控制变量值的版本来保证CAS的正确性。

24.synchronized实现原理

synchronized也要分为两个版本分析,在Java6之前,synchronized是重量级锁,对于系统开销太大,Java6对synchronized进行了大幅优化,提供了三种锁机制,大大提高了其性能。

Java 虚拟机中的synchronized是基于进入(monitorenter)和退出管程(monitorexit)实现。但在方法上添加synchronized是特例,JVM是调用在常量池中ACC_SYNCHRONIZED 标志来隐式实现的。

我们先需要了解一下synchronized锁对象的存在:

对象存在堆内存中,主要包括三个部分:对象头,实例变量和填充数据

对象头主要又由两个部分组成:Markword和Classpoint,每个部分储存内容见下表:


Markword中就记录着锁的类型是无锁、偏向锁、轻量级锁还是重量级锁,在Java6之前只有重量级锁。

对于Java6之前的synchronized来说,只有重量级锁,主要通过monitorenter和monitorexit来实现,每个对象在创建时都会同时创建自己的monitor监视器,当一个 monitor 被某个线程持有后,它便处于锁定状态,每个monitor各自维护了一个计数器count,调用monitorenter就是尝试获取这个对象,如果count!=0,就阻塞,等待锁释放;如果count=0,则该线程获取成功,count数加1,如果重复进入,则继续加1,线程退出就执行monitorexit,count数减1。

因为monitor是依赖于底层的操作系统来实现的,而操作系统实现线程之间的切换时需要从用户态转换到核心态,时间开销太大,所以Java6之后,对synchronized进行了大幅的优化,下面讲一下synchronized锁升级过程。

25.锁升级过程

锁升级的过程包括无锁、偏向锁、轻量级锁还有重量级锁,其中锁只能升级或者释放为无锁,不会出现锁的降级。

·无锁:没有对资源进行锁定,所有线程都可以访问修改,但只有一个锁能成功

·偏向锁:当锁竞争不强时,通常一个线程会多次获取同一个锁,此时偏向锁可以减少获取锁的代价。

当线程1访问并获取锁时,会在对象头和值栈记录线程1的ThreadID,由于偏向锁不会主动释放锁,又有线程来访问锁时,会比较这个线程的ID和自己对象头里的ThreadID是否一致,如果一致,则不需要加锁解锁,如果不一致,就会先去查看线程1是否存活,如果存活再查找值栈信息,如果线程1还需要持有这个对象,就升级为轻量级锁,不需要就变成无锁

·轻量级锁:线程1升级为轻量级锁后,就会把锁对象的Markword拷贝一份到自己的值栈中,然后CAS 操作把对象头中的内容替换为自己值栈的指针,如果存在竞争激烈,CAS操作失败,就会自旋尝试获取锁

·重量级锁:自旋一定次数后依旧获取不到锁时,就升级为重量级锁

最近发表
标签列表