程序由指令和数据组成,程序要执行就必须将指令加载到CPU,将数据加载至内存。同时在程序的运行过程中还需要用到磁盘、网络等设备,进程就是用来加载指令、管理内存、管理IO的。
程序被执行时,程序会将代码加载至内存,这时就开启了一个进程
进程可以视为是程序的一个实例。大部分程序可以同时运行多个实例(记事本、画图、浏览器等),但也有一部分程序只能运行一个实例(安全卫士、网易云音乐等)
在并发场景下线程还是串行执行的,操作系统中的任务调度器将CPU的时间片分给不同的线程使用(并发(cocurrent))
小例子的举例:
在单核的场景下、多线程不会对性能提升有帮助、反而会因为线程的上下文切换影响程序的执行效率
在多核的场景下、多线程的使用就会对程序整体性能的提升有很大的帮助了()
注意:在IO以及网络请求的环节中,不会占用线程,但有可能发生阻塞(阻塞IO)从而影响效率
@Slf4j(topic = "c.Test1")
public class Test1 {
public static void main(String[] args) {
Thread t = new Thread() {
@Override
public void run() {
log.debug("running");
}
};
t.setName("t1");
t.start();
log.debug("running");
}
}
实现Runnable接口的方式实现了线程的定义与功能实现的解耦
@Slf4j(topic = "c.Test2")
public class Test2 {
public static void main(String[] args) {
Runnable r = new Runnable() {
@Override
public void run() {
log.debug("running");
}
};
Thread t = new Thread(r, "t2");
t.run();
}
}
被@FunctionalInterface注解标识的类是一个接口且其只有一个抽象方法,这种方法可以使用Lambda表达式进行简化。
@Slf4j(topic = "c.Test2")
public class Test2 {
public static void main(String[] args) {
// 使用Lambda表达式简化程序代码
Runnable r = () -> {
log.debug("running");
};
Thread t = new Thread(r, "t2");
t.run();
}
}
更简洁的写法:
@Slf4j(topic = "c.Test2")
public class Test2 {
public static void main(String[] args) {
// 直接在Thread下进行调用
Thread t = new Thread(() -> {log.debug("running");}, "t2");
t.run();
}
}
Callable接口类似于Runnable、但Callable接口可以抛出异常,也可以通过.get()方法获取返回值
@Slf4j(topic = "c.Test2")
public class Test3 {
public static void main(String[] args) throws ExecutionException, InterruptedException {
FutureTask<Integer> task = new FutureTask(new Callable<Integer>() {
// 在调用.start方法之后会执行下面的语句
@Override
public Integer call() throws Exception {
log.debug("running.......");
// 在此阻塞
Thread.sleep(1000);
return 1000;
}
});
Thread t1 = new Thread(task, "t1");
t1.start
// .get方法可以在callable接口中得到call方法的返回值,但其是阻塞的,主线程只有在得到返回值之后才会继续执行
log.debug("{}", task.get());
}
}
Java可以使用使用jps来查看所有的Java进程
使用taskkill /F /PID 39228来杀死一个进程
在Linux下,可以使用ps -fe | grep java来查看Java进程
或直接使用jps进行查看
top -H -p 4262 这种形式的进程查看命令可以动态的查看Java的命令
jstack 4262 这种形式的命令会抓取对应Java程序在那一时刻的快照并显示所有线程的详细信息
使用jconsole在windows的控制台直接运行这个指令(不进黑框)进行连接就可以看见线程的相关信息:
记得要在Linux下优先配置以下信息(注意换行):
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类
java -Djava.rmi.server.hostname=192.168.202.130 -
Dcom.sun.management.jmxremote -Dcom.sun.management.jmxremote.port=12345 -
Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false LinuxTest
每个线程启动时,虚拟机就会为其分配一块栈的内存,每个栈由多个栈帧组成,对应每次方法调用时占用的内存,每个线程也有一个栈,每一个栈中有一个个的栈帧,每个栈帧也对应着现在活动的方法。
栈帧中:局部变量表(存储局部变量、形参等)、返回地址、锁记录、操作数栈
注意如果存在多个线程、则线程之间会有独立的栈,其中又会有自己的栈帧,互不影响
start()方法:启动一个线程,并运行run方法(使线程就绪、具体什么时候开始运行要看操作系统的调度)
run()方法:在线程被启动后调用的方法
join()方法:等待线程运行结束
join(long n)方法:等待线程结束、且最多等待n毫秒
getId():获取线程的唯一Id
getName():获取线程名
setName():设置线程名
getPriority():获取线程的优先级
setPriority():设置线程的优先级:线程的优先级是1-10之间的整数,数字越大,优先级越高
setState():获取线程状态:Java中的线程是由6个enum表示的,分别为:NEW、RUNNABLE、BLOCKED、WAITING、TIMED_WAITING、TERMINATED
isInterrupted():判断是否被打断:若线程被打断的话,会有一个打断标记,这个方法不会清除打断标记
isAlive():判断线程是否存活(未执行完毕)
interrupt():打断线程,若被打断的线程正在sleep、wait、join则会导致被打断的线程抛出InterruptedException并清除打断标记,若打断正在运行的线程,则会设置打断标记,若park线程被打断,也会设置打断标记
cuurentThread():获取当前正在执行的线程
sleep(long n):让当前正在执行的线程休眠n(ms),将时间片让给其他线程使用
yield():提示线程调度器让出当前对于CPU的使用
若Interrupt方法打断了正常运行的线程,则可以通过isInterrupt()方法获取打断标识为true、若打断的是wait、sleep、join时的方法,则不会将打断标识置为true。
由于有些线程是不断的进行循环检测状态的类型,这些线程处于 检测、休眠、检测、休眠…的反复循环中,若在检测时被打断、则一切正常,但若在休眠时被打断,不会将interrupted标记设置为true、故我们会将休眠时的打断设置一个异常,在异常中将打断标记设置为true。
两阶段终止模式的示例:
import lombok.extern.slf4j.Slf4j;
public class TestTwoPhaseTermination {
public static void main(String[] args) throws InterruptedException {
TwoPhaseTermination twoPhaseTermination = new TwoPhaseTermination();
twoPhaseTermination.start();
Thread.sleep(3500);
twoPhaseTermination.stop();
}
}
@Slf4j(topic = "c.TwoPhaseTermination")
class TwoPhaseTermination {
private Thread monitor;
public void start() {
monitor = new Thread(() -> {
while(true) {
Thread current = Thread.currentThread();
if (current.isInterrupted()) {
log.debug("料理后事");
break;
}
try {
Thread.sleep(1000);
log.debug("正在监控执行记录");
} catch (InterruptedException e) {
e.printStackTrace();
current.interrupt();
}
}
});
monitor.start();
}
public void stop() {
monitor.interrupt();
}
}
.另外,除了isInterrupted方法可以判断线程是否被打断以外,interrupted方法也可以判断是否被打断,其区别在于
park不是Thread中的方法,它是LockSupport类中的一个方法,若打断标记为false,则这个方法会将当前线程阻塞,若打断标记为true,则不会生效。
默认情况下,Java的进程只有在所有的线程都结束之后才会结束。但有一种特殊的线程叫做守护线程,只要其他非守护线程结束了,即使守护线程没有执行完,其也会强制结束。
t1.setDaemon(true) 这种操作就可以将t1线程设置为守护线程