listentry(在java中集合中迭代是怎么一回事儿)
资讯
2023-10-26
389
1. listentry,在java中集合中迭代是怎么一回事儿?
其实严格来说,迭代是一种设计模式,迭代的目的是遍历某一类数据集合的内容,而无需了解该类数据的数据结构。
其实现思路是:定义一个迭代器接口,该接口至少声明两个方法,分别是:hasNext(),和next()。通过hasNext()判断是否还有可遍历元素,通过next()返回可遍历元素。至于具体实现则根据不同需求创建不同实现类即可。
也就是说,迭代本没有编程语言属性,只不过几乎所有的编程语言都会支持迭代罢了。
在Java中遍历集合可以不用迭代方式,当然,不用迭代方式的遍历仅限于List接口的实现类。因为List接口规定可以通过集合索引来取得对应的值,不论这个List用的是数组还是链表实现。
但是,Java中的集合不仅仅只有List,还有Set。像这种非List集合是无法通过索引来获取指定元素的,所以这时候迭代方式就排上了用场。
除了List和Set之外,Java还有一种双列集合,又称key-value键值对集合。只不过这种集合遍历时无法直接通过迭代方式遍历。而是通过折中方式,要么先获得key值集合,通过遍历key来遍历map,要么是获得Map.Entry的集合来遍历。
总之,迭代方式不属于任何编程语言,它只是一种设计思想,只不过大多数编程语言会去实现它罢了,包括Java。
2. Java程序员面试中容易被问哪些问题?
由于我每年都会面试不少程序员,其中就包括相当数量的Java程序员,所以就这个问题,跟头条的广大网友交流一下。
Java工程师面试也是有一定的侧重点,做不同的研发方向,问题上也有所区别。另外,初级程序员与主力程序员的面试问题也有很大的不同,总的来说就是方向与定位相结合,下面为大家一一描述。
Java初级程序员面试面试Java初级程序员的流程相对来说比较简单,通常是两轮面试,首轮面试一般进行技术面试,次轮面试通常是有HR统一安排关于职业规划以及心理方面的面试(大部分情况是走个流程),这两轮面试中,技术面试起到了关键的作用。
面试初级程序员(2-3级)一般从技术细节入手,要通过面试,首先要知道初级程序员都需要具备哪些能力。下面先描述一下作为一个Java初级程序员(2-3级,Web方向为主)应该满足哪些要求:
清晰Java面向对象编程步骤,理解封装、继承、多态等核心概念,理解基本的前端内容(HTML、CSS、JavaScript)。
能理解程序流程图描述的功能需求,能独立完成功能模块的编写、部署,能理解功能模块之间的交互关系,能完成功能模块之间的消息传递和处理。
能理解MVC、DAO等框架,并在实际开发中熟练使用,能独立完成前后台的数据处理,能完成各种认证功能的处理。
能使用数据库连接池等基本优化方式提高程序运行效率。
能理解容器在程序运行中的作用,能配置各种容器的基本参数。
对程序的安全性有一定的理解,能避免初级的安全性问题。
能处理一定数量的并发问题,能解决多线程情况下的数据安全问题。
能理解不同角色的任务分配,能进行有效沟通。
常见框架的使用,比如SpringMVC等。
初级程序员的工作往往是编写程序基本功能组件,是保证程序质量的一个重要环节,所以团队的整体能力往往并不是由架构师决定的,反而是由众多的初级程序员决定的。
Java主力程序员面试主力程序员的面试通常要3轮或4轮,当然如果你走到了后面的两轮,基本上选择权就在你手里了,后面的两轮面试公司会给你安排一个看似比较美好的职业发展规划,因为主力程序员不仅要编码通常还承担者一部分管理工作(项目责任人),也是管理层比较倚重的。
主力程序员的技术面试流程并没有那么多细节问题,首先要了解一下做过哪些项目,在项目中都承担了什么任务,项目中遇到了哪些问题,怎么解决的等等。通常主力程序员的面试时间也比较长(2至3个小时),面试的氛围也比初级程序员要轻松很多。
主力程序员大部分是推荐上来的,有人力资源公司推荐的,也有公司内推上来的,当然也有主动投送简历要求面试的。一般对主力程序员都会有背景调查,这一方面是对公司负责,另一方面也是对应聘者应有的尊重。
希望通过我的回答,大家能对Java程序员的面试有一个基本的了解。
我是悟空问答的签约作者,致力于为头条网友提供优质的科技类问题解答,关注我,跟我一起交流关于科技类的问题吧。
3. 对hashmap按值排序怎么做?
我们都是HashMap的值是没有顺序的,他是按照key的HashCode来实现的。对于这个无序的HashMap我们要怎么来实现排序呢?参照TreeMap的value排序,我们一样的也可以实现HashMap的排序。
4. 辛德勒的名单片头曲?
辛德勒名单开头的插曲是《一步之遥》,Por Una Cabeza,这首华丽而高贵动人的探戈名曲,是阿根廷探戈舞曲的极致代表,也是全世界乐迷所最为熟知而深深爱上探戈旋律,是由阿根廷史上最负盛名的探戈歌手卡洛斯葛戴尔(Carlos Gardel)所作,别于近代阿根廷探戈音乐大师艾斯特皮耶左拉(Astor Piazzola)将探戈与西方古典、爵士音乐结合而创造出新现代的探戈组曲;卡洛斯‧葛戴尔作品所代表的意义是将探戈音乐歌曲化、将这个流传于阿根廷的民间音乐成功的推向巴黎及纽约等城市上流社会国际舞台的第一人,也是探戈音乐全盛成熟时期开始的代表人物,英俊潇洒的他更深受欧美地区国家的乐迷所热爱,是阿根廷探戈音乐世界里的猫王!创作出众多经典探戈名曲的卡洛斯葛戴尔拥有百张以上的唱片录音在全世界发行,即便在逝世的65年后的今天,他仍是阿根廷的音乐传奇,广受全世界乐迷的尊崇与喜爱!
5. Map和Collection的区别?
Map和Collection都是Java中常用的数据结构。它们的主要区别如下:
1. 对象的存储方式不同
Map是一种键值对映射的数据结构,存储的是一组键值对,每个键都对应唯一的值。
Collection是一种存储对象的数据结构,存储的是一组对象,没有键值对的概念。
2. 常见实现方式不同
Map的常见实现方式有HashMap、TreeMap、LinkedHashMap等,它们在内部实现上都不同,但在外部API上都提供了相同的操作方法。
Collection的常见实现方式有ArrayList、LinkedList、HashSet、TreeSet等,不同的实现方式在内部实现和外部行为上都有所不同。
3. 遍历方式不同
Map的遍历通常采用迭代器或者键值对的方式,例如for each、keySet()、values()方法等。
Collection的遍历则通常使用迭代器或者for each方式,例如Iterator、ListIterator、for each等。
4. 存放元素的方式不同
Map存放的元素是键值对,需要同时提供键和值,而Collection只需要提供元素即可。
5. 替换元素的方式不同
Map的替换元素一般采用put()方法,即将已有键的值更新为新值。
Collection则常常采用set()方法来替换元素,例如List集合中的set(int index, Object element)方法。
综上所述,Map和Collection虽然都是常用的Java数据结构,但它们在存储方式、常见实现、遍历方式、存放元素的方式和替换元素的方式等方面都有不同。开发者应根据具体需求选择恰当的数据结构来提高程序效率。
6. Lock和synchronized该如何选择?
synchronized和lock比较浅析
synchronized是基于jvm底层实现的数据同步,lock是基于Java编写,主要通过硬件依赖CPU指令实现数据同步。下面一一介绍
一、synchronized的实现方案
1.synchronized能够把任何一个非null对象当成锁,实现由两种方式:
a.当synchronized作用于非静态方法时,锁住的是当前对象的事例,当synchronized作用于静态方法时,锁住的是class实例,又因为Class的相关数据存储在永久带,因此静态方法锁相当于类的一个全局锁。
b.当synchronized作用于一个对象实例时,锁住的是对应的代码块。
2.synchronized锁又称为对象监视器(object)。 3.当多个线程一起访问某个对象监视器的时候,对象监视器会将这些请求存储在不同的容器中。
>Contention List:竞争队列,所有请求锁的线程首先被放在这个竞争队列中
>Entry List:Contention List中那些有资格成为候选资源的线程被移动到Entry List中
>Wait Set:哪些调用wait方法被阻塞的线程被放置在这里
>OnDeck:任意时刻,最多只有一个线程正在竞争锁资源,该线程被成为OnDeck
>Owner:当前已经获取到所资源的线程被称为Owner
> !Owner:当前释放锁的线程
下图展示了他们之前的关系
4.synchronized在jdk1.6之后提供了多种优化方案:
>自旋锁
jdk1.6之后默认开启,可以使用参数-XX:+UseSpinning控制,自旋等待不能代替阻塞,且先不说对处理器数量的要求,自旋等待本身虽然避免了线程切换的开销,但它是要占用处理器时间的,因此,如果锁被占用的时间很短,自旋等待的效果就会非常好,反之,如果锁被占用的时候很长,那么自旋的线程只会白白消耗处理器资源,而不会做任何有用的工作,反而会带来性能上的浪费。自旋次数的默认值是 10 次,用户可以使用参数 -XX:PreBlockSpin 来更改。
自旋锁的本质:执行几个空方法,稍微等一等,也许是一段时间的循环,也许是几行空的汇编指令。
>锁消除
即时编译器在运行时,对一些代码上要求同步,但是被检测到不可能存在共享数据竞争的锁进行消除,依据来源于逃逸分析的数据支持,那么是什么是逃逸分析?对于虚拟机来说需要使用数据流分析来确定是否消除变量底层框架的同步代码,因为有许多同步的代码不是自己写的。
例1.1
public static String concatString(String s1, String s2, String s3) { return s1 + s2 + s3; }
由于 String 是一个不可变的类,对字符串的连接操作总是通过生成新的 String 对象来进行的,因此 Javac 编译器会对 String 连接做自动优化。在 JDK 1.5 之前,会转化为 StringBuffer 对象的连续 append() 操作,在 JDK 1.5 及以后的版本中,会转化为 StringBuilder 对象的连续 append() 操作,这里的stringBuilder.append是线程不同步的(假设是同步)。
Javac 转化后的字符串连接代码为:
public static String concatString(String s1, String s2, String s3) { StringBuffer sb = new StringBuffer(); sb.append(s1); sb.append(s2); sb.append(s3); return sb.toString(); }
此时的锁对象就是sb,虚拟机观察变量 sb,很快就会发现它的动态作用域被限制在 concatString() 方法内部。也就是说,sb 的所有引用永远不会 “逃逸” 到concatString() 方法之外,其他线程无法访问到它,虽然这里有锁,但是可以被安全地消除掉,在即时编译之后,这段代码就会忽略掉所有的同步而直接执行了。
>锁粗化
将同步块的作用范围限制得尽量小——只在共享数据的实际作用域中才进行同步,这样是为了使得需要同步的操作数量尽可能变小,如果存在锁竞争,那等待锁的线程也能尽快拿到锁。
>轻量级锁
加锁过程:在代码进入同步块的时候,如果此同步对象没有被锁定(锁标志位为 “01” 状态)虚拟机首先将在当前线程的栈帧中建立一个名为锁记录(Lock Record)的空间,用于存储锁对象目前的 Mark Word 的拷贝,这时候线程堆栈与对象头的状态如图 13-3 所示
然后,虚拟机将使用 CAS 操作尝试将对象的 Mark Word 更新为指向 Lock Record 的指针。如果这个更新动作成功了,那么这个线程就拥有了该对象的锁,并且对象 Mark Word 的锁标志位 (Mark Word 的最后 2bit)将转变为 “00”,即表示此对象处于轻量级锁定状态,这时线程堆栈与对象头的状态如图13-4
如果上述更新操作失败,则说明这个锁对象被其他锁占用,此时轻量级变为重量级锁,标志位为“10”,后面等待的线程进入阻塞状态。
解锁过程:也是由CAS进行操作的,如果对象的 Mark Word 仍然指向着线程的锁记录,那就用 CAS 操作把对象当前的 Mark Word 和线程中复制的 Displaced Mark Word 替换回来,如果替换成功,整个同步过程就完成了。如果替换失败,说明有其他线程尝试过获取该锁,那就要释放锁的同时,唤醒被挂起的线程。
轻量级锁能提升程序同步性能的依据是 “对于绝大部分的锁,在整个同步周期内都是不存在竞争的”,这是一个经验数据。如果没有竞争,轻量级锁使用 CAS 操作避免了使用互斥量的开销,但如果存在锁竞争,除了互斥量的开销外,还额外发生了 CAS 操作,因此在有竞争的情况下,轻量级锁会比传统的重量级锁更慢。
>偏向锁
偏向锁也是 JDK 1.6 中引入的一项锁优化,它的目的是消除数据在无竞争情况下的同步原语,进一步提高程序的运行性能。如果说轻量级锁是在无竞争的情况下使用 CAS 操作去消除同步使用的互斥量,那偏向锁就是在无竞争的情况下把整个同步都消除掉,连 CAS 操作都不做了。
实质就是设置一个变量,判断这个变量是否是当前线程,是就避免再次加锁解锁操作,从而避免了多次的CAS操作。坏处是如果一个线程持有偏向锁,另外一个线程想争用偏向对象,拥有者想释放这个偏向锁,释放会带来额外的性能开销,但是总体来说偏向锁带来的好处还是大于CAS的代价的。在具体问题具体分析的前提下,有时候使用参数 -XX:-UseBiasedLocking 来禁止偏向锁优化反而可以提升性能。
二、lock的实现方案
与synchronized不同的是lock是纯java手写的,与底层的JVM无关。在java.util.concurrent.locks包中有很多Lock的实现类,常用的有ReenTrantLock、ReadWriteLock(实现类有ReenTrantReadWriteLock)
,其实现都依赖java.util.concurrent.AbstractQueuedSynchronizer类(简称AQS),实现思路都大同小异,因此我们以ReentrantLock作为讲解切入点。
分析之前我们先来花点时间看下AQS。AQS是我们后面将要提到的CountDownLatch/FutureTask/ReentrantLock/RenntrantReadWriteLock/Semaphore的基础,因此AQS也是Lock和Excutor实现的基础。它的基本思想就是一个同步器,支持获取锁和释放锁两个操作。
要支持上面锁获取、释放锁就必须满足下面的条件:
1、 状态位必须是原子操作的
2、 阻塞和唤醒线程
3、 一个有序的队列,用于支持锁的公平性
场景:可定时的、可轮询的与可中断的锁获取操作,公平队列,或者非块结构的锁。
主要从以下几个特点介绍:
1.可重入锁
如果锁具备可重入性,则称作为可重入锁。像synchronized和ReentrantLock都是可重入锁,可重入性在我看来实际上表明了锁的分配机制:基于线程的分配,而不是基于方法调用的分配。
2.可中断锁
可中断锁:顾名思义,就是可以相应中断的锁。
在Java中,synchronized就不是可中断锁,而Lock是可中断锁。
如果某一线程A正在执行锁中的代码,另一线程B正在等待获取该锁,可能由于等待时间过长,线程B不想等待了,想先处理其他事情,我们可以让它中断自己或者在别的线程中中断它,这种就是可中断锁。
3.公平锁和非公平锁
公平锁以请求锁的顺序来获取锁,非公平锁则是无法保证按照请求的顺序执行。synchronized就是非公平锁,它无法保证等待的线程获取锁的顺序。而对于ReentrantLock和ReentrantReadWriteLock,它默认情况下是非公平锁,但是可以设置为公平锁。
参数为true时表示公平锁,不传或者false都是为非公平锁。
ReentrantLock lock = new ReentrantLock(true);
4.读写锁
读写锁将对一个资源(比如文件)的访问分成了2个锁,一个读锁和一个写锁。
正因为有了读写锁,才使得多个线程之间的读操作不会发生冲突。
ReadWriteLock就是读写锁,它是一个接口,ReentrantReadWriteLock实现了这个接口。
可以通过readLock()获取读锁,通过writeLock()获取写锁。
三、总结
1.synchronized
优点:实现简单,语义清晰,便于JVM堆栈跟踪,加锁解锁过程由JVM自动控制,提供了多种优化方案,使用更广泛
缺点:悲观的排他锁,不能进行高级功能
2.lock
优点:可定时的、可轮询的与可中断的锁获取操作,提供了读写锁、公平锁和非公平锁
缺点:需手动释放锁unlock,不适合JVM进行堆栈跟踪
3.相同点
都是可重入锁
7. hashmap数据库使用方法?
一、添加方法
put方法,可以单次向HashMap中添加一个键值对。
注意:添加到Map中的数据,与List不一样,是没有顺序的。顺序是根据哈希算法得出的,不用深究。
putAll方法,可以把一个HashMap集合对象,整体加入到另外一个HashMap对象中。
注意:两个集合中重复的元素,会被覆盖为新的值。
二、删除方法
remove方法,可以单次删除一个元素。
注意:删除不存在的元素,不会报错。
三、遍历方法
EntrySet遍历方法,可以得到一个Entry对象的结果集,然后使用Entry对象的getKey和getValue方法。
KeySet遍历方法,先使用keySet函数,获取到HashMap的所有Key的集合对象,然后循环所有的key,通过HashMap的get方法,获取到对应的value。
四、查询方法
get方法,传入key,就可以查询到value。
本站涵盖的内容、图片、视频等数据系网络收集,部分未能与原作者取得联系。若涉及版权问题,请联系我们删除!联系邮箱:ynstorm@foxmail.com 谢谢支持!
1. listentry,在java中集合中迭代是怎么一回事儿?
其实严格来说,迭代是一种设计模式,迭代的目的是遍历某一类数据集合的内容,而无需了解该类数据的数据结构。
其实现思路是:定义一个迭代器接口,该接口至少声明两个方法,分别是:hasNext(),和next()。通过hasNext()判断是否还有可遍历元素,通过next()返回可遍历元素。至于具体实现则根据不同需求创建不同实现类即可。
也就是说,迭代本没有编程语言属性,只不过几乎所有的编程语言都会支持迭代罢了。
在Java中遍历集合可以不用迭代方式,当然,不用迭代方式的遍历仅限于List接口的实现类。因为List接口规定可以通过集合索引来取得对应的值,不论这个List用的是数组还是链表实现。
但是,Java中的集合不仅仅只有List,还有Set。像这种非List集合是无法通过索引来获取指定元素的,所以这时候迭代方式就排上了用场。
除了List和Set之外,Java还有一种双列集合,又称key-value键值对集合。只不过这种集合遍历时无法直接通过迭代方式遍历。而是通过折中方式,要么先获得key值集合,通过遍历key来遍历map,要么是获得Map.Entry的集合来遍历。
总之,迭代方式不属于任何编程语言,它只是一种设计思想,只不过大多数编程语言会去实现它罢了,包括Java。
2. Java程序员面试中容易被问哪些问题?
由于我每年都会面试不少程序员,其中就包括相当数量的Java程序员,所以就这个问题,跟头条的广大网友交流一下。
Java工程师面试也是有一定的侧重点,做不同的研发方向,问题上也有所区别。另外,初级程序员与主力程序员的面试问题也有很大的不同,总的来说就是方向与定位相结合,下面为大家一一描述。
Java初级程序员面试面试Java初级程序员的流程相对来说比较简单,通常是两轮面试,首轮面试一般进行技术面试,次轮面试通常是有HR统一安排关于职业规划以及心理方面的面试(大部分情况是走个流程),这两轮面试中,技术面试起到了关键的作用。
面试初级程序员(2-3级)一般从技术细节入手,要通过面试,首先要知道初级程序员都需要具备哪些能力。下面先描述一下作为一个Java初级程序员(2-3级,Web方向为主)应该满足哪些要求:
清晰Java面向对象编程步骤,理解封装、继承、多态等核心概念,理解基本的前端内容(HTML、CSS、JavaScript)。
能理解程序流程图描述的功能需求,能独立完成功能模块的编写、部署,能理解功能模块之间的交互关系,能完成功能模块之间的消息传递和处理。
能理解MVC、DAO等框架,并在实际开发中熟练使用,能独立完成前后台的数据处理,能完成各种认证功能的处理。
能使用数据库连接池等基本优化方式提高程序运行效率。
能理解容器在程序运行中的作用,能配置各种容器的基本参数。
对程序的安全性有一定的理解,能避免初级的安全性问题。
能处理一定数量的并发问题,能解决多线程情况下的数据安全问题。
能理解不同角色的任务分配,能进行有效沟通。
常见框架的使用,比如SpringMVC等。
初级程序员的工作往往是编写程序基本功能组件,是保证程序质量的一个重要环节,所以团队的整体能力往往并不是由架构师决定的,反而是由众多的初级程序员决定的。
Java主力程序员面试主力程序员的面试通常要3轮或4轮,当然如果你走到了后面的两轮,基本上选择权就在你手里了,后面的两轮面试公司会给你安排一个看似比较美好的职业发展规划,因为主力程序员不仅要编码通常还承担者一部分管理工作(项目责任人),也是管理层比较倚重的。
主力程序员的技术面试流程并没有那么多细节问题,首先要了解一下做过哪些项目,在项目中都承担了什么任务,项目中遇到了哪些问题,怎么解决的等等。通常主力程序员的面试时间也比较长(2至3个小时),面试的氛围也比初级程序员要轻松很多。
主力程序员大部分是推荐上来的,有人力资源公司推荐的,也有公司内推上来的,当然也有主动投送简历要求面试的。一般对主力程序员都会有背景调查,这一方面是对公司负责,另一方面也是对应聘者应有的尊重。
希望通过我的回答,大家能对Java程序员的面试有一个基本的了解。
我是悟空问答的签约作者,致力于为头条网友提供优质的科技类问题解答,关注我,跟我一起交流关于科技类的问题吧。
3. 对hashmap按值排序怎么做?
我们都是HashMap的值是没有顺序的,他是按照key的HashCode来实现的。对于这个无序的HashMap我们要怎么来实现排序呢?参照TreeMap的value排序,我们一样的也可以实现HashMap的排序。
4. 辛德勒的名单片头曲?
辛德勒名单开头的插曲是《一步之遥》,Por Una Cabeza,这首华丽而高贵动人的探戈名曲,是阿根廷探戈舞曲的极致代表,也是全世界乐迷所最为熟知而深深爱上探戈旋律,是由阿根廷史上最负盛名的探戈歌手卡洛斯葛戴尔(Carlos Gardel)所作,别于近代阿根廷探戈音乐大师艾斯特皮耶左拉(Astor Piazzola)将探戈与西方古典、爵士音乐结合而创造出新现代的探戈组曲;卡洛斯‧葛戴尔作品所代表的意义是将探戈音乐歌曲化、将这个流传于阿根廷的民间音乐成功的推向巴黎及纽约等城市上流社会国际舞台的第一人,也是探戈音乐全盛成熟时期开始的代表人物,英俊潇洒的他更深受欧美地区国家的乐迷所热爱,是阿根廷探戈音乐世界里的猫王!创作出众多经典探戈名曲的卡洛斯葛戴尔拥有百张以上的唱片录音在全世界发行,即便在逝世的65年后的今天,他仍是阿根廷的音乐传奇,广受全世界乐迷的尊崇与喜爱!
5. Map和Collection的区别?
Map和Collection都是Java中常用的数据结构。它们的主要区别如下:
1. 对象的存储方式不同
Map是一种键值对映射的数据结构,存储的是一组键值对,每个键都对应唯一的值。
Collection是一种存储对象的数据结构,存储的是一组对象,没有键值对的概念。
2. 常见实现方式不同
Map的常见实现方式有HashMap、TreeMap、LinkedHashMap等,它们在内部实现上都不同,但在外部API上都提供了相同的操作方法。
Collection的常见实现方式有ArrayList、LinkedList、HashSet、TreeSet等,不同的实现方式在内部实现和外部行为上都有所不同。
3. 遍历方式不同
Map的遍历通常采用迭代器或者键值对的方式,例如for each、keySet()、values()方法等。
Collection的遍历则通常使用迭代器或者for each方式,例如Iterator、ListIterator、for each等。
4. 存放元素的方式不同
Map存放的元素是键值对,需要同时提供键和值,而Collection只需要提供元素即可。
5. 替换元素的方式不同
Map的替换元素一般采用put()方法,即将已有键的值更新为新值。
Collection则常常采用set()方法来替换元素,例如List集合中的set(int index, Object element)方法。
综上所述,Map和Collection虽然都是常用的Java数据结构,但它们在存储方式、常见实现、遍历方式、存放元素的方式和替换元素的方式等方面都有不同。开发者应根据具体需求选择恰当的数据结构来提高程序效率。
6. Lock和synchronized该如何选择?
synchronized和lock比较浅析
synchronized是基于jvm底层实现的数据同步,lock是基于Java编写,主要通过硬件依赖CPU指令实现数据同步。下面一一介绍
一、synchronized的实现方案
1.synchronized能够把任何一个非null对象当成锁,实现由两种方式:
a.当synchronized作用于非静态方法时,锁住的是当前对象的事例,当synchronized作用于静态方法时,锁住的是class实例,又因为Class的相关数据存储在永久带,因此静态方法锁相当于类的一个全局锁。
b.当synchronized作用于一个对象实例时,锁住的是对应的代码块。
2.synchronized锁又称为对象监视器(object)。 3.当多个线程一起访问某个对象监视器的时候,对象监视器会将这些请求存储在不同的容器中。
>Contention List:竞争队列,所有请求锁的线程首先被放在这个竞争队列中
>Entry List:Contention List中那些有资格成为候选资源的线程被移动到Entry List中
>Wait Set:哪些调用wait方法被阻塞的线程被放置在这里
>OnDeck:任意时刻,最多只有一个线程正在竞争锁资源,该线程被成为OnDeck
>Owner:当前已经获取到所资源的线程被称为Owner
> !Owner:当前释放锁的线程
下图展示了他们之前的关系
4.synchronized在jdk1.6之后提供了多种优化方案:
>自旋锁
jdk1.6之后默认开启,可以使用参数-XX:+UseSpinning控制,自旋等待不能代替阻塞,且先不说对处理器数量的要求,自旋等待本身虽然避免了线程切换的开销,但它是要占用处理器时间的,因此,如果锁被占用的时间很短,自旋等待的效果就会非常好,反之,如果锁被占用的时候很长,那么自旋的线程只会白白消耗处理器资源,而不会做任何有用的工作,反而会带来性能上的浪费。自旋次数的默认值是 10 次,用户可以使用参数 -XX:PreBlockSpin 来更改。
自旋锁的本质:执行几个空方法,稍微等一等,也许是一段时间的循环,也许是几行空的汇编指令。
>锁消除
即时编译器在运行时,对一些代码上要求同步,但是被检测到不可能存在共享数据竞争的锁进行消除,依据来源于逃逸分析的数据支持,那么是什么是逃逸分析?对于虚拟机来说需要使用数据流分析来确定是否消除变量底层框架的同步代码,因为有许多同步的代码不是自己写的。
例1.1
public static String concatString(String s1, String s2, String s3) { return s1 + s2 + s3; }
由于 String 是一个不可变的类,对字符串的连接操作总是通过生成新的 String 对象来进行的,因此 Javac 编译器会对 String 连接做自动优化。在 JDK 1.5 之前,会转化为 StringBuffer 对象的连续 append() 操作,在 JDK 1.5 及以后的版本中,会转化为 StringBuilder 对象的连续 append() 操作,这里的stringBuilder.append是线程不同步的(假设是同步)。
Javac 转化后的字符串连接代码为:
public static String concatString(String s1, String s2, String s3) { StringBuffer sb = new StringBuffer(); sb.append(s1); sb.append(s2); sb.append(s3); return sb.toString(); }
此时的锁对象就是sb,虚拟机观察变量 sb,很快就会发现它的动态作用域被限制在 concatString() 方法内部。也就是说,sb 的所有引用永远不会 “逃逸” 到concatString() 方法之外,其他线程无法访问到它,虽然这里有锁,但是可以被安全地消除掉,在即时编译之后,这段代码就会忽略掉所有的同步而直接执行了。
>锁粗化
将同步块的作用范围限制得尽量小——只在共享数据的实际作用域中才进行同步,这样是为了使得需要同步的操作数量尽可能变小,如果存在锁竞争,那等待锁的线程也能尽快拿到锁。
>轻量级锁
加锁过程:在代码进入同步块的时候,如果此同步对象没有被锁定(锁标志位为 “01” 状态)虚拟机首先将在当前线程的栈帧中建立一个名为锁记录(Lock Record)的空间,用于存储锁对象目前的 Mark Word 的拷贝,这时候线程堆栈与对象头的状态如图 13-3 所示
然后,虚拟机将使用 CAS 操作尝试将对象的 Mark Word 更新为指向 Lock Record 的指针。如果这个更新动作成功了,那么这个线程就拥有了该对象的锁,并且对象 Mark Word 的锁标志位 (Mark Word 的最后 2bit)将转变为 “00”,即表示此对象处于轻量级锁定状态,这时线程堆栈与对象头的状态如图13-4
如果上述更新操作失败,则说明这个锁对象被其他锁占用,此时轻量级变为重量级锁,标志位为“10”,后面等待的线程进入阻塞状态。
解锁过程:也是由CAS进行操作的,如果对象的 Mark Word 仍然指向着线程的锁记录,那就用 CAS 操作把对象当前的 Mark Word 和线程中复制的 Displaced Mark Word 替换回来,如果替换成功,整个同步过程就完成了。如果替换失败,说明有其他线程尝试过获取该锁,那就要释放锁的同时,唤醒被挂起的线程。
轻量级锁能提升程序同步性能的依据是 “对于绝大部分的锁,在整个同步周期内都是不存在竞争的”,这是一个经验数据。如果没有竞争,轻量级锁使用 CAS 操作避免了使用互斥量的开销,但如果存在锁竞争,除了互斥量的开销外,还额外发生了 CAS 操作,因此在有竞争的情况下,轻量级锁会比传统的重量级锁更慢。
>偏向锁
偏向锁也是 JDK 1.6 中引入的一项锁优化,它的目的是消除数据在无竞争情况下的同步原语,进一步提高程序的运行性能。如果说轻量级锁是在无竞争的情况下使用 CAS 操作去消除同步使用的互斥量,那偏向锁就是在无竞争的情况下把整个同步都消除掉,连 CAS 操作都不做了。
实质就是设置一个变量,判断这个变量是否是当前线程,是就避免再次加锁解锁操作,从而避免了多次的CAS操作。坏处是如果一个线程持有偏向锁,另外一个线程想争用偏向对象,拥有者想释放这个偏向锁,释放会带来额外的性能开销,但是总体来说偏向锁带来的好处还是大于CAS的代价的。在具体问题具体分析的前提下,有时候使用参数 -XX:-UseBiasedLocking 来禁止偏向锁优化反而可以提升性能。
二、lock的实现方案
与synchronized不同的是lock是纯java手写的,与底层的JVM无关。在java.util.concurrent.locks包中有很多Lock的实现类,常用的有ReenTrantLock、ReadWriteLock(实现类有ReenTrantReadWriteLock)
,其实现都依赖java.util.concurrent.AbstractQueuedSynchronizer类(简称AQS),实现思路都大同小异,因此我们以ReentrantLock作为讲解切入点。
分析之前我们先来花点时间看下AQS。AQS是我们后面将要提到的CountDownLatch/FutureTask/ReentrantLock/RenntrantReadWriteLock/Semaphore的基础,因此AQS也是Lock和Excutor实现的基础。它的基本思想就是一个同步器,支持获取锁和释放锁两个操作。
要支持上面锁获取、释放锁就必须满足下面的条件:
1、 状态位必须是原子操作的
2、 阻塞和唤醒线程
3、 一个有序的队列,用于支持锁的公平性
场景:可定时的、可轮询的与可中断的锁获取操作,公平队列,或者非块结构的锁。
主要从以下几个特点介绍:
1.可重入锁
如果锁具备可重入性,则称作为可重入锁。像synchronized和ReentrantLock都是可重入锁,可重入性在我看来实际上表明了锁的分配机制:基于线程的分配,而不是基于方法调用的分配。
2.可中断锁
可中断锁:顾名思义,就是可以相应中断的锁。
在Java中,synchronized就不是可中断锁,而Lock是可中断锁。
如果某一线程A正在执行锁中的代码,另一线程B正在等待获取该锁,可能由于等待时间过长,线程B不想等待了,想先处理其他事情,我们可以让它中断自己或者在别的线程中中断它,这种就是可中断锁。
3.公平锁和非公平锁
公平锁以请求锁的顺序来获取锁,非公平锁则是无法保证按照请求的顺序执行。synchronized就是非公平锁,它无法保证等待的线程获取锁的顺序。而对于ReentrantLock和ReentrantReadWriteLock,它默认情况下是非公平锁,但是可以设置为公平锁。
参数为true时表示公平锁,不传或者false都是为非公平锁。
ReentrantLock lock = new ReentrantLock(true);
4.读写锁
读写锁将对一个资源(比如文件)的访问分成了2个锁,一个读锁和一个写锁。
正因为有了读写锁,才使得多个线程之间的读操作不会发生冲突。
ReadWriteLock就是读写锁,它是一个接口,ReentrantReadWriteLock实现了这个接口。
可以通过readLock()获取读锁,通过writeLock()获取写锁。
三、总结
1.synchronized
优点:实现简单,语义清晰,便于JVM堆栈跟踪,加锁解锁过程由JVM自动控制,提供了多种优化方案,使用更广泛
缺点:悲观的排他锁,不能进行高级功能
2.lock
优点:可定时的、可轮询的与可中断的锁获取操作,提供了读写锁、公平锁和非公平锁
缺点:需手动释放锁unlock,不适合JVM进行堆栈跟踪
3.相同点
都是可重入锁
7. hashmap数据库使用方法?
一、添加方法
put方法,可以单次向HashMap中添加一个键值对。
注意:添加到Map中的数据,与List不一样,是没有顺序的。顺序是根据哈希算法得出的,不用深究。
putAll方法,可以把一个HashMap集合对象,整体加入到另外一个HashMap对象中。
注意:两个集合中重复的元素,会被覆盖为新的值。
二、删除方法
remove方法,可以单次删除一个元素。
注意:删除不存在的元素,不会报错。
三、遍历方法
EntrySet遍历方法,可以得到一个Entry对象的结果集,然后使用Entry对象的getKey和getValue方法。
KeySet遍历方法,先使用keySet函数,获取到HashMap的所有Key的集合对象,然后循环所有的key,通过HashMap的get方法,获取到对应的value。
四、查询方法
get方法,传入key,就可以查询到value。
本站涵盖的内容、图片、视频等数据系网络收集,部分未能与原作者取得联系。若涉及版权问题,请联系我们删除!联系邮箱:ynstorm@foxmail.com 谢谢支持!