并发之线程
线程创建
- 继承 Thread 类,本质是 Runable 的实现类
- 实现 Runnable 接口,重写 run() 方法
- 无返回值
- 无法将异常抛到外面,任何check Exception都需要在代码块中自行解决
- 异步执行,不阻塞主方法
- 实现 Callable 接口,重写 call() 方法
- 有返回值
- 可以将异常抛到外层
以下为上三种类型代码实现
public class TestDemo {
public static void main(String[] args) {
// 1. 继承Thread
ThreadDemo threadDemo = new ThreadDemo();
threadDemo.start(); // 异步执行
// threadDemo.run(); // 同步执行
// 2. 实现 Runnable
RunnerDemo runnerDemo = new RunnerDemo();
Thread runnableThread = new Thread(runnerDemo);
runnableThread.start();
// 2.1 另一种简单写法
new Thread(() -> {
System.out.println(Thread.currentThread().getName()+":RunnerDemo2 简写方式");
}).start();
// 3. 实现 Callable
CallDemo callDemo = new CallDemo();
FutureTask futureTask = new FutureTask(callDemo);
new Thread(futureTask).start();
// 3.1 简单写法
FutureTask<String> task = new FutureTask<>(() -> {
System.out.println(Thread.currentThread().getName() + ":CallDemo2 简单写法");
return "CallDemo2";
});
new Thread(task).start();
try {
// 阻塞并等待获取结果,本质也是 runnable
String result = task.get();
System.out.println("call 执行返回结果:"+ result);
} catch (InterruptedException e) {
throw new RuntimeException(e);
} catch (ExecutionException e) {
throw new RuntimeException(e);
}
}
}
// 1.继承 Thread 重写 run方法
class ThreadDemo extends Thread {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+":ThreadDemo");
}
}
// 2. 实现 Runnable 接口,实现 run方法
class RunnerDemo implements Runnable {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+":RunnerDemo1");
}
}
// 3. 实现 Callable 接口,实现 call方法
class CallDemo implements Callable<String> {
@Override
public String call() {
System.out.println(Thread.currentThread().getName()+":CallDemo1");
return "CallDemo1";
}
}
D:\JDK8\bin\java.exe "-javaagent:D:\IntelliJ IDEA
Thread-0:ThreadDemo
Thread-1:RunnerDemo1
Thread-2:RunnerDemo2 简写方式
Thread-3:CallDemo1
Thread-4:CallDemo2 简单写法
call 执行返回结果:CallDemo2
Process finished with exit code 0
常用方法
- sleep()
意思就是睡眠。让当前线程放弃资源,让给其它线程去执行。睡眠时间由你来定,时间一到重新去争夺资源。 - yield()
意思 放弃资源。让当前线程放弃资源,然后与其它线程重新争夺资源。可能重新抢到,可能其它线程抢到。 - join()
意思 加入其它线程。停止当前线程 t1, 执行指定线程t2,t2 执行完再执行t1。用于顺序执行。
自己 join 自己,没有意义
线程状态
线程状态:
- 新建状态
- 就绪状态(代码中没有此状态,可以认为是 运行状态的前置状态)
- 等待状态
- 超时等待状态
- 阻塞状态
- 运行状态
- 结束状态
备注:
BOLCKED、WAITING、TIME_WAITING 三种状态本质上都是放弃资源。
- BOLCKED 当线程阻塞(synchronized)后才会进行此状态
- WAITING 实际上调用USafe.pack()方法,JUC包下的类挂起时会进行此状态
- TIME_WAITING 调用USafe.pack(time)方法,进入等待,直到被唤醒
三种类型 都是线程挂起
- NEW一个线程,在没有调用
start()
前,线程处于新建状态
- 调用
start()
方法后,存在两个状态- 线程获取到资源,线程处于
运行状态
。(之后,调用 yield方法,状态由运行到就绪) - 线程没有获取到资源,线程会进行等待队列,处于`就绪状态。(之后,获取资源后,状态由就绪到运行)
- 线程获取到资源,线程处于
- 运行情况下的状态变迁
- 当线程没有获取到锁情况下,进行阻塞队列,处于
阻塞状态
- 获取到锁后,线程状态由
阻塞状态
转为就绪状态
- 调用 thread.sleep() 方法后,进行
等待状态
。(使用 notify()方法,线程状态由等待 到 运行)
- 当线程没有获取到锁情况下,进行阻塞队列,处于
- 运行结束,状态由运行到结束
Join 实现原理
Join方法的本质是基于synchronized以及wait与nofity实现的。直接对当前线程对象加锁,然后wait挂起线程,wait判断的逻辑是线程是否存活。isAlive()。
// join 方法功能就是,告诉线程我们要释放资源了
public final synchronized void join(final long millis)
throws InterruptedException {
if (millis > 0) {
if (isAlive()) {
final long startTime = System.nanoTime();
long delay = millis;
do {
// 等待 delay时间后,才会执行 wait方法
wait(delay);
} while (isAlive() && (delay = millis -
TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startTime)) > 0);
}
} else if (millis == 0) {
// 如果线程存活,则返回 true,否则就是 false
while (isAlive()) {
//wait 是优雅的关闭资源,需要等待 run()/call() 方法完成后,才会释放资源。未释放前,线程等待(重点)
// 而 synchronized的存在,使外围主线程等待。
wait(0);
}
} else {
throw new IllegalArgumentException("timeout value is negative");
}
}
Stop() 方法问题
不赞同使用此方法。
stop()方法会强制停止run()/call()方法的执行,存在代码未执行的情况。
中断 interrupt
中断的也是和线程停止有关,线程A想停止线程B的运行。整个流程如下:
- 线程A中调用线程B的interrupt方法设置为 true
- 在线程B需要执行时,run方法中手写 interrupt方法的判断,并执行后续操作
- 如果线程B处于sleep的状态,会唤醒线程B并抛出异常 interruptexeption。此时可以通过捕获异常来执行后续逻辑
主线程凉凉,子线程怎样
- 如果子线程是用户线程,则没有影响
- 如果字线程是守护线程,则一起凉凉