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讲相关推荐

  1. 【总结】Java核心技术36讲知识点大纲(转载)

    Java核心技术36讲知识点总结大纲 1 Java平台的理解 Java的特性,解释运行和编译运行 2 Exception 和 Error 有什么区别 理解Java的异常体系的设计,Throwable ...

  2. 【总结】Java核心技术36讲知识点大纲

    前段时间在极客时间上购买了杨晓峰老师的<Java核心技术36讲>,趁着这段时间有空,对相关知识点做了一个整体的大纲,也对自己所掌握的Java基础进行了一个复习和梳理,若想深入学习,可以购买 ...

  3. 杨晓峰-java核心技术36讲(学习笔记)- 第1讲 | 谈谈你对Java平台的理解?

    杨晓峰-java核心技术36讲(学习笔记) 接下来我会分享杨晓峰-java核心技术36讲的学习笔记,内容较多,补充了其中一些牛人评论,相对详细(仅供个人学习记录整理,希望大家支持正版:https:// ...

  4. 读Java核心技术36讲有感——谈谈对Java的理解,谈谈Exception和Error

    读过杨晓峰老师的36讲之后,想总结下自己的感想,写下来也有助于记忆,方便以后面试查阅和复习.题目所提到的话题本来是两讲,但是由于感想篇幅较短,所以合成一篇来写. 一.谈谈对Java平台的理解: 1.J ...

  5. 《java核心技术36讲》学习笔记-------杨晓峰(极客时间)

    非常荣幸作为晓峰哥的同事,之前就看过这篇文章,重写读一遍,再学习学习.同时也推荐给大家 一.开篇词 初级.中级:java和计算机科学基础.开源框架的使用:高级.专家:java io/nio.并发.虚拟 ...

  6. Java核心技术36讲(个人整理)

    今天我要问你的问题是,谈谈你对 Java 平台的理解? "Java 是解释执行",这句话正确吗? Java特性: 面向对象(封装,继承,多态) 平台无关性(JVM运行.class文 ...

  7. Java核心技术36讲 第一讲:Java平台的理解

    java语言 一次编译,到处运行 GC.Garbage Collection JRE: Java Runtime Environment 包含JVM和Java类库等 JDK: Java Develop ...

  8. 杨晓峰Java核心36讲学习笔记

    最近在极客时间上订阅了Oracle首席工程师杨晓峰的Java核心技术36讲, 接下来会对每一课的学习: 记下学习笔记 有不懂的地方继续深入 一些思考或者总结. 下面从第一课开始,Exception和E ...

  9. java核心技术精讲-李兴华-专题视频课程

    java核心技术精讲-101993人已学习 课程介绍         本课程主要读者全面细致的讲解Java编程的所有核心知识,从基础语法.到面向对象以及Java的实际应用进行完整讲解.官方QQ群:61 ...

最新文章

  1. join left 大数据_Java并发编程笔记-JDK内置并行执行框架Fork/Join
  2. Cpp 对象模型探索 / 虚基类表作用
  3. pm2 start 带参数_3款有海景天窗的国产SUV,最适合带女朋友看星星,首付3万拿下...
  4. ubuntu16 安装opencv
  5. ln命令:软链接建立与删除
  6. linux服务器知识学习:了解Linux系统的启动过程
  7. ActiveRecord::Fixture::FixtureError: table users has no column named activated_at.
  8. eclipse debug 的断点查看和清除
  9. M1 Macbook安装MATLAB
  10. 计算机三级权限管理方法,Serv―U 三级用户权限机制及配置一例
  11. 从外网到域控(vulnstack靶机实战一)
  12. Android课程表显示
  13. 第五回:样式色彩秀芳华
  14. (3.1E)Shortest Distance (20)
  15. 百度云原生产品 6 月刊 | CCE 节点组支持配置多个备选机型、CCR 新增镜像加速功能
  16. c++版本的高斯混合模型的源代码完全注释
  17. 《无响应,是否重启人生?》程序人生征文结果
  18. mysql CONFLICT 冲突
  19. change事件做延迟处理
  20. 苹果刷机有好处和坏处_苹果更新要通电源吗

热门文章

  1. 前端学习(1611):redux使用
  2. java学习(92):线程的创建方法一
  3. Linux守护进程的创建(结合nginx框架)
  4. 01-几种应用上下文区别
  5. 知乎问答:现在程序员的工资是不是被高估了?
  6. django之快速分页
  7. bsgs(Baby Steps Giant Steps)算法
  8. 数学入门题——《算法竞赛入门经典-训练指南》
  9. MongoDB分析工具之三:db.currentOp()
  10. WebApi权限验证流程的设计和实现