Java核心技术36讲
java平台的理解
谈谈你对 Java 平台的理解?“Java 是解释执行”,这句话正确么?
Java本身是一种面向对象的语音,最显著的特性有两个方面,一个是所谓的“书写一次,到处运行”(Write once,run anywhere),能够非常容易地获得跨平台能力;另一个就是垃圾收集(GC,Garbage Collection)
Java源代码,首先通过Javac编译成字节码(bytecode),然后在运行时,通过Java虚拟机内嵌的解释器将字节码转换为最终的机器码。但是常见的JVM,比如我们大多数情况下使用的Oracle JDK提供的Hotspot JVM,都提供了JIT(Just-In-Time)编译器,也就是常说的动态编译器,JIT能够在运行时将热点代码编译成机器码,这种情况下部分热点代码就属于编译执行,而不是解释执行了。Javac的编译,编译Java源码生成“.class”文件里面实际是字节码,而不是可以直接执行的机器码。Java通过字节码和JVM这种跨平台的抽象,屏蔽了操作系统和硬件的细节,这也是实现“一次编译,到处运行”的基础。
Exception和Error有什么区别?
throwable下分为error和exception
常见的error: OutofMemoryError,StackOveFlowError,NoClassDefFoundError,
exception:NullPointerException,runtimeException,classCastException等
实际开发中异常处理的两个原则:
尽量不要捕获类似 Exception 这样的通用异常,而是应该捕获特定异常
不要生吞(swallow)异常这是异常处理中要特别注意的事情,因为很可能会导致非常难以诊断的诡异情况
生吞异常,往往是基于假设这段代码可能不会发生,或者感觉忽略异常是无所谓的,但是千万不要在产品代码做这种假设
Throw early, catch late 原则
public void readPreferences(String filename) {Objects. requireNonNull(filename);//...perform other operations... InputStream in = new FileInputStream(filename);//...read the preferences file... }
强引用、软引用、弱引用、幻象引用有什么区别?具体使用场景是什么..
强引用:强引用,是我们最常见的普通对象引用,只要还有强引用指向一个对象,就能表明对象还活着,垃圾收集器不会碰这种对象。
软引用:是一种相对强引用弱化一些的引用,可以让对象豁免一些垃圾收集,只有当JVM认为内存不足时,才会去试图回收软引用指向的对象。软引用通常用来实现内存敏感的缓存,如果还有空闲内存,就可以暂时保留缓存,当内存不足时清理掉,这样就保证了使用缓存的同时,不会耗尽内存。软引用可以和一个引用队列(ReferenceQueue)联合使用,如果软引用所引用的对象被垃圾回收器回收,Java虚拟机就会把这个软引用加入到与之关联的引用队列中。后续,我们可以调用ReferenceQueue的poll()方法来检查是否有它所关心的对象被回收。如果队列为空,将返回一个null,否则该方法返回队列中前面的一个Reference对象
弱引用:并不能使对象豁免垃圾收集,仅仅是提供一种访问在弱引用状态下对象的途径。这就可以用来构建一种没有特定约束的关系,比如,维护一种非强制性的映射关系,如果试图获取时对象还在,就使用它,否则重新实例化。它同样是很多缓存实现的选择
虚引用:幻象引用,也叫虚引用,不能通过它来访问对象,用幻象引用监控对象的创建和销毁
对象可达性状态流转分析:
String、StringBuffer、StringBuilder有什么区别?
String 是 Java 语言非常基础和重要的类,提供了构造和管理字符串的各种基本逻辑。它是典型的 Immutable 类,被声明成为 final class,所有属性也都是 final 的。也由于它的不可变性,类似拼接、裁剪字符串等动作,都会产生新的 String 对象。由于字符串操作的普遍性,所以相关操作的效率往往对应用性能有明显影响
StringBuffer 本质是一个线程安全的可修改字符序列,它保证了线程安全,也随之带来了额外的性能开销。如果没有线程安全的需要,推荐使用它的后继者,也就是 StringBuilder。
字符串的设计和实现考量:StringBuffer 和 StringBuilder 底层都是利用可修改的(char,JDK 9 以后是 byte)数组,二者都继承了 AbstractStringBuilder,里面包含了基本操作,区别仅在于最终的方法是否加了 synchronized。目前的实现是,构建时初始字符串长度加 16,如果没有构建对象时输入最初的字符串,那么初始值就是 16。
String的演化:Java 的字符串在历史版本中使用 char 数组来存数据的,Java 中的 char 是两个 bytes 大小,拉丁语系语言的字符,根本就不需要太宽的 char,这样无区别的实现就造成了一定的浪费。密度是编程语言平台永恒的话题,因为归根结底绝大部分任务是要来操作数据的。Java 9 中,我们引入了 Compact Strings 的设计,对字符串进行了大刀阔斧的改进。将数据存储方式从 char 数组,改变为一个 byte 数组加上一个标识编码的所谓 coder,并且将相关字符串操作类都进行了修改。另外,所有相关的 Intrinsic 之类也都进行了重写,以保证没有任何性能损失。紧凑字符串带来的优势,即更小的内存占用、更快的操作速度。
很多字符串操作,比如 getBytes()/String(byte[] bytes) 等都是隐含着使用平台默认编码,这是一种好的实践吗?是否有利于避免乱码?
getBytes和String相关的转换时根据业务需要建议指定编码方式,如果不指定则看看JVM参数里有没有指定file.encoding参数,如果JVM没有指定,那使用的默认编码就是运行的操作系统环境的编码了,那这个编码就变得不确定了。常见的编码iso8859-1是单字节编码,UTF-8是变长的编码。
动态代理是基于什么原理?
反射,它就像是一种魔法,引入运行时自省能力,赋予了 Java 语言令人意外的活力,通过运行时操作元数据或对象,Java 可以灵活地操作运行时才能确定的信息。而动态代理,则是延伸出来的一种广泛应用于产品开发中的技术,很多繁琐的重复编程,都可以被动态代理机制优雅地解决
动态代理应用非常广泛,虽然最初多是因为 RPC 等使用进入我们视线,但是动态代理的使用场景远远不仅如此,它完美符合 Spring AOP 等切面编程。我在后面的专栏还会进一步详细分析 AOP 的目的和能力。简单来说它可以看作是对 OOP 的一个补充,因为 OOP 对于跨越不同对象或类的分散、纠缠逻辑表现力不够,比如在不同模块的特定阶段做一些事情,类似日志、用户鉴权、全局性异常处理、性能监控,甚至事务处理等,你可以参考下面这张图
AOP 通过(动态)代理机制可以让开发者从这些繁琐事项中抽身出来,大幅度提高了代码的抽象程度和复用度。从逻辑上来说,我们在软件设计和实现中的类似代理,如 Facade、Observer 等很多设计目的,都可以通过动态代理优雅地实现。
int和Integer有什么区别
自动装箱 / 自动拆箱是发生在什么阶段?
自动装箱实际上算是一种语法糖.Java 平台为我们自动进行了一些转换,保证不同的写法在运行时等价,它们发生在编译阶段,也就是生成的字节码是一致的
自动装箱 / 自动拆箱似乎很酷,在编程实践中,有什么需要注意的吗?
原则上,建议避免无意中的装箱、拆箱行为,尤其是在性能敏感的场合,创建 10 万个 Java 对象和 10 万个整数的开销可不是一个数量级的,不管是内存使用还是处理速度,光是对象头的空间占用就已经是数量级的差距了
扩展:使用原始数据类型、数组甚至本地代码实现等,在性能极度敏感的场景往往具有比较大的优势,用其替换掉包装类、动态数组(如 ArrayList)等可以作为性能优化的备选项。一些追求极致性能的产品或者类库,会极力避免创建过多对象。当然,在大多数产品代码里,并没有必要这么做,还是以开发效率优先
对比Vector、ArrayList、LinkedList有何区别?
Vector 是 Java 早期提供的线程安全的动态数组,如果不需要线程安全,并不建议选择,毕竟同步是有额外开销的。Vector 内部是使用对象数组来保存数据,可以根据需要自动的增加容量,当数组已满时,会创建新的数组,并拷贝原有数组数据
ArrayList 是应用更加广泛的动态数组实现,它本身不是线程安全的,所以性能要好很多。与 Vector 近似,ArrayList 也是可以根据需要调整容量,不过两者的调整逻辑有所区别,Vector 在扩容时会提高 1 倍,而 ArrayList 则是增加 50%
LinkedList 顾名思义是 Java 提供的双向链表,所以它不需要像上面两种那样调整容量,它也不是线程安全的
也可以补充一下不同容器类型适合的场景:
Vector 和 ArrayList 作为动态数组,其内部元素以数组形式顺序存储的,所以非常适合随机访问的场合。除了尾部插入和删除元素,往往性能会相对较差,比如我们在中间位置插入一个元素,需要移动后续所有元素。
而 LinkedList 进行节点插入、删除却要高效得多,但是随机访问性能则要比动态数组慢
在应用开发中,如果事先可以估计到,应用操作是偏向于插入、删除,还是随机访问较多,就可以针对性的进行选择
在 Java 8 之中,Java 平台支持了 Lambda 和 Stream,相应的 Java 集合框架也进行了大范围的增强,以支持类似为集合创建相应 stream 或者 parallelStream 的方法实现,我们可以非常方便的实现函数式代码。在 Java 9 中,Java 标准类库提供了一系列的静态工厂方法,比如,List.of()、Set.of(),大大简化了构建小的容器实例的代码量
ArrayList<String> list = new ArrayList<>();list.add("Hello");list.add("World"); //利用新的容器静态工厂方法 List<String> simpleList = List.of("Hello","world");
对比Hashtable、HashMap、TreeMap有什么不同?
Hashtable 是早期 Java 类库提供的一个哈希表实现,本身是同步的,不支持 null 键和值,由于同步导致的性能开销,所以已经很少被推荐使用。
HashMap 是应用更加广泛的哈希表实现,行为上大致上与 HashTable 一致,主要区别在于 HashMap 不是同步的,支持 null 键和值等。通常情况下,HashMap 进行 put 或者 get 操作,可以达到常数时间的性能,所以它是绝大部分利用键值对存取场景的首选
TreeMap 则是基于红黑树的一种提供顺序访问的 Map,和 HashMap 不同,它的 get、put、remove 之类操作都是 O(log(n))的时间复杂度,具体顺序可以由指定的 Comparator 来决定,或者根据键的自然顺序来判断。
hashmap扩容时为什么这里需要将高位数据移位到低位进行异或运算呢?
这是因为有些数据计算出的哈希值差异主要在高位,而 HashMap 里的哈希寻址是忽略容量以上的高位的,那么这种处理就可以有效避免类似情况下的哈希碰撞
为什么 HashMap 要树化呢?
本质上这是个安全问题。因为在元素放置过程中,如果一个对象哈希冲突,都被放置到同一个桶里,则会形成一个链表,我们知道链表查询是线性的,会严重影响存取的性能。
而在现实世界,构造哈希冲突的数据并不是非常复杂的事情,恶意代码就可以利用这些数据大量与服务器端交互,导致服务器端 CPU 大量占用,这就构成了哈希碰撞拒绝服务攻击,国内一线互联网公司就发生过类似攻击事件
解决哈希冲突的常用方法有:
开放定址:当关键字key的哈希地址p=H(key)出现冲突时,以p为基础,产生另一个哈希地址p1,如果p1仍然冲突,再以p为基础,产生另一个哈希地址p2,…,直到找出一个不冲突的哈希地址pi ,将相应元素存入其中。
再哈希法:这种方法是同时构造多个不同的哈希函数:
Hi=RH1(key) i=1,2,…,k当哈希地址Hi=RH1(key)发生冲突时,再计算Hi=RH2(key)……,直到冲突不再产生。这种方法不易产生聚集,但增加了计算时间。
链地址:这种方法的基本思想是将所有哈希地址为i的元素构成一个称为同义词链的单链表,并将单链表的头指针存在哈希表的第i个单元中,因而查找、插入和删除主要在同义词链中进行。链地址法适用于经常进行插入和删除的情况。
ConcurrentHashMap如何实现高效地线程安全?
Java 提供了不同层面的线程安全支持。在传统集合框架内部,除了 Hashtable 等同步容器,还提供了所谓的同步包装器(Synchronized Wrapper),我们可以调用 Collections 工具类提供的包装方法,来获取一个同步的包装容器(如 Collections.synchronizedMap),但是它们都是利用非常粗粒度的同步方式,在高并发情况下,性能比较低下。
另外,更加普遍的选择是利用并发包提供的线程安全容器类,它提供了:
各种并发容器,比如 ConcurrentHashMap、CopyOnWriteArrayList。
各种线程安全队列(Queue/Deque),如 ArrayBlockingQueue、SynchronousQueue。
各种有序容器的线程安全版本等。
具体保证线程安全的方式,包括有从简单的 synchronize 方式,到基于更加精细化的,比如基于分离锁实现的 ConcurrentHashMap 等并发实现等。具体选择要看开发的场景需求,总体来说,并发包内提供的容器通用场景,远优于早期的简单同步实现。
为什么需要 ConcurrentHashMap?
Hashtable 本身比较低效,因为它的实现基本就是将 put、get、size 等各种方法加上“synchronized”。简单来说,这就导致了所有并发操作都要竞争同一把锁,一个线程在进行同步操作时,其他线程只能等待,大大降低了并发操作的效率
Collections 提供的同步包装器:只是利用输入 Map 构造了另一个同步版本,所有操作虽然不再声明成为 synchronized 方法,但是还是利用了“this”作为互斥的 mutex,没有真正意义上的改进!
构造的时候,Segment 的数量由所谓的 concurrentcyLevel 决定,默认是 16
在JDK1.7中,ConcurrentHashMap采用Segment + HashEntry的方式进行实现,结构如下:
一个 ConcurrentHashMap 里包含一个 Segment 数组。Segment 的结构和HashMap类似,是一种数组和链表结构,一个 Segment 包含一个 HashEntry 数组,每个 HashEntry 是一个链表结构的元素,每个 Segment 守护着一个HashEntry数组里的元素,当对 HashEntry 数组的数据进行修改时,必须首先获得对应的 Segment的锁。 ———————————————— 版权声明:本文为CSDN博主「Java程序员-张凯」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。 原文链接:https://blog.csdn.net/qq_41701956/article/details/110119625
在JDK1.8中,放弃了Segment臃肿的设计,取而代之的是采用Node + CAS + Synchronized来保证并发安全进行实现,synchronized只锁定当前链表或红黑二叉树的首节点,这样只要hash不冲突,就不会产生并发,效率又提升N倍。
Java提供了哪些IO方式? NIO如何实现多路复用?
javaio分为:同步阻塞IO,在读取输入流或者写入输出流时,在读、写动作完成之前,线程会一直阻塞在那里,它们之间的调用是可靠的线性顺序。
优点:代码比较简单、直观,
缺点则是 IO 效率和扩展性存在局限性,容易成为应用性能的瓶颈。
java1.4引入了NIO框架,提供了Channel、Selector、Buffer等抽象,构建多路复用的、同步非阻塞 IO 程序,同时提供了更接近操作系统底层的高性能数据操作方式。
java7中引入了AIO,异步非阻塞 IO 方式。基于事件和回调机制。应用操作直接返回,而不会阻塞在那里,当后台处理完成,操作系统会通知相应线程进行后续工作。
BIO、NIO、NIO 2(AIO)
基础 API 功能与设计, InputStream/OutputStream 和 Reader/Writer 的关系和区别。
NIO、NI
O 2 的基本组成。
给定场景,分别用不同模型实现,分析 BIO、NIO 等模式的设计和实现原理。
NIO 提供的高性能数据操作方式是基于什么原理,如何使用?
或者,从开发者的角度来看,你觉得 NIO 自身实现存在哪些问题?有什么改进的想法吗?
概念:区分同步或异步(synchronous/asynchronous)。简单来说,同步是一种可靠的有序运行机制,当我们进行同步操作时,后续的任务是等待当前调用返回,才会进行下一步;而异步则相反,其他任务不需要等待当前调用返回,通常依靠事件、回调等机制来实现任务间次序关系。
区分阻塞与非阻塞(blocking/non-blocking)。在进行阻塞操作时,当前线程会处于阻塞状态,无法从事其他任务,只有当条件就绪才能继续,比如 ServerSocket 新连接建立完毕,或数据读取、写入操作完成;而非阻塞则是不管 IO 操作是否结束,直接返回,相应操作在后台继续处理。
IO 不仅仅是对文件的操作,网络编程中,比如 Socket 通信,都是典型的 IO 操作目标。
输入流、输出流(InputStream/OutputStream)是用于读取或写入字节的,例如操作图片文件。
而 Reader/Writer 则是用于操作字符,增加了字符编解码等功能,适用于类似从文件中读取或者写入文本信息。本质上计算机操作的都是字节,不管是网络通信还是文件读取,Reader/Writer 相当于构建了应用逻辑和原始数据之间的桥梁。
BufferedOutputStream 等带缓冲区的实现,可以避免频繁的磁盘读写,进而提高 IO 处理效率。这种设计利用了缓冲区,将批量数据进行一次操作,但在使用中千万别忘了 flush。
Java NIO 概览
首先,熟悉一下 NIO 的主要组成部分:
Buffer,高效的数据容器,除了布尔类型,所有原始数据类型都有相应的 Buffer 实现。
Channel,类似在 Linux 之类操作系统上看到的文件描述符,是 NIO 中被用来支持批量式 IO 操作的一种抽象。
File 或者 Socket,通常被认为是比较高层次的抽象,而 Channel 则是更加操作系统底层的一种抽象,这也使得 NIO 得以充分利用现代操作系统底层机制,获得特定场景的性能优化,例如,DMA(Direct Memory Access)等。不同层次的抽象是相互关联的,我们可以通过 Socket 获取 Channel,反之亦然。
Selector,是 NIO 实现多路复用的基础,它提供了一种高效的机制,可以检测到注册在 Selector 上的多个 Channel 中,是否有 Channel 处于就绪状态,进而实现了单线程对多 Channel 的高效管理。
Chartset,提供 Unicode 字符串定义,NIO 也提供了相应的编解码器等,例如,通过下面的方式进行字符串到 ByteBuffer 的转换
NIO能解决什么问题
场景:
public class DemoServer extends Thread {private ServerSocket serverSocket;public int getPort() {return serverSocket.getLocalPort();}public void run() {try {serverSocket = new ServerSocket(0);while (true) {Socket socket = serverSocket.accept();RequestHandler requestHandler = new RequestHandler(socket);requestHandler.start();}} catch (IOException e) {e.printStackTrace();} finally {if (serverSocket != null) {try {serverSocket.close();} catch (IOException e) {e.printStackTrace();};}}}public static void main(String[] args) throws IOException {DemoServer server = new DemoServer();server.start();try (Socket client = new Socket(InetAddress.getLocalHost(), server.getPort())) {BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(client.getInputStream()));bufferedReader.lines().forEach(s -> System.out.println(s));}}} // 简化实现,不做读取,直接发送字符串 class RequestHandler extends Thread {private Socket socket;RequestHandler(Socket socket) {this.socket = socket;}@Overridepublic void run() {try (PrintWriter out = new PrintWriter(socket.getOutputStream());) {out.println("Hello world!");out.flush();} catch (Exception e) {e.printStackTrace();}}}
稍微修正一下这个问题,我们引入线程池机制来避免浪费
serverSocket = new ServerSocket(0); executor = Executors.newFixedThreadPool(8);while (true) {Socket socket = serverSocket.accept();RequestHandler requestHandler = new RequestHandler(socket);executor.execute(requestHandler); }
如果连接数并不是非常多,只有最多几百个连接的普通应用,这种模式往往可以工作的很好。但是,如果连接数量急剧上升,这种实现方式就无法很好地工作了,因为线程上下文切换开销会在高并发时变得很明显,这是同步阻塞方式的低扩展性劣势.
NIO 则是利用了单线程轮询事件的机制,通过高效地定位就绪的 Channel,来决定做什么,仅仅 select 阶段是阻塞的,可以有效避免大量客户端连接时,频繁线程切换带来的问题,应用的扩展能力有了非常大的提高
NIO 引入的多路复用机制,提供了另外一种思路
public class NIOServer extends Thread {public void run() {try (Selector selector = Selector.open();ServerSocketChannel serverSocket = ServerSocketChannel.open();) {// 创建 Selector 和 ChannelserverSocket.bind(new InetSocketAddress(InetAddress.getLocalHost(), 8888));serverSocket.configureBlocking(false);// 注册到 Selector,并说明关注点serverSocket.register(selector, SelectionKey.OP_ACCEPT);while (true) {selector.select();// 阻塞等待就绪的 Channel,这是关键点之一Set<SelectionKey> selectedKeys = selector.selectedKeys();Iterator<SelectionKey> iter = selectedKeys.iterator();while (iter.hasNext()) {SelectionKey key = iter.next();// 生产系统中一般会额外进行就绪状态检查sayHelloWorld((ServerSocketChannel) key.channel());iter.remove();}}} catch (IOException e) {e.printStackTrace();}}private void sayHelloWorld(ServerSocketChannel server) throws IOException {try (SocketChannel client = server.accept();) { client.write(Charset.defaultCharset().encode("Hello world!"));}}// 省略了与前面类似的 main }
在 Java 7 引入的 NIO 2 中,又增添了一种额外的异步 IO 模式,利用事件和回调,处理 Accept、Read 等操作。 AIO 实现看起来是类似这样子:
AsynchronousServerSocketChannel serverSock = AsynchronousServerSocketChannel.open().bind(sockAddr); serverSock.accept(serverSock, new CompletionHandler<>() { // 为异步操作指定 CompletionHandler 回调函数@Overridepublic void completed(AsynchronousSocketChannel sockChannel, AsynchronousServerSocketChannel serverSock) {serverSock.accept(serverSock, this);// 另外一个 write(sock,CompletionHandler{})sayHelloWorld(sockChannel, Charset.defaultCharset().encode("Hello World!"));}// 省略其他路径处理方法... });
谈谈接口和抽象类有什么区别?
接口:接口是对行为的抽象,是抽象方法的集合。利用接口可以达到API定义和实现分离的目的。接口,不能实例化;不能包含任何非常量成员,任何 field 都是隐含着 public static final 的意义;同时,没有非静态方法实现,也就是说要么是抽象方法,要么是静态方法。实现接口用 implements关键字
抽象类:抽象类是不能实例化的类,用 abstract 关键字修饰 class,其目的主要是代码重用。其他和一般的java类没有太大区别,可有一个或者多个抽象方法也可以没有。用extends关键字继承。
Java 不支持多继承。java可以实现了多个接口,因为接口是抽象方法的集合。在一些情况下存在特定场景,需要抽象出与具体实现、实例化无关的通用逻辑,或者纯调用关系的逻辑,但是使用传统的抽象类会陷入到单继承的窘境。以往常见的做法是,实现由静态方法组成的工具类(Utils),比如 java.util.Collections。
为接口添加任何抽象方法,相应的所有实现了这个接口的类,也必须实现新增方法,否则会出现编译错误。对于抽象类,如果我们添加非抽象方法,其子类只会享受到能力扩展,而不用担心编译出问题
有一类没有任何方法的接口,通常叫作 Marker Interface,顾名思义,它的目的就是为了声明某些东西。这类似于注明annotation,对于 Annotation,因为可以指定参数和值,在表达能力上要更强大一些,所以更多人选择使用 Annotation。
Java 8 增加了函数式编程的支持,所以又增加了一类定义,即所谓 functional interface,简单说就是只有一个抽象方法的接口,通常建议使用 @FunctionalInterface Annotation 来标记。 Java 8 开始,interface 增加了对 default method 的支持。Java 9 以后,甚至可以定义 private default method.
public interface Collection<E> extends Iterable<E> {/*** Returns a sequential Stream with this collection as its source* ...**/default Stream<E> stream() {return StreamSupport.stream(spliterator(), false);}}
进行面向对象编程,掌握基本的设计原则是必须的,我今天介绍最通用的部分,也就是所谓的 S.O.L.I.D 原则。
单一职责(Single Responsibility),类或者对象最好是只有单一职责,在程序设计中如果发现某个类承担着多种义务,可以考虑进行拆分。
开关原则(Open-Close, Open for extension, close for modification),设计要对扩展开放,对修改关闭。换句话说,程序设计应保证平滑的扩展性,尽量避免因为新增同类功能而修改已有实现,这样可以少产出些回归(regression)问题。
里氏替换(Liskov Substitution),这是面向对象的基本要素之一,进行继承关系抽象时,凡是可以用父类或者基类的地方,都可以用子类替换。
接口分离(Interface Segregation),我们在进行类和接口设计时,如果在一个接口里定义了太多方法,其子类很可能面临两难,就是只有部分方法对它是有意义的,这就破坏了程序的内聚性。 对于这种情况,可以通过拆分成功能单一的多个接口,将行为进行解耦。在未来维护中,如果某个接口设计有变,不会对使用其他接口的子类构成影响。
依赖反转(Dependency Inversion),实体应该依赖于抽象而不是实现。也就是说高层次模块,不应该依赖于低层次模块,而是应该基于抽象。实践这一原则是保证产品代码之间适当耦合度的法宝。
synchronized和ReentrantLock有什么区别呢?
锁作为并发的基础工具之一,你至少需要掌握:
理解什么是线程安全。
synchronized、ReentrantLock 等机制的基本使用与案例。
更近一步,你还需要:
掌握 synchronized、ReentrantLock 底层实现;理解锁膨胀、降级;理解偏斜锁、自旋锁、轻量级锁、重量级锁等概念。
掌握并发包中 java.util.concurrent.lock 各种不同实现和案例分析。
线程安全:保证多线程环境下共享的、可修改的状态(数据)的正确性
线程安全需要保证几个基本特性:
原子性,简单说就是相关操作不会中途被其他线程干扰,一般通过同步机制实现。
可见性,是一个线程修改了某个共享变量,其状态能够立即被其他线程知晓,通常被解释为将线程本地状态反映到主内存上,volatile 就是负责保证可见性的。
有序性,是保证线程内串行语义,避免指令重排等
在JDk的API里对于join()方法是:
join
public final void join() throws InterruptedException Waits for this thread to die. Throws: InterruptedException - if any thread has interrupted the current thread. The interrupted status of the current thread is cleared when this exception is thrown.
即join()的作用是:“等待该线程终止”,这里需要理解的就是该线程是指的主线程等待子线程的终止。也就是在子线程调用了join()方法后面的代码,只有等到子线程结束了才能执行。也就是让外部线程等待该线程结束后再继续执行,如果不调用join()方法,则主线程不会等待子线程执行完,各执行各的。
public class ThreadSafeSample {public int sharedState;public void nonSafeAction() {while (sharedState < 100000) {int former = sharedState++;int latter = sharedState;if (former != latter - 1) {System.out.printf("Observed data race, former is " +former + ", " + "latter is " + latter);}}}public static void main(String[] args) throws InterruptedException {ThreadSafeSample sample = new ThreadSafeSample();Thread threadA = new Thread(){public void run(){sample.nonSafeAction();}};Thread threadB = new Thread(){public void run(){sample.nonSafeAction();}};threadA.start();threadB.start();threadA.join();threadB.join();} }
synchronized底层如何实现?什么是锁的升级、降级?
synchronized 代码块是由一对儿 monitorenter/monitorexit 指令实现的,Monitor 对象是同步的基本实现单元。
在 Java 6 之前,Monitor 的实现完全是依靠操作系统内部的互斥锁,因为需要进行用户态到内核态的切换,所以同步操作是一个无差别的重量级操作。
现代的(Oracle)JDK 中,JVM 对此进行了大刀阔斧地改进,提供了三种不同的 Monitor 实现,也就是常说的三种不同的锁:偏斜锁(Biased Locking)、轻量级锁和重量级锁,大大改进了其性能
所谓锁的升级、降级,就是 JVM 优化 synchronized 运行的机制,当 JVM 检测到不同的竞争状况时,会自动切换到适合的锁实现,这种切换就是锁的升级、降级。
synchronized 是 JVM 内部的 Intrinsic Lock,所以偏斜锁、轻量级锁、重量级锁的代码实现,并不在核心类库部分,而是在 JVM 的代码中。
线程自身的方法,除了 start,还有个 join 方法(等待线程结束);yield 是告诉调度器,主动让出 CPU;另外,就是一些已经被标记为过时的 resume、stop、suspend 之类,据我所知,在 JDK 最新版本中,destory/stop 方法将被直接移除。
Spring事务不起作用 问题汇总
spring事务@transactional注解不起作用
Java核心技术36讲相关推荐
- 【总结】Java核心技术36讲知识点大纲(转载)
Java核心技术36讲知识点总结大纲 1 Java平台的理解 Java的特性,解释运行和编译运行 2 Exception 和 Error 有什么区别 理解Java的异常体系的设计,Throwable ...
- 【总结】Java核心技术36讲知识点大纲
前段时间在极客时间上购买了杨晓峰老师的<Java核心技术36讲>,趁着这段时间有空,对相关知识点做了一个整体的大纲,也对自己所掌握的Java基础进行了一个复习和梳理,若想深入学习,可以购买 ...
- 杨晓峰-java核心技术36讲(学习笔记)- 第1讲 | 谈谈你对Java平台的理解?
杨晓峰-java核心技术36讲(学习笔记) 接下来我会分享杨晓峰-java核心技术36讲的学习笔记,内容较多,补充了其中一些牛人评论,相对详细(仅供个人学习记录整理,希望大家支持正版:https:// ...
- 读Java核心技术36讲有感——谈谈对Java的理解,谈谈Exception和Error
读过杨晓峰老师的36讲之后,想总结下自己的感想,写下来也有助于记忆,方便以后面试查阅和复习.题目所提到的话题本来是两讲,但是由于感想篇幅较短,所以合成一篇来写. 一.谈谈对Java平台的理解: 1.J ...
- 《java核心技术36讲》学习笔记-------杨晓峰(极客时间)
非常荣幸作为晓峰哥的同事,之前就看过这篇文章,重写读一遍,再学习学习.同时也推荐给大家 一.开篇词 初级.中级:java和计算机科学基础.开源框架的使用:高级.专家:java io/nio.并发.虚拟 ...
- Java核心技术36讲(个人整理)
今天我要问你的问题是,谈谈你对 Java 平台的理解? "Java 是解释执行",这句话正确吗? Java特性: 面向对象(封装,继承,多态) 平台无关性(JVM运行.class文 ...
- Java核心技术36讲 第一讲:Java平台的理解
java语言 一次编译,到处运行 GC.Garbage Collection JRE: Java Runtime Environment 包含JVM和Java类库等 JDK: Java Develop ...
- 杨晓峰Java核心36讲学习笔记
最近在极客时间上订阅了Oracle首席工程师杨晓峰的Java核心技术36讲, 接下来会对每一课的学习: 记下学习笔记 有不懂的地方继续深入 一些思考或者总结. 下面从第一课开始,Exception和E ...
- java核心技术精讲-李兴华-专题视频课程
java核心技术精讲-101993人已学习 课程介绍 本课程主要读者全面细致的讲解Java编程的所有核心知识,从基础语法.到面向对象以及Java的实际应用进行完整讲解.官方QQ群:61 ...
最新文章
- join left 大数据_Java并发编程笔记-JDK内置并行执行框架Fork/Join
- Cpp 对象模型探索 / 虚基类表作用
- pm2 start 带参数_3款有海景天窗的国产SUV,最适合带女朋友看星星,首付3万拿下...
- ubuntu16 安装opencv
- ln命令:软链接建立与删除
- linux服务器知识学习:了解Linux系统的启动过程
- ActiveRecord::Fixture::FixtureError: table users has no column named activated_at.
- eclipse debug 的断点查看和清除
- M1 Macbook安装MATLAB
- 计算机三级权限管理方法,Serv―U 三级用户权限机制及配置一例
- 从外网到域控(vulnstack靶机实战一)
- Android课程表显示
- 第五回:样式色彩秀芳华
- (3.1E)Shortest Distance (20)
- 百度云原生产品 6 月刊 | CCE 节点组支持配置多个备选机型、CCR 新增镜像加速功能
- c++版本的高斯混合模型的源代码完全注释
- 《无响应,是否重启人生?》程序人生征文结果
- mysql CONFLICT 冲突
- change事件做延迟处理
- 苹果刷机有好处和坏处_苹果更新要通电源吗