Java线程
创建和运行线程
方法一,使用Thread
1 | // 创建线程对象 |
例如:
1 | // 构造方法的参数是给线程指定名字,推荐 |
输出:
1 | 19:19:00 [t1] c.ThreadStarter - hello |
方法二,使用Runnable配合Thread
把【线程】和【任务】(要执行的代码)分开
-
Thread 代表线程
-
Runnable 可运行的任务(线程要执行的代码)
1 | Runnable runnable = new Runnable() { |
例如:
1 | // 创建任务对象 |
输出:
1 | 19:19:00 [t2] c.ThreadStarter - hello |
Java 8 以后可以使用 lambda 精简代码
1 | // 创建任务对象 |
方法三,FutureTask 配合 Thread
FutureTask 能够接收 Callable 类型的参数,用来处理有返回结果的情况
1 | // 创建任务对象 |
输出:
1 | 19:22:27 [t3] c.ThreadStarter - hello |
小结
-
方法1 是把线程和任务合并在了一起,方法2 是把线程和任务分开了
-
用 Runnable 更容易与线程池等高级 API 配合
-
用 Runnable 让任务类脱离了 Thread 继承体系,更灵活
观察多个线程同时运行,线程是交替执行的,谁先谁后,不由我们控制
查看进程线程的方法
windows
-
任务管理器可以查看进程和线程数,也可以用来杀死进程
-
tasklist 查看进程
-
taskkill 杀死进程
linux
-
ps -fe 查看所有进程
-
ps -fT -p
查看某个进程(PID)的所有线程 -
kill 杀死进程
-
top 按大写 H 切换是否显示线程
-
top -H -p
查看某个进程(PID)的所有线程
Java
-
jps 命令查看所有 Java 进程
-
jstack
查看某个 Java 进程(PID)的所有线程状态 -
jconsole 来查看某个 Java 进程中线程的运行情况(图形界面)
jconsole 远程监控配置
- 非认证远程连接应用
1 | java -Djava.rmi.server.hostname=`ip地址` -Dcom.sun.management.jmxremote -Dcom.sun.management.jmxremote.port=`连接端口` -Dcom.sun.management.jmxremote.rmi.port=`连接端口` -Dcom.sun.management.jmxremote.ssl=是否安全连接 -Dcom.sun.management.jmxremote.authenticate=是否认证 -jar jar包 |
-
使用密码远程连接应用
-
进入$JAVA_HOME/conf/management/目录,java8是在 $JAVA_HOME/jre/lib/management目录
-
参照目录下的jmxremote.password.template文件创建jmxremote.password
-
在jmxremote.password新建用户名与密码
- 在本目录的jmxremote.access文件为新增用户设置权限,这里我设置为只读
-
修改 jmxremote.password 和 jmxremote.access 文件的权限为 600
-
执行命令启动应用
1
java -Dcom.sun.management.jmxremote.port=9999 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.password.file=jmxremote.password -jar Test.jar
-
-
使用数字证书远程连接应用
-
进入$JAVA_HOME/bin目录,java8是在 $JAVA_HOME/jre/bin目录
-
执行如下命令生成客户端密钥对
1
2
3
4
5
6
7keytool -genkeypair -alias jconsole -keyalg RSA -validity 365 -storetype pkcs12 -keystore jconsole.keystore -storepass 123456 -keypass 123456 -dname "CN=名称,OU=组织下属单位,O=组织名,L=城市,S=省份,C=CN"
-alias jconsole 密钥对别名为jconsole
-keystore jconsole.keystore 密钥库名称为jconsole.keystore
-storepass 123456 存储密码为123456
-keypass 123456 密钥密码为123456
-dname 证书申请实体信息- 执行如下命令导出客户端证书
1
keytool -exportcert -alias jconsole -storetype pkcs12 -keystore jconsole.keystore -file jconsole.cer -storepass 123456
- 查看生成结果,前2步骤顺利的话会在当前文件夹看到以下文件(客户端)
-
将客户端证书传到应用服务端
-
生成服务端应用的密钥对
1
keytool -genkeypair -alias app -keyalg RSA -validity 365 -storetype pkcs12 -keystore app.keystore -storepass 123456 -keypass 123456 -dname "CN=app名称,OU=app组织下属单位,O=app组织名,L=城市,S=省份,C=CN"
- 导出服务端应用的证书
1
keytool -exportcert -alias app -storetype pkcs12 -keystore app.keystore -file app.cer -storepass 123456
-
将服务端证书传到客户端
-
执行命令将客户端证书导入到服务端的truststore(受信的密钥库)中
1
2
3keytool -importcert -alias jconsole -file jconsole.cer -keystore app-ssl.truststore -storepass 123456 -noprompt
-storepass 123456 受信密钥库密码为123456- 执行命令将服务端证书导入到客户端的truststore(受信的密钥库)中
1
keytool -importcert -alias app -file app.cer -keystore jconsole-ssl.truststore -storepass 123456 -noprompt
- 设置应用启动脚本如下
1
2
3
4
5
6
7
8# java8
java -Dcom.sun.management.jmxremote.port=8079 -Dcom.sun.management.jmxremote.rmi.port=8079 -Djava.rmi.server.hostname=${ip} -Dcom.sun.management.jmxremote.password.file=${path} -Dcom.sun.management.jmxremote.authenticate=true -Dcom.sun.management.jmxremote.ssl=true -Dcom.sun.management.jmxremote.registry.ssl=true -Dcom.sun.management.jmxremote.ssl.need.client.auth=true -Djavax.net.ssl.trustStore=/home/iic/app-ssl.truststore -Djavax.net.ssl.trustStorePassword=123456 -Djavax.net.ssl.keyStore=${path} -Djavax.net.ssl.keyStorePassword=123456 -jar demo-0.0.1-SNAPSHOT.jar
# java11
java -Dcom.sun.management.jmxremote.port=8079 -Dcom.sun.management.jmxremote.rmi.port=8079 -Djava.rmi.server.hostname=${ip} -Dcom.sun.management.jmxremote.authenticate=false -Dcom.sun.management.jmxremote.ssl=true -Dcom.sun.management.jmxremote.registry.ssl=true -Dcom.sun.management.jmxremote.ssl.need.client.auth=true -Djavax.net.ssl.trustStore=/home/iic/app-ssl.truststore -Djavax.net.ssl.trustStorePassword=123456 -Djavax.net.ssl.keyStore=${path} -Djavax.net.ssl.keyStorePassword=123456 -jar demo-0.0.1-SNAPSHOT.jar
-Djavax.net.ssl.trustStore=${path} 受信密钥库路径
-Djavax.net.ssl.trustStorePassword=123456 受信密钥库密码- 使用ssl连接远程服务,在jconsole目录执行以下命令
1
jconsole -J-Djavax.net.ssl.trustStore=F:\professional\java-cert\jconsole-ssl.truststore -J-Djavax.net.ssl.trustStorePassword=123456 -J-Djavax.net.ssl.keyStore=F:\professional\java-cert\jconsole.keystore -J-Djavax.net.ssl.keyStorePassword=123456
-
线程运行原理
栈与栈帧
Java Virtual Machine Stacks (Java 虚拟机栈)
我们都知道 JVM 中由堆、栈、方法区所组成,其中栈内存是给谁用的呢?其实就是线程,每个线程启动后,虚拟机就会为其分配一块栈内存。
-
每个栈由多个栈帧(Frame)组成,对应着每次方法调用时所占用的内存
-
每个线程只能有一个活动栈帧,对应着当前正在执行的那个方法
线程上下文切换(Thread Context Switch)
因为以下一些原因导致 cpu 不再执行当前的线程,转而执行另一个线程的代码
-
线程的 cpu 时间片用完
-
垃圾回收
-
有更高优先级的线程需要运行
-
线程自己调用了 sleep、yield、wait、join、park、synchronized、lock 等方法
当 Context Switch 发生时,需要由操作系统保存当前线程的状态,并恢复另一个线程的状态,Java 中对应的概念就是程序计数器(Program Counter Register),它的作用是记住下一条 jvm 指令的执行地址,是线程私有的
-
状态包括程序计数器、虚拟机栈中每个栈帧的信息,如局部变量、操作数栈、返回地址等
-
Context Switch 频繁发生会影响性能