Fork me on GitHub

走进JVM Java内存布局

原文来自《码出高效 Java开发手册》

写的非常好,推荐购买

内存是非常重要的系统资源 , 是硬盘和 CPU 的中 间仓库及桥梁 , 承载着操作系统和应用程序的实时运行。 JVM 内存布局规定了 Java 在运行过程中内存申请、分配、管理的策略 ,保证了 JVM 的高效稳定运行。不同的 JVM 对于内存的划分方式和管理机制存在着部分差异。结合 JVM 虚拟机规范 , 来探讨一下经典的 NM 内存布局 , 如图所示。

1. Heap (堆区)

Heap 是 OOM 故障最主要的发源地,它存储着几乎所有实例对象,堆由垃圾收集器自动回收,堆区由各子线程共享使用。通常情况下,它占用的空间是所有内存区域中最大的,但如果无节制地创建大量对象,也容易消耗完所有的空间。堆的内存空间即可以固定大小,也可以在运行时动态地调整,通过如下参数设定初始值和最大值,比如 -Xms256M -Xmx1024M,其中 -X 表示它是 JVM 运行参数, ms 是 memory start 的简称, mx 是 memory max 的简称,分别代表最小堆容量和最大堆容量。但是在通常情况下,服务器在运行过程中,堆空间不断地扩容与回缩,势必形成不必要的系统压力 , 所以在线上生产环境中 , JVM 的 Xms 和 Xmx 设置成一样大小,避免在 GC 后调整堆大小时带来的额外压力 。

堆分成两大块 新生代和老年代。对象产生之初在新生代 , 步入暮年时进入老年 代 , 但是老年代也接纳在新生代无法容纳的超大对象。新生代= 1 个 Eden 区+ 2 个 Survivor 区。绝大部分对象在 Eden 区生成 , 当 Eden 区装填满的时候 , 会触发 Young Garbage Collection , 即 YGC。垃圾回收的时候 , 在 Eden 区实现清除策略 , 没有被引用的对象则直接回收。依然存活的对象会被移送到 Survivor 区 , 这个区真是名副其实的存在。 Survivor 区分为 S0 和 S1 两块内存空间 ,送到哪块空间呢?每次 YGC 的 时候, 它们将存活的对象复制到未使用的那块空间,然后将当前正在使用的空间完全清除 , 交换两块空间的使用状态。如果 YGC 要移送的对象大于 Survivor 区容量的上限 ,则直接移交给老年代。假如一些没有进取心的对象以为可以一直在新生代的 Survivor 区交换来交换去,那就错了。每个对象都有一个计数器,每次 YGC 都会加 1 。 -XX:MaxTenuringThreshold 参数能配置计数器的值到达某个阀值的时候 , 对象从新生代晋升至老年代。如果该参数配置为 1,那么从新生代的 Eden 区直接移至老年代。 默认值是 15 , 可以在 Survivor 区交换 14 次之后 , 晋升至老年代。 对象晋升流程图如图所示。

图 中,如果 Survivor 区无法放下, 或者超大对象的闹值超过上限, 则尝试在老年代中进行分配 ; 如果老年代也无法放下, 则会触发 Full Garbage Collection , 即 FGC。 如果依然无法放下, 则抛出 OOM。 堆内存出现 OOM 的概率是所有内存耗尽 异常中最高的。 出错时的堆内信息对解决问题非常有帮助 , 所以给 JVM 设置运行参数 -XX:+HeapDumpOnOutOfMemoryError ,让 JVM 遇到 OOM 异常时能输出堆内信息,特别是对相隔数月才出现的 OOM 异常尤为重要。

在不同的 JVM 实现及不同的回收机制中 , 堆内存的划分方式是不一样的。

2. Metaspace (元空间)

本书源码解析和示例代码基本采用 JDK11 版本, JVM 则 为 Hotspot。 早在 JDK8 版本中, 元空间的前身 Perm 区已经被淘汰。 在 JDK7 及之前的版本中, 只有 Hotspot 才有 Perm 区,译为永久代 , 它在启动时固定大小, 很难进行调优, 并且 FGC 时会移动类元信息。 在某些场景下, 如果动态加载类过多, 容易产生 Perm 区的 OOM。 比如某个实际 Web 工程中, 因为功能点比较多, 在运行过程中, 要不断动态加载很多的类, 经常出现致命错误:

1
"Exception in thread 'dubbo client x.x connector' java.lang.OutOfMemoryError: PermGenspace"

为了解决该问题 ,需要设定运行参数 -XX:MaxPermSize=1280m ,如果部署到新机器上, 往往会因为 JVM 参数没有修改导致故障再现。 不熟悉此应用的人排查问题时往往苦不堪言, 除此之外, 永久代在垃圾回收过程中还存在诸多问题。 所 以, JDK8 使用元空间替换永久代。 在 JDK8 及以上版本中, 设定 MaxPermSize 参 数, JVM 在启动时并不会报锚, 但是会提示 : Java HotSpot 64Bit Server VM warning: ignoring option MaxPem1Size=2560m; support was removed in 8.0。

区别于永久代 , 元空间在本地内存中分配。 在 JDK8 里, Perm 区中的所有内容中字符串常量移至堆内存, 其他内容包括类元信息、字段、静态属性、方法、常量等都移动至元空间内, 比如图 4-10 中的 Object 类元信息、静态属性 System.out、整型常量 10000000 等。图 4-10 中显示在常量池中的 Strirg, 其实际对象是被保存在堆内 存中的。

3. JVM Stack ( 虚拟机栈)

栈( Stack )是一个先进后出的数据结构 , 就像子弹的弹夹 , 最后压入的子弹先发射 , 压在底部的子弹最后发射 , 撞针只能访问位于顶部的那一颗子弹。

相对于基于寄存器的运行环境来说 ,JVM 是基于栈结构的运行环境。栈结构移植性更好 ,可控性更强。JVM 中的虚拟机栈是描述 Java 方法执行的内存区域,它是线程私有的。栈中的元素用于支持虚拟机进行方法调用 , 每个方法从开始调用到执行完成的过程 , 就是栈帧从入栈到出栈的过程。在活动线程中 , 只有位于栈顶的帧才是有效的 , 称为当前栈帧。正在执行的方法称为当前方法 , 栈帧是方法运行的基本结构。 在执行引擎运行时 , 所有指令都只能针对当前栈帧进行操作。而 StackOverflow Error 表示请求的栈溢出 , 导致内存耗尽 , 通常出现在递归方法中。 JVM 能够横扫干军 , 虚拟机栈就是它的心腹大将 , 当前方法的栈帧 , 都是正在战斗的战场 , 其中的操作栈是参与战斗的士兵。操作栈的压栈与出栈如图所示。

虚拟机栈通过压栈和出栈的方式, 对每个方法对应的活动栈帧进行运算处理, 方法正常执行结束, 肯定会跳转到另一个栈帧上。 在执行的过程中, 如果出现异常, 会进行异常回溯, 返回地址通过异常处理表确定。 栈帧在整个 JVM 体系中的地位颇高, 包括局部变量表、操作栈、动态连接、方法返回地址等。

(1)局部变量表

局部变量表是存放方法参数和局部变量的区域。 相对于类属性变量的准备阶段和初始化阶段来说, 局部变量没有准备阶段, 必须显式初始化。 如果是非静态方法, 则在 index[0]位置上存储的是方法所属对象的实例引用, 随后存储的是参数和局部变量。 字节码指令中的 STORE 指令就是将操作栈中计算完成的局部变量写回局部变量表的 存储空间内。

###(2 )操作栈

操作栈是一个初始状态为空的桶式结构栈。在方法执行过程中,会有各种指令往栈中写入和提取信息。JVM 的执行引擎是基于栈的执行引擎,其中的栈指的就是操作栈。字节码指令集的定义都是基于栈类型的,栈的深度在方法元信息的 stack 属性中,下面用一段简单的代码说明操作栈与局部变量表的交互:

1
2
3
4
5
6
7
public int simpleMethod() {
int x= 13;
int y = 14;
int z = x + y;

return z;
}

详细的字节码操作顺序如下 :

第1处说明:局部变量表就像一个中药柜 , 里面有很多抽屉 , 依次编号为 0, 1, 2, 3, … , n , 字节码指令 ISTORE_1 就是打开 1 号抽屉 , 把栈顶中的数 13 存进去。栈是一个很深的竖桶, 任何时候只能对桶口元素进行操作 ,所以数据只能在栈顶进行存取。某些指令可以直接在抽屉里进行 , 比如 iinc 指令 , 直接对抽屉里的数值进行 +1 操作。 程序员面试过程中 , 常见的 i++ 和 ++i 的区别 ,可以从字节码上对比出来 ,如 表 4-1 所示。

在表 4-1 左列中 ,iload l 从局部变量表的第 1 号抽屉里取出一个数 ,压入栈顶 , 下一步直接在抽屉里实现 +1 的操作 , 而这个操作对栈元素的值没有影响。 所以 istore_2 只是把栈顶元素赋值给 a ,表格右列 , 先在第 1 号抽屉里执行 +1 操作 , 然后通过 iload_1 把第 1 号抽屉里的数压人栈顶, 所以 istore_2 存人的是 +1 之后的值。

这里延伸一个信息 , i++ 并非原子操作。 即使通过 volatile 关键字进行修饰 , 多个线程同时写的话 , 也会产生数据互相覆盖的问题。

(3 )动态连接

每个栈帧中包含一个在常量池中对当前方法的引用 ,目的是支持方法调用过程的动态连接。

(4)方法返回地址

方法执行时有两种退出情况:

第一,正常退出,即正常执行到任何方法的返回字节码指令,如RETURN、 IRETURN 、 ARETURN等;

第二,异常退出。无论何种退出情况,都将返回至方法当前被调用的位置。方法退出的过程相当于弹出当前栈帧,退出可能有三种方式:

  • 返回值压入上层调用栈帧。
  • 异常信息抛给能够处理栈帧。
  • PC 计数器指向方法调用后的下一条指令。

4. Native Method Stacks(本地方法栈)

本地方法栈( Native Method Stack )在 JVM 内存布局中 ,也是线程对象私有的,但是虚拟机栈“主内 ”, 而本地方法栈”主外“。这个“内外 ”是针对 JVM 来说的,本地方法栈为 Native 方法服务。线程开始调用本地方法时,会进入一个不再受 JVM 约束的世界。本地方法可以通过 JNI ( Java Native Interface )来访问虚拟机运行时的数据区 ,甚至可以调用寄存器,具有和 JVM 相同的能力和权限。当大量本地方法出现时 势必会削弱 JVM 对系统的控制力,因为它的出错信息都比较黑盒。对于内存不足的情况 ,本地方法栈还是会抛出 native heap OutOfMemory。

重点说一下 JNI 类本地方法 , 最著名的本地方法应该是 System. currentTimeMillis() , JNI 使 Java 深度使用操作系统的特性功能,复用非 Java 代码。 但是在项目过程中 , 如果大量使用其他语言来实现 JNI , 就会丧失跨平台特性 ,威胁到程序运行的稳定性。假如需要与本地代码交互,就可以用中间标准框架进行解耦, 这样即使本地方法崩溃也不至于影响到 JVM 的稳定。当然, 如果要求极高的执行效率、 偏底层的跨进程操作等,可以考虑设计为 JNI 调用方式。

5. Program Counter Register (程序计数寄存器)

在程序计数寄存器( Program Counter Register, PC )中, Register 的命名源于 CPU 的寄存器, CPU 只有把数据装载到寄存器才能够运行。寄存器存储指令相关的现场信息,由于 CPU 时间片轮限制,众多线程在并发执行过程中,任何一个确定的时刻,一个处理器或者多核处理器中的一个内核,只会执行某个线程中的一条指令。 这样必然导致经常中断或恢复,如何保证分毫无差呢?每个线程在创建后,都会产生自己的程序计数器和栈帧,程序计数器用来存放执行指令的偏移量和行号指示器等, 线程执行或恢复都要依赖程序计数器。程序计数器在各个线程之间互不影响,此区域也不会发生内存溢出异常 。

最后 ,从线程共享的角度来看,堆和元空间是所有线程共享的,而虚拟机栈、本地方法栈、程序计数器是线程内部私有的,从这个角度看下 Java 内存结构,如图所示。

计算机基础 HTTPS

原文地址: https请求过程

1. HTTPS请求过程

我们都知道HTTPS能够加密信息,以免敏感信息被第三方获取。所以很多银行网站或电子邮箱等等安全级别较高的服务都会采用HTTPS协议。

HTTPS简介

HTTPS其实是有两部分组成:HTTP + SSL / TLS,也就是在HTTP上又加了一层处理加密信息的模块。服务端和客户端的信息传输都会通过TLS进行加密,所以传输的数据都是加密后的数据。具体是如何进行加密,解密,验证的,且看下图。

1. 客户端发起HTTPS请求

这个没什么好说的,就是用户在浏览器里输入一个https网址,然后连接到server的443端口。

2. 服务端的配置

采用HTTPS协议的服务器必须要有一套数字证书,可以自己制作,也可以向组织申请。区别就是自己颁发的证书需要客户端验证通过,才可以继续访问,而使用受信任的公司申请的证书则不会弹出提示页面(startssl就是个不错的选择,有1年的免费服务)。这套证书其实就是一对公钥和私钥。如果对公钥和私钥不太理解,可以想象成一把钥匙和一个锁头,只是全世界只有你一个人有这把钥匙,你可以把锁头给别人,别人可以用这个锁把重要的东西锁起来,然后发给你,因为只有你一个人有这把钥匙,所以只有你才能看到被这把锁锁起来的东西。

3. 传送证书

这个证书其实就是公钥,只是包含了很多信息,如证书的颁发机构,过期时间等等。

4. 客户端解析证书

这部分工作是有客户端的TLS来完成的,首先会验证公钥是否有效,比如颁发机构,过期时间等等,如果发现异常,则会弹出一个警告框,提示证书存在问题。如果证书没有问题,那么就生成一个随即值。然后用证书对该随机值进行加密。就好像上面说的,把随机值用锁头锁起来,这样除非有钥匙,不然看不到被锁住的内容。

5. 传送加密信息

这部分传送的是用证书加密后的随机值,目的就是让服务端得到这个随机值,以后客户端和服务端的通信就可以通过这个随机值来进行加密解密了。

6. 服务段解密信息

服务端用私钥解密后,得到了客户端传过来的随机值(私钥),然后把内容通过该值进行对称加密。所谓对称加密就是,将信息和私钥通过某种算法混合在一起,这样除非知道私钥,不然无法获取内容,而正好客户端和服务端都知道这个私钥,所以只要加密算法够彪悍,私钥够复杂,数据就够安全。

7. 传输加密后的信息

这部分信息是服务段用私钥加密后的信息,可以在客户端被还原

8. 客户端解密信息

客户端用之前生成的私钥解密服务段传过来的信息,于是获取了解密后的内容。整个过程第三方即使监听到了数据,也束手无策。

2. HTTPS

原文来自《码出高效:Java 开发手册》

总结

HTTPS: HTTP over SSL,简单的理解就是在之前的 HTTP 传输上增加了 SSL 协议的加密能力。

其实就是建立公钥加密数据,私钥解密数据的过程

服务端的公钥加密数据,客户端的私钥解密数据

访问一个 HTTPS 的网站的大致流程如下:

(1)浏览器向服务端发送请求,请求中包括浏览器支持的协议,并附带一个随机数。

(2)服务器收到请求后,选择某种非对称加密算法,把数字证书签名公钥、身份信息发送给浏览器,同时也附带一个随机数。

(3)浏览器收到后,验证证书的真实性,用服务器的公钥发送握手信息给服务器。

(4)服务器解密后,使用之前的随机数计算出一个对称加密的秘钥,以此作为加密信息并发送。

(5)后续所有的信息发送都是以对称加密方式进行的。

阅读更多...

理解Callable 和 Spring DeferredResult

原文地址: 理解Callable 和 Spring DeferredResult(翻译)

1 介绍

Servlet 3中的异步支持为在另一个线程中处理HTTP请求提供了可能性。当有一个长时间运行的任务时,这是特别有趣的,因为当另一个线程处理这个请求时,容器线程被释放,并且可以继续为其他请求服务。
这个主题已经解释了很多次,Spring框架提供的关于这个功能的类似乎有一点混乱——在一个Controller中返回Callable 和 DeferredResult。

在这篇文章中,我将实施这两个例子,以显示其差异。

这里所显示的所有示例都包括执行一个控制器,该控制器将执行一个长期运行的任务,然后将结果返回给客户机。长时间运行的任务由taskservice处理:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Service
public class TaskServiceImpl implements TaskService {
private final Logger logger = LoggerFactory.getLogger(this.getClass());

@Override
public String execute() {
try {
Thread.sleep(5000);
logger.info("Slow task executed");
return "Task finished";
} catch (InterruptedException e) {
throw new RuntimeException();
}
}
}

这个web应用是用Spring Boot创建的,我们将执行下面的类来运行我们的例子:

1
2
3
4
5
6
7
@SpringBootApplication
public class MainApp {

public static void main(String[] args) {
SpringApplication.run(MainApp.class, args);
}
}

2 阻塞的Controller

在这个例子中,一个请求到达控制器。servlet线程不会被释放,直到长时间运行的方法被执行,我们退出@requestmapping注释的方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@RestController
public class BlockingController {
private final Logger logger = LoggerFactory.getLogger(this.getClass());
private final TaskService taskService;

@Autowired
public BlockingController(TaskService taskService) {
this.taskService = taskService;
}

@RequestMapping(value = "/block", method = RequestMethod.GET, produces = "text/html")
public String executeSlowTask() {
logger.info("Request received");
String result = taskService.execute();
logger.info("Servlet thread released");

return result;
}
}

如果我们运行这个例子http://localhost:8080/block,在日志里我们会发现servlet request不会被释放,直到长时间的任务执行完(5秒后)。

1
2
3
2015-07-12 12:41:11.849  [nio-8080-exec-6] x.s.web.controller.BlockingController    : Request received
2015-07-12 12:41:16.851 [nio-8080-exec-6] x.spring.web.service.TaskServiceImpl : Slow task executed
2015-07-12 12:41:16.851 [nio-8080-exec-6] x.s.web.controller.BlockingController : Servlet thread released

3 返回Callable

在这个例子中,不是直接返回的结果,我们将返回一个Callable:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@RestController
public class AsyncCallableController {
private final Logger logger = LoggerFactory.getLogger(this.getClass());
private final TaskService taskService;

@Autowired
public AsyncCallableController(TaskService taskService) {
this.taskService = taskService;
}

@RequestMapping(value = "/callable", method = RequestMethod.GET, produces = "text/html")
public Callable<String> executeSlowTask() {
logger.info("Request received");
Callable<String> callable = taskService::execute;
logger.info("Servlet thread released");

return callable;
}
}

返回Callable意味着Spring MVC将调用在不同的线程中执行定义的任务。Spring将使用TaskExecutor来管理线程。在等待完成的长期任务之前,servlet线程将被释放。

1
2
3
2015-07-12 13:07:07.012  [nio-8080-exec-5] x.s.w.c.AsyncCallableController          : Request received
2015-07-12 13:07:07.013 [nio-8080-exec-5] x.s.w.c.AsyncCallableController : Servlet thread released
2015-07-12 13:07:12.014 [ MvcAsync2] x.spring.web.service.TaskServiceImpl : Slow task executed

你可以看到我们在长时间运行的任务执行完毕之前就已经从servlet返回了。这并不意味着客户端收到了一个响应。与客户端的通信仍然是开放的等待结果,但接收到的请求的线程已被释放,并可以服务于另一个客户的请求。

4 返回DeferredResult

首先,我们需要创建一个deferredresult对象。此对象将由控制器返回。我们将完成和Callable相同的事,当我们在另一个线程处理长时间运行的任务的时候释放servlet线程。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
@RestController
public class AsyncDeferredController {
private final Logger logger = LoggerFactory.getLogger(this.getClass());
private final TaskService taskService;

@Autowired
public AsyncDeferredController(TaskService taskService) {
this.taskService = taskService;
}

@RequestMapping(value = "/deferred", method = RequestMethod.GET, produces = "text/html")
public DeferredResult<String> executeSlowTask() {
logger.info("Request received");
DeferredResult<String> deferredResult = new DeferredResult<>();
CompletableFuture.supplyAsync(taskService::execute)
.whenCompleteAsync((result, throwable) -> deferredResult.setResult(result));
logger.info("Servlet thread released");

return deferredResult;
}
}

所以,返回DeferredResult和返回Callable有什么区别?不同的是这一次线程是由我们管理。创建一个线程并将结果set到DeferredResult是由我们自己来做的。

用completablefuture创建一个异步任务。这将创建一个新的线程,在那里我们的长时间运行的任务将被执行。也就是在这个线程中,我们将set结果到DeferredResult并返回。

是在哪个线程池中我们取回这个新的线程?默认情况下,在completablefuture的supplyasync方法将在forkjoin池运行任务。如果你想使用一个不同的线程池,你可以通过传一个executor到supplyasync方法:

1
public static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier, Executor executor)

如果我们运行这个例子,我们将得到如下结果:

1
2
3
2015-07-12 13:28:08.433  [io-8080-exec-10] x.s.w.c.AsyncDeferredController          : Request received
2015-07-12 13:28:08.475 [io-8080-exec-10] x.s.w.c.AsyncDeferredController : Servlet thread released
2015-07-12 13:28:13.469 [onPool-worker-1] x.spring.web.service.TaskServiceImpl : Slow task executed

5 结论

站在一定高度来看这问题,Callable和Deferredresult做的是同样的事情——释放容器线程,在另一个线程上异步运行长时间的任务。不同的是谁管理执行任务的线程。

文中涉及的代码spring-rest

翻译自Xavier Padró’s Blog

《未来世界的幸存者》读后感

机器人、自动化、人工智能正变得比人类更强大。

在可预见的将来,技术最终将淘汰人类

现实篇

技术进步不可避免的导致工作岗位减少

思考🤔:

竞争会愈加激烈,岗位要求会提高,学历要求会水涨船高

比如现在软件招聘很多只要本科,感觉未来会变成只要985 211

技术进步可能也会造成技术岗位的减少,出现内卷

比如云服务的出现,造成运维岗位的减少。

技术虽然带来了收入的不平等,但也会带来了前所未有的平等,主要是在享受技术成果方面。比如,普通人完全可以使用跟美国总统或世界首富一样的手机。现在人与人之间的不平等,几乎都体现在非技术方面,比如收入、地位、住宅等。

思考🤔:

现实世界越不平等,人们越会逃到虚拟世界享受片刻的平等

有三种能力,机器不太可能实现

(1)人性化和人格魅力。机器提供的服务时没有人性的

(2)创意

(3)决策和领导力(即企业家能力)

思考🤔:

这些“柔软的能力”区分了人和机器,程序员敲代码敲的多了思考会靠近机器,因此也需要抽出时间多锻炼这种“柔软的能力”,这可能是初级程序员和高级程序员的分水岭。

每天忙于工作,干到累死,但还是很穷,只能租屋住,没有自己的积蓄,一旦停止工作或者生病在床,生活来源顿时就成了问题。

思考🤔:

现在流行的996工作制是不也是一种穷忙,加班越多,学习的时间会越少,越难提高工作效率

职业篇

想要技术上有造诣,工作经验的积累只是一方面,真正的突破要靠自己业余深造!不然,路就会越走越窄,公司迟早让你来承担成本,甚至通过摆脱你来降低成本。

思考🤔:

下班之后也要多学习,多输出

骡子只是施工队的工具,跟锄头或者扁担没有本质区别。但你不是他人的工具,你活着不是为了被动地被他人使用,而是应该要有自己的价值。我觉得,人应该过一种有乐趣、有追求、自己做主的生活,而不能像骡子那样被推着走。

思考🤔:

好的工作是能够获得成长和获得成就感的工作

java实现死锁

参考文章: Java实现死锁的几种方式

死锁是指两个或两个以上的进程在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态,这些永远在互相等待的进程称为死锁进程。

代码实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
public class Test {
public static void main(String[] args) {
final Object a = new Object();
final Object b = new Object();
Thread threadA = new Thread(new Runnable() {
@Override
public void run() {
synchronized (a) {
try {
System.out.println("now i in threadA-locka");
Thread.sleep(1000l);
synchronized (b) {
System.out.println("now i in threadA-lockb");
}
} catch (Exception e) {
// ignore
}
}
}
});

Thread threadB = new Thread(new Runnable() {
public void run() {
synchronized (b) {
try {
System.out.println("now i in threadB-lockb");
Thread.sleep(1000l);
synchronized (a) {
System.out.println("now i in threadB-locka");
}
} catch (Exception e) {
// ignore
}
}
}
});

threadA.start();
threadB.start();
}
}

运行效果

static方法能否被重写?

原文地址: (技术点)能不能重写静态方法?

答案: 不能

我们在子类重写父类的静态方法在编译过程是不会报错的,但是却达不到预期的效果。

也就是说,重写静态方法,java不会阻止你这么做, 但是你却得不到预期的结果(父类的引用指向子类对象的时候,调用的仍然是父类的静态方法)。

下面我们来写几个方法看一下结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
class SuperClass{ 
//父类静态方法
public static void staticMethod(){
System.out.println("SuperClass: inside staticMethod");
}
}

public class SubClass extends SuperClass{
//重写父类静态方法
public static void staticMethod(){
System.out.println("SubClass: inside staticMethod");
}

public static void main(String []args){
//父类引用指向父类对象
SuperClass superClassWithSuperCons = new SuperClass();
//父类引用指向子类对象(多态)
SuperClass superClassWithSubCons = new SubClass();
//子类引用指向子类对象
SubClass subClassWithSubCons = new SubClass();

superClassWithSuperCons.staticMethod();
superClassWithSubCons.staticMethod();
subClassWithSubCons.staticMethod();
}
}

运行结果:

1
2
3
SuperClass: inside staticMethod 
SuperClass: inside staticMethod // 输出的还是父类的静态方法
SubClass: inside staticMethod

我们预期的superClassWithSubCons(第二个) 输出的应该是子类的静态方法,然而却输出了父类的静态方法。

为什么静态方法这么特殊呢?

原因是因为静态方法是属于类的方法,他在编译期就已经存储在了方法区,当我们使用父类引用调用子类静态方法时,会直接在方法区去找这个方法,找到的当然是属于父类的静态方法。

一个类的静态方法和静态变量都在方法区

静态方法是在编译阶段使用了编译类型信息,进行静态绑定的。

而普通方法使用父类引用调用子类方法时,我们java栈里面找到引用,然后在堆里面找到了子类的对象,那么调用的是子类的方法。

思考 4 网络是否在使我们的思考变得同质化

手机每天都在给我们推送消息和新闻

很多时候你收到了这则信息,别人也会收到这则信息,我们了解事情的过程越来越趋同。

那么我们看完之后的得到结论是我们自己思考的结果,还是这些信息想让我们这样思考的呢?

如何保持独立思考的能力呢

我觉得一个是要扩展自己获取信息的渠道,二是需要保持怀疑的态度,不要一下子全盘接受,需要辩证思考。

《人生哲思录》读书笔记

一个人活在世上,必须有自己真正爱好的事情,才会活的有意思。

也许,寻找生命的意义,所贵者不在意义本身,而在寻找,意义就寓于寻找的过程之中。

目的只是手段,过程才是目的。对过程不感兴趣的人,是不会有生存的乐趣的。

自白

善演讲的人有三个特点

  1. 记忆力
  2. 自信力
  3. 表现力

糟糕程序员的5个不良习惯(翻译)

原文作者: Ravi Shankar Rajan

原文地址: 5 Bad Habits of Absolutely Ineffective Programmers.

有些程序员比其他程序员更好。实际上,有一个分布统计:一些是绝对出色的,一些是好的,大多数至少是有能力的,一些几乎没有能力,而某些则是真正可怕的。

就是说,一个好的程序员和一个坏的程序员之间的区别不一定是编码技能。实际上,是更基本的东西。不良习惯。而且坏习惯很难在生活和工作中被打破。

就是说,我们的开发人员经常会陷入不良习惯,被编码所束缚,最终使我们无法发挥全部潜能。虽然有些习惯可以帮助我们加快工作速度,但其他习惯(例如,在我打字时用薄荷塞住我的脸……)可能会损害我们的业务和个人生活。

而且我们常常不知道自己的不良习惯,需要有人来阐明它们。就像生活一样,编程也没有硬性规定。有时你会赢得胜利。

让我们谈谈一些不良的编程习惯,你应该尽快摆脱这种习惯。

我的代码是最好的。

弗里德里希·尼采说了一下。

“每当我爬山时,我都会跟随一只叫’自我’的狗。”

所有团队所需要的那种人都是谦虚,饥饿和聪明的人:谦虚,很少自我,更多地关注队友而不是自己。饥饿,意味着他们具有强烈的职业道德,决心将事情做好,并尽其所能。聪明,意味着不是智力上的聪明,而是内心的个人聪明。

不要批评别人的代码,它可能是你的代码。尝试做出客观和专业的观察,但不要判断。谦虚,并尝试向周围的人学习。

永远记住,你的自我是你工作的障碍。如果你开始相信自己的伟大,那就是创造力的死亡。当你开始相信没有其他东西需要学习时,你的学习就会停止。


我可以一蹴而就

安吉拉·达克沃思(Angela Duckworth)曾经说过。

“没有通往真正卓越的捷径。”

帮个忙。允许自己充分利用自己的生活。如果您将所有时间都花在用牙刷擦洗角落上,那简直就是错了。捷径并不意味着捷径最终结果。

捷径非常诱人,每个人都做到了。实际上在某些情况下有必要,但总的来说,它们是危险的,非常危险的,应该避免。出现错误的快捷方式可以为你节省几个小时,但可能导致数月的痛苦并增加声誉损失。

认真对待我的建议。我了解了走捷的艰难。


我记得一切。我不需要记录。

迪克·布兰登(Dick Brandon)

“文件就像性;当它好时,它非常非常好;当它坏时,它总比没有好。”

文档是编程的基础。经理们认为这对程序员是件好事,程序员讨厌它!

但这就是说,优秀的开发人员将其作为日常工作的必要组成部分。

他们意识到,与任何业务职能一样,软件开发团队总是在不断变化。程序员可能会换工作,从一个部门调到另一个部门或退休。在最坏的情况下,生病,受伤或死亡可能会在团队成员最不期望的情况下处于旁观状态。

代码时代也是如此。如果一年或一年以上没有接触过代码,开发人员就很容易忘记自己的代码是如何工作的。

在任何这些情况下,访问设计文档,API规范,手册页和代码注释都可能意味着装运产品和到期日之间的差异。

这种态度使他们成为团队的宝贵资产。通过有意不记录任何内容,您不会变得“ 不可替代 ”。你最终将成为团队的“ 不可挽回 ”的责任。


不是我!

李小龙说得对。

“如果有勇气承认错误,总是可以原谅的。”

上面的陈述也许不能被低估,这是真正出色的开发人员的最重要特征之一。

我们总是有一个借口……..就像我们在正常情况下说我们永远不会犯错一样,老实说很难相信。

糟糕的开发人员会指责客户未正确使用产品。糟糕的开发人员无法对整个产品和错误承担所有权和责任。他们确保每个人都确切知道其他人创建错误时由谁负责。

归咎于指责究竟能实现什么?没事

采取健康的态度,我们可以说:“ 是的,很抱歉,现在我们需要这样做以解决此问题,我的错” *将帮助你树立声誉并得到同事的更好考虑。

越早承认自己的错误,就有更多的时间来学习和纠正错误。就那么简单!!!


你的“完成”并不是完成。

里克·柠檬斯说的话很正确。

“不要让用户提供系统已经知道的信息。”

如果编程是性行为,那么将会有很多不满意的计算机。你不能只是进去,半途而废然后入睡。我发现你苦苦挣扎的概念之一是“ 完成 ” 的概念。

记住完成是指:根据用户的要求进行测试和批准。从你的角度来看,这并未完成。

优秀的开发人员渴望学习新事物。他们努力了解架构的所有部分如何协同工作以及它们处于什么状态。他们质疑功能背后的设计和思想以寻求解决方案。他们了解什么可以带来良好的用户体验。

另一方面,糟糕的开发人员则依附于他们最喜欢的技术。他们认为单一的方法或过程是“ 理想的 ”,并且用户体验和情况永远都不能决定决策。他们将不必要的依赖项引入到项目中以适应他们的偏好。

这样的不良开发商行为就像在中国商店里的牛肉。最终只有时间,努力和声誉的破坏。


最后的想法。

那么,有什么可以概括这里的所有内容呢?

答案是态度。

每天拥有良好的态度胜过拥有多年经验的人。

仅仅工作是不够的,你必须在工作中拥有正确的态度,而正确的态度比拥有正确的技能要重要得多。

在工作中拥有良好,积极的态度以及积极的思想,将会反映出你的所作所为,并使你成为更有生产力的员工。这可以确定你完成项目的能力以及其他人对你的看法。好的态度是有感染力的。

正如Zig Ziglar正确总结的那样。

“你的态度,而不是你的才能,将决定你的高度。”

ARTS-5

ARTS是由左耳朵耗子陈皓在极客时间专栏《左耳听风》中发起的一个每周学习打卡计划。

1
2
3
4
5
6
7
Algorithm:至少做一个 LeetCode 的算法题。主要为了编程训练和学习。

Review :阅读并点评至少一篇英文技术文章。主要为了学习英文,如果你英文不行,很难成为技术高手。

Tip:学习至少一个技术技巧。主要是为了总结和归纳你日常工作中所遇到的知识点。

Share:分享一篇有观点和思考的技术文章。主要为了输出你的影响力,能够输出你的价值观。

Algorithm(算法)

leetcode 25 每k个一组翻转链表

递归

视频地址: https://www.youtube.com/watch?v=DryIN7iL4pA

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
public class Solution {

/*
给定这个链表:1->2->3->4->5

当 k = 2 时,应当返回: 2->1->4->3->5

当 k = 3 时,应当返回: 3->2->1->4->5

1->2->3->4->5
count = 0 cur = 1;
count = 2 cur = 3;
3->4->5
count = 2 cur = 5
5->null

时间复杂度: O(n)
*/
public ListNode reverseKGroup(ListNode head, int k) {
// 判断边界条件
if (head == null || head.next == null) return head;
int count = 0;
ListNode cur = head;
// 如果 cur 不为空且 count 不等与 k继续循环
while (cur != null && count != k) {
cur = cur.next;
count++;
}
if (count == k) {
// 递归
cur = reverseKGroup(cur, k);
while (count-- > 0) {
ListNode temp = head.next; // 存储下一个节点
head.next = cur; // 将当前节点的next域指向前一个节点
cur = head; // prev 指针向后移动
head = temp; // curr 指针向后移动
}
head = cur;
}
// 5 -> null
return head;
}
}

Review(点评)

糟糕程序员的5个不良习惯(翻译)

糟糕程序员的5个不良习惯

  1. 我的代码是最好的。
  2. 我可以一蹴而就
  3. 我记得一切。我不需要记录
  4. 不是我!
  5. 你的“完成”并不是完成

Tip(技巧)

DTO 或 DO 转换为VO

1
2
3
4
5
6
7
8
9
10
if (Objects.isNull(AccountList)) {
return null;
}

List<AccountVO> AccountVOList = new ArrayList<>();
for (Account Account : AccountList) {
AccountVO AccountVO = new AccountVO();
BeanUtils.copyProperties(Account, AccountVO);
AccountVOList.add(AccountVO);
}

Share(分享)

工具推荐 1: Postman-最常用的接口测试工具

  • Copyrights © 2015-2023 高行行
  • 访问人数: | 浏览次数:

请我喝杯咖啡吧~

支付宝
微信