@Slf4j
public class ThreadCreate {
public static void main(String[] args) {
Thread t1 = new Thread("t1"){
@Override
public void run() {
log.debug("hello");
}
};
// 启动线程
t1.start();
log.debug("do other things ...");
}
}
@Slf4j
public class ThreadCreate {
public static void main(String[] args) {
Runnable task2 = new Runnable() {
@Override
public void run(){
log.debug("hello");
}
};
Thread t2 = new Thread(task2, "t2");
// 启动线程
t2.start();
log.debug("do other things ...");
}
}
//写法2
Thread t4 = new Thread(new Runnable() {
@Override
public void run() {
log.debug("hello");
}
},"t4");
t4.start();
Java 8 以后可以使用 lambda 精简代码
Runnable task2 = () -> log.debug("hello");
Thread t4 = new Thread(task2, "t4");
t4.start();
//写法2
Thread t4 = new Thread(() ->log.debug("hello"),"t4");
t4.start();
FutureTask 能够接收 Callable 类型的参数,用来处理有返回结果的情况
@Slf4j
public class ThreadCreate {
public static void main(String[] args) {
FutureTask<Integer> task3 = new FutureTask<>(new Callable<Integer>() {
@Override
public Integer call() throws Exception {
log.debug("hello");
return 100;
}
});
Thread t5 = new Thread(task3, "t5");
t5.start();
// 主线程阻塞,同步等待 task 执行完毕的结果
// 获取call方法返回的结果(正常/异常结果)
Integer result = task3.get();
log.debug("结果是:{}", result);
}
}
//简写
FutureTask<Integer> task3 = new FutureTask<>(() -> {
log.debug("hello");
return 100;
});
Thread t5 = new Thread(task3, "t5");
t5.start();
// 主线程阻塞,同步等待 task 执行完毕的结果
// 获取call方法返回的结果(正常/异常结果)
Integer result = task3.get();
log.debug("结果是:{}", result);
方法 | 说明 |
---|---|
public void start() | 启动一个新线程,Java虚拟机调用此线程的 run 方法 |
public void run() | 线程启动后调用该方法 |
public void setName(String name) | 给当前线程取名字 |
public void getName() | 获取当前线程的名字线程存在默认名称:子线程是 Thread-索引,主线程是 main |
public static Thread currentThread() | 获取当前线程对象 |
public static void sleep(long time) | 让当前线程休眠多少毫秒再继续执行Thread.sleep(0) : 让操作系统立刻重新进行一次 CPU 竞争 |
public static native void yield() | 提示线程调度器让出当前线程对 CPU 的使用 |
public final int getPriority() | 返回此线程的优先级 |
public final void setPriority(int priority) | 更改此线程的优先级,常用 1 5 10 |
public void interrupt() | 中断这个线程,异常处理机制 |
public static boolean interrupted() | 判断当前线程是否被打断,清除打断标记 |
public boolean isInterrupted() | 判断当前线程是否被打断,不清除打断标记 |
public final void join() | 等待这个线程结束 |
public final void join(long millis) | 等待这个线程死亡 millis 毫秒,0 意味着永远等待 |
public final native boolean isAlive() | 线程是否存活(还没有运行完毕) |
public final void setDaemon(boolean on) | 将此线程标记为守护线程或用户线程 |
@Slf4j(topic = "test")
public class RunAndStart {
public static void main(String[] args) {
Thread t1 = new Thread(() -> {
log.debug("测试!!!");
}, "t1");
t1.run();
log.debug("do other things ...");
}
}
16:09:05.495 [main] DEBUG test - 测试!!!
16:09:05.496 [main] DEBUG test - do other things ...
@Slf4j(topic = "test")
public class RunAndStart {
public static void main(String[] args) {
Thread t1 = new Thread(() -> {
log.debug("测试!!!");
}, "t1");
t1.start();
log.debug("do other things ...");
}
}
16:10:24.051 [main] DEBUG test - do other things ...
16:10:24.051 [t1] DEBUG test - 测试!!!
sleep:
调用 sleep 会让当前线程从 Running
进入 Timed Waiting
状态(阻塞)
sleep() 方法的过程中,线程不会释放对象锁
其它线程可以使用 interrupt 方法打断正在睡眠的线程,这时 sleep 方法会抛出 InterruptedException
睡眠结束后的线程未必会立刻得到执行,需要抢占 CPU
建议用 TimeUnit 的 sleep 代替 Thread 的 sleep 来获得更好的可读性
TimeUnit.SECONDS.sleep(10);
yield:
等待这个线程结束
原理:调用者轮询检查线程 alive 状态,t1.join() 等价于:
public final synchronized void join(long millis) throws InterruptedException {
// 调用者线程进入 thread 的 waitSet 等待, 直到当前线程运行结束
while (isAlive()) {
wait(0);
}
}
线程同步:
interrupt()
:打断当前线程,异常处理机制
interrupted()
:判断当前线程是否被打断,打断返回 true,清除打断标记
isInterrupted()
:判断当前线程是否被打断,不清除打断标记
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(()->{
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}, "t1");
t1.start();
Thread.sleep(500);
t1.interrupt();
System.out.println(" 打断状态: " + t1.isInterrupted());// 打断状态: false
}
public static void main(String[] args) throws Exception {
Thread t2 = new Thread(()->{
while(true) {
Thread current = Thread.currentThread();
boolean interrupted = current.isInterrupted();
if(interrupted) {
System.out.println(" 打断状态: " + interrupted);//打断状态: true
break;
}
}
}, "t2");
t2.start();
Thread.sleep(500);
t2.interrupt();
}
park 作用类似 sleep,打断 park 线程,不会清空打断状态(true)
public static void main(String[] args) throws Exception {
Thread t1 = new Thread(() -> {
System.out.println("park...");
LockSupport.park();
System.out.println("unpark...");
System.out.println("打断状态:" + Thread.currentThread().isInterrupted());//打断状态:true
}, "t1");
t1.start();
Thread.sleep(2000);
t1.interrupt();
}
如果打断标记已经是 true, 则 park 会失效
LockSupport.park();
System.out.println("unpark...");
LockSupport.park();//失效,不会阻塞
System.out.println("unpark...");//和上一个unpark同时执行
可以修改获取打断状态方法,使用 Thread.interrupted()
,清除打断标记
终止模式之两阶段终止模式:Two Phase Termination
目标:在一个线程 T1 中如何优雅终止线程 T2?优雅指的是给 T2 一个后置处理器
错误思想:
两阶段终止模式图示:
打断线程可能在任何时间,所以需要考虑在任何时刻被打断的处理方法:
public class Test {
public static void main(String[] args) throws InterruptedException {
TwoPhaseTermination tpt = new TwoPhaseTermination();
tpt.start();
Thread.sleep(3500);
tpt.stop();
}
}
class TwoPhaseTermination {
private Thread monitor;
// 启动监控线程
public void start() {
monitor = new Thread(new Runnable() {
@Override
public void run() {
while (true) {
Thread thread = Thread.currentThread();
if (thread.isInterrupted()) {
System.out.println("后置处理");
break;
}
try {
Thread.sleep(1000); // 睡眠
System.out.println("执行监控记录"); // 在此被打断不会异常
} catch (InterruptedException e) { // 在睡眠期间被打断,进入异常处理的逻辑
e.printStackTrace();
// 重新设置打断标记,打断 sleep 会清除打断状态
thread.interrupt();
}
}
}
});
monitor.start();
}
// 停止监控线程
public void stop() {
monitor.interrupt();
}
}
Thread t = new Thread() {
@Override
public void run() {
System.out.println("running");
}
};
// 设置该线程为守护线程
t.setDaemon(true);
t.start();
用户线程:平常创建的普通线程
守护线程:服务于用户线程,只要其它非守护线程运行结束了,即使守护线程代码没有执行完,也会强制结束。
常见的守护线程:
Java 虚拟机栈(Java Virtual Machine Stacks):每个线程启动后,虚拟机就会为其分配一块栈内存
线程上下文切换(Thread Context Switch):一些原因导致 CPU 不再执行当前线程,转而执行另一个线程
程序计数器(Program Counter Register):记录正在执行的字节码指令地址,是线程私有的
当 Context Switch 发生时,需要由操作系统保存当前线程的状态(PCB 中),并恢复另一个线程的状态,包括程序计数器、虚拟机栈中每个栈帧的信息,如局部变量、操作数栈、返回地址等
Java 中 main 方法启动的是一个进程也是一个主线程,main 方法里面的其他线程均为子线程,main 线程是这些线程的父线程
操作系统进程的状态(5种):创建态(new)、就绪态(ready)、运行态(running)、阻塞态(waiting)、终止态(terminated)
在 Java API 中 java.lang.Thread.State
这个枚举中给出了六种线程状态:
线程状态 | 导致状态发生条件 |
---|---|
NEW(新建) | 线程刚被创建,但是并未启动,还没调用 start 方法,只有线程对象,没有线程特征 |
Runnable(可运行) | 线程可以在 Java 虚拟机中运行的状态,可能正在运行自己代码,也可能没有,这取决于操作系统处理器,调用了 t.start() 方法:就绪(经典叫法) |
Blocked(阻塞) | 当一个线程试图获取一个对象锁,而该对象锁被其他的线程持有,则该线程进入 Blocked 状态;当该线程持有锁时,该线程将变成 Runnable 状态 |
Waiting(无限等待) | 一个线程在等待另一个线程执行一个(唤醒)动作时,该线程进入 Waiting 状态,进入这个状态后不能自动唤醒,必须等待另一个线程调用 notify 或者 notifyAll 方法才能唤醒 |
Timed Waiting (限期等待) | 有几个方法有超时参数,调用将进入 Timed Waiting 状态,这一状态将一直保持到超时期满或者接收到唤醒通知。带有超时参数的常用方法有 Thread.sleep 、Object.wait |
Teminated(结束) | run 方法正常退出而死亡,或者因为没有捕获的异常终止了 run 方法而死亡 |
windows
tasklist
查看进程taskkill
杀死进程linux
ps -ef
查看所有进程
ps -fT -p <PID>
查看某个进程(PID)的所有线程
kill
杀死进程
top
按大写 H 切换是否显示线程
top -H -p <PID>
查看某个进程(PID)的所有线程
Java
jps
命令查看所有 Java 进程
jstack <PID>
查看某个 Java 进程(PID)的所有线程状态
jconsole
查看某个 Java 进程中线程的运行情况(图形界面)
# jconsole 远程监控配置
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类
一段代码块内如果存在对共享资源的多线程读写操作,称这段代码块为临界区
static int counter = 0;
static void increment()
// 临界区
{
counter++;
}
static void decrement()
// 临界区
{
counter--;
}
多个线程在临界区内执行,由于代码的执行序列不同而导致结果无法预测,称之为发生了竞态条件
一个程序运行多个线程是没有问题,多个线程读共享资源也没有问题,在多个线程对共享资源读写操作时发生指令交错,就会出现问题
为了避免临界区的竞态条件发生(解决线程安全问题):
管程(monitor):指的是管理共享变量以及对共享变量的操作过程,让它们支持并发。翻译为 Java 就是管理类的成员变量和成员方法,让这个类是线程安全的。
synchronized 是可重入、不公平的重量级锁
synchronized:对象锁,保证了临界区内代码的原子性,采用互斥的方式让同一时刻至多只有一个线程能持有对象锁,其它线程获取这个对象锁时会阻塞,保证拥有锁的线程可以安全的执行临界区内的代码,不用担心线程上下文切换
互斥和同步都可以采用 synchronized 关键字来完成,区别:
synchronized(锁对象){
// 访问共享资源的核心代码
}
实例:
public class demo {
static int counter = 0;
//static修饰,则元素是属于类本身的,不属于对象 ,与类一起加载一次,只有一个
static final Object obj = new Object();
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(() -> {
for (int i = 0; i < 5000; i++) {
synchronized (obj) {
counter++;
}
}
}, "t1");
Thread t2 = new Thread(() -> {
for (int i = 0; i < 5000; i++) {
synchronized (obj) {
counter--;
}
}
}, "t2");
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println(counter);
}
}
解决线程安全问题的核心方法是使用锁,每次只能一个线程进入访问
synchronized 修饰的方法的不具备继承性,所以子类是线程不安全的
如果子类的方法也被 synchronized 修饰,两个锁对象其实是一把锁,而且是子类对象作为锁
用法:直接给方法加上一个修饰符 synchronized
//同步方法
修饰符 synchronized 返回值类型 方法名(方法参数) {
方法体;
}
//同步静态方法
修饰符 static synchronized 返回值类型 方法名(方法参数) {
方法体;
}
如果方法是实例方法:同步方法默认用 this 作为的锁对象
public synchronized void test() {}
//等价于
public void test() {
synchronized(this) {}
}
如果方法是静态方法:同步方法默认用类名 .class 作为的锁对象
class Test{
public synchronized static void test() {}
}
//等价于
class Test{
public void test() {
synchronized(Test.class) {}
}
}
线程八锁
线程八锁就是考察 synchronized 锁住的是哪个对象
说明:主要关注锁住的对象是不是同一个
线程不安全:因为锁住的不是同一个对象,线程 1 调用 a 方法锁住的类对象和线程 2 调用 b 方法锁住的 n2 对象,不是同一个对象
class Number{
public static synchronized void a(){
Thread.sleep(1000);
System.out.println("1");
}
public synchronized void b() {
System.out.println("2");
}
}
public static void main(String[] args) {
Number n1 = new Number();
Number n2 = new Number();
new Thread(()->{ n1.a(); }).start();
new Thread(()->{ n2.b(); }).start();
}
线程安全:因为 n1 调用 a() 方法,锁住的是类对象,n2 调用 b() 方法,锁住的也是类对象,所以线程安全
class Number{
public static synchronized void a(){
Thread.sleep(1000);
System.out.println("1");
}
public static synchronized void b() {
System.out.println("2");
}
}
public static void main(String[] args) {
Number n1 = new Number();
Number n2 = new Number();
new Thread(()->{ n1.a(); }).start();
new Thread(()->{ n2.b(); }).start();
}
Hashtable table = new Hashtable();
// 线程1,线程2
if(table.get("key") == null) {
table.put("key", value);
}
// get、put 两个方法分别是线程安全的,一起使用就是不安全的
String 的 replace 等方法底层是新建一个对象,复制过去
Map<String,Object> map = new HashMap<>(); // 线程不安全
String S1 = "..."; // 线程安全
final String S2 = "..."; // 线程安全
Date D1 = new Date(); // 线程不安全
final Date D2 = new Date(); // 线程不安全,final让D2引用的对象不能变,但对象的内容可以变
public abstract foo(Student s);