一、创建线程(3种)
实际上每个Java程序在启动时都已经创建了一个线程(main),我们称之为主方法线程或者主线程。
默认已经有一个主线程运行,若还想在主线程之外创建线程,可用如下方法创建:
方法一:直接使用Thread
// 创建线程对象(实际上利用匿名内部类的写法,创建的实际为Thread的一个子类)
Thread t = new Thread() {
// 覆盖其中的run方法
public void run() {
// 要执行的任务
}
};
// 启动线程
t.start();
注意:创建和启动分为两步
例如:
// 构造方法的参数是给线程指定名字,推荐
Thread t1 = new Thread("t1") {
@Override
// run 方法内实现了要执行的任务
public void run() {
log.debug("hello");
}
};
t1.start();
输出:
19:19:00 [t1] c.ThreadStarter - hello
方法二:使用Runnable配合Thread
将【线程】和【任务】(要执行的代码)分开 ===>更为灵活
● Thread 代表线程
● Runnable 可运行的任务(线程要执行的代码)
Runnable runnable = new Runnable() {
public void run(){
// 要执行的任务
}
};
// 创建线程对象
Thread t = new Thread( runnable );
// 启动线程
t.start();
例如:
// 创建任务对象
Runnable task2 = new Runnable() {
@Override
public void run() {
log.debug("hello");
}
};
// 参数1 是任务对象; 参数2 是线程名字,推荐
Thread t2 = new Thread(task2, "t2");
t2.start();
输出:
19:19:00 [t2] c.ThreadStarter - hello
Java 8 以后可以使用 lambda 精简代码
// 创建任务对象
Runnable task2 = () -> log.debug("hello");
// 参数1 是任务对象; 参数2 是线程名字,推荐
Thread t2 = new Thread(task2, "t2");
t2.start();
* 原理之 Thread 与 Runnable 的关系
可分析Thread的源码,分析它与Runnable的关系
小结
● 第一种创建对象的方法是将线程与任务合并到一起,而第二种方法是将线程和任务分开(不推荐直接去操作线程对象;而是推荐去操作Runnable的这种任务对象)
● 用 Runnable 更容易与线程池等高级 API 配合
● 用 Runnable 让任务类脱离了 Thread 继承体系,更灵活
方法三:FutureTask配合Thread(实际上也是对Runnable的一个拓展,可以获取任务的执行结果)
FutureTask 能够接收 Callable (比Runnable多了个返回值)类型的参数,用来处理有返回结果的情况
2秒后成功返回!!!(主线程执行到get方法时会阻塞等待2s)
二、线程运行
2.1 现象
理解
● 交替执行
● 谁先谁后,不由我们控制
import lombok.extern.slf4j.Slf4j;
/演示多个线程并发交替执行/
@Slf4j(topic = "c.TestMultiThread")
public class TestMultiThread {
public static void main(String[] args) {
new Thread(() -> {
// 不断被执行打印操作
while(true) {
log.debug("running");
}
},"t1").start();
new Thread(() -> {
while(true) {
log.debug("running");
}
},"t2").start();
}
}
结果:CPU不断交替执行两个线程内的代码
当然它的底层是否由多个核心对这两个线程并行执行还是一个核心对它们并发处理,包括打印的前后顺序是不由我们控制的。都是由底层任务调度器来决定的
注意:若CPU只有一个核心,不要运行以上代码。两个线程同时运行均要使用CPU,While(ture)为死循环,两个线程均需要不断使用CPU,而两个线程大量使用CPU会造成CPU占用率达到100%,其他代码、操作系统其他程序就会暂停卡住。
2.2 线程的查看和杀死
① windows
● 任务管理器可以查看进程和线程数,也可以用来杀死进程
● tasklist 查看进程
● taskkill 杀死进程
taskkill /F /PID **** // 【/F:强制杀死 /PID:进程编号 】
② linux
ps -fe 查看所有进程
ps -fT -p 查看某个进程(PID)的所有线程
kill 杀死进程
top 按大写 H 切换是否显示线程(动态)
top -H -p 4262 // 查看4262进程中所有线程的信息
top -H -p 查看某个进程(PID)的所有线程
③ Java
jps 命令查看所有 Java 进程
jstack (快照:只能查看某一刻) 查看某个 Java 进程(PID)的所有线程状态
jconsole(图形化界面工具) 来查看某个 Java 进程中线程的运行情况(图形界面)
jconsole的使用(可以连接到某个Java进程上查看其状态,会将本地的Java进程列出来)
① Win+R,输入jconsole
②
jconsole 远程监控配置
● 需要以如下方式运行你的 java 类
java -Djava.rmi.server.hostname=`ip地址` -Dcom.sun.management.jmxremote -
Dcom.sun.management.jmxremote.port=`连接端口` -Dcom.sun.management.jmxremote.ssl=是否安全连接 -Dcom.sun.management.jmxremote.authenticate=是否认证 java类
● 修改 /etc/hosts 文件将 127.0.0.1 映射至主机名
如果要认证访问,还需要做如下步骤
● 复制 jmxremote.password 文件
● 修改 jmxremote.password 和 jmxremote.access 文件的权限为 600 即文件所有者可读写
● 连接时填入 controlRole(用户名),R&D(密码)
三、线程运行原理
3.1 原理-栈帧debug
原理之线程运行
● 栈与栈帧
Java Virtual Machine Stacks (Java 虚拟机栈)
我们都知道 JVM 中由堆、栈、方法区所组成,其中栈内存是给谁用的呢?其实就是线程,每个线程启动后,虚拟机就会为其分配一块栈内存。
——每个栈由多个栈帧(Frame)组成,对应着每次方法调用时所占用的内存
——每个线程只能有一个活动栈帧,对应着当前正在执行的那个方法
通过例子了解栈帧的调用关系
public class Test {
public static void main(String[] args) {
method1(10);
}
private static void method1(int x) {
int y = x + 1;
Object m = method2();
System.out.println(m);
}
private static Object method2() {
Object n = new Object();
return n;
}
}
● 多线程(可在DeBug模式下查看两个线程运行状态)
public class Test {
public static void main(String[] args) {
// t1线程调用
new Thread(()->{
method1(20);
},"t1").start();
// 主线程调用
method1(10);
}
private static void method1(int x) {
int y = x + 1;
Object m = method2();
System.out.println(m);
}
private static Object method2() {
Object n = new Object();
return n;
}
}
多个线程同时运行时线程的栈内存是相互独立的,互不干扰
● 线程上下文切换(Thread Context Switch)
因为以下一些原因导致 cpu 不再执行当前的线程,转而执行另一个线程的代码(从使用CPU到不适用CPU称之为线程的一次上下文切换)
—— 线程的 cpu 时间片用完
—— 垃圾回收(会暂停当前所有的工作线程)
—— 有更高优先级的线程需要运行
—— 线程自己调用了 sleep、yield、wait、join、park、synchronized、lock 等方法
当 Context Switch 发生时,需要由操作系统保存当前线程的状态,并恢复另一个线程的状态,Java 中对应的概念就是程序计数器(Program Counter Register),它的作用是记住下一条 jvm 指令的执行地址,是线程私有的
——状态包括程序计数器、虚拟机栈中每个栈帧的信息,如局部变量、操作数栈、返回地址等
——Context Switch 频繁发生会影响性能
线程数并不是越多越好,线程数如果超过CPU核心,CPU必然会在这些线程中轮流切换,线程上下文切换越频繁成本越高,进而影响性能