同步和异步
这个概念相对于调用者来说,如果调用者需要等待方法返回后才能继续运行,就是同步,如果不需要等待方法返回即可继续运行,就是异步。在多线程里,同步的扩展意思是多线程之间协调一致。
IO操作
IO操作不占用CPU,但是线程被阻塞
创建和启动线程
- 直接使用Thread
Thread thread = new Thread(){
@Overide
public void run(){
}
};
//必须调用start启动线程
thread.start()
- 使用Runnable
Runnable runnable = new Runnable() {
@Override
public void run() {
}
};
new Thread(runnable).start();
- 使用FutureTask和Tread配合
package com.example.demo;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
public class Juc {
public static void main(String[] args) throws ExecutionException, InterruptedException {
FutureTask<Integer> futureTask = new FutureTask<>(new Callable<Integer>() {
@Override
public Integer call() throws Exception {
System.out.println("sleep 1000 ms后返回");
Thread.sleep(1000);
return 0;
}
});
Thread myThread = new Thread(futureTask,"t1");
myThread.start();
Integer integer = futureTask.get();
System.out.println("接收t1返回:"+integer);
}
}
线程执行的顺序
- 交替执行
- 先后顺序受调度,不受代码控制
查看进程和线程
- linux:
ps -ef 查看进程信息
ps -fT -p <PID> 查看进程下的所有线程信息
jps 只列出java进程
kill <PID> 杀死进程
top -H -p <PID> 查看某个进程中线程信息 -H是查看所有线程
jstack <JPID> 查看线程信息(快照形式)
jconsole 图形界面工具 可以连接JVM,监控线程信息 需要启动Java程序时添加一些参数
栈和栈帧
- 每个栈都是由多个栈帧(Frame)组成,对应着每次方法调用时所占用的内存
- 每个线程都有一个活动栈帧,对应着当前正在执行的那个方法
- 栈帧包括局部变量表,返回地址,锁记录,操作数栈
线程上下文切换
原因
- 线程CPU时间片用完
- 垃圾回收
- 有更高优先级的线程要运行
- 线程主动让出CPU,比如sleep,yield,wait,join,park,synchronized,lock等方法时
线程切换
切换时操作系统要记录下一条JVM指令地址,记录这个地址的东西在JVM中叫程序计数器,线程私有。除了保存下一条指令地址,还会保存栈帧,如局部变量,操作数栈,返回地址等,频繁切换会影响性能
线程中常见方法
1. start():启动新线程使用,start调用后线程会进入就绪状态,等待调度,不一定直接运行,同一线程智能调用一次start方法,多了会报错
2. run():线程运行时会调用的方法
3. join():等待线程运行结束
4. join(long n):等待线程运行结束,最多等待n毫秒
5. getId():获取线程唯一ID
6. getName():获取线程名
7. getState():获取线程状态,java中线程状态:NEW,RUNNABLE,BLOCKED,WAITING,TIMED:TIMED_WAITING,TERMINATED
8. isInterrupted():判断是否被打断,不会清除**打断标记**
9. isAlive():线程是否存活
10. interrupt():打断线程,如果被打断线程正在sleep,wait,join则会导致打断线程抛出InterruptedException异常,并清除打断标记,如果打断正在运行的线程则会设置打断标记,PARK的线程被打断,也会设置打断标记。
11. interrupted():判断当前线程是否被打断,会清除打断标记。
12. sleep(long n):休眠n毫秒,线程进入TIMED_WAITIING状态,建议使用TimeUnit.SECONDDS.sleep(int n)方法,加一个sleep防止CPU空转导致CPU占用100%
while(true){
try {
TimeUnit.MICROSECONDS.sleep(300);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
13. yield():让出CPU时间片,主要用于测试,会让线程从RUNNING进入到RUNNABLE
join方法的使用
private static volatile int result = 0;
public static void main(String[] args) {
Thread myThread = new Thread(()->{
try {
TimeUnit.MILLISECONDS.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
result = 10;
});
myThread.start();
try {
myThread.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(result);
}
以上例子中,主线程调用了myThread的join方法,等待myThread执行完成。这样主线程就可以拿到myThread线程的result。
private static volatile int result1 = 0;
private static volatile int result2 = 0;
public static void main(String[] args) throws InterruptedException {
Thread myThread1 = new Thread(()->{
try {
TimeUnit.MILLISECONDS.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
result1 = 10;
});
Thread myThread2 = new Thread(()->{
try {
TimeUnit.MILLISECONDS.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
result2 = 20;
});
myThread1.start();
myThread2.start();
long startTime = System.currentTimeMillis();
myThread1.join();
myThread2.join();
System.out.println("result1=" + result1 + " result2=" + result2);
System.out.println("耗时:" + (System.currentTimeMillis() - startTime) + " ms");
}
上面的例子输出:
result1=10 result2=20
耗时:2001 ms
是两秒的原因是myThread1.join()
等待了两秒,这期间myThread2
已经sleep
完1秒然后线程结束运行了,然后调用它的join
方法不需要时间,所以是2秒
interrupt方法
- 打断阻塞状态的线程 (sleep,wait,join)
Thread myThread1 = new Thread(()->{
try {
System.out.println("开始睡眠 myThread1");
TimeUnit.MILLISECONDS.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
myThread1.start();
TimeUnit.MILLISECONDS.sleep(1000);
System.out.println("开始打断 myThread1");
myThread1.interrupt();
System.out.println("打断标记:"+myThread1.isInterrupted());
输出:
开始睡眠 myThread1
开始打断 myThread1
打断标记:false
java.lang.InterruptedException
at java.lang.Thread.sleep(Native Method)
at java.util.concurrent.TimeUnit.sleep(TimeUnit.java:386)
at com.example.demo.Juc.lambda$main$0(Juc.java:12)
at com.example.demo.Juc$$Lambda$1/0x0000000000000000.run(Unknown Source)
at java.lang.Thread.run(Thread.java:823)
interrupt打断睡眠中的线程会导致睡眠中的线程抛出一个InterruptedException异常,然后打断标记被设置为false
- 打断正常状态的线程
Thread myThread1 = new Thread(() -> {
System.out.println("开始执行 myThread1");
int i = 0;
while (true) {
i++;
}
});
myThread1.start();
TimeUnit.MILLISECONDS.sleep(1000);
System.out.println("开始打断 myThread1");
myThread1.interrupt();
System.out.println("打断标记:" + myThread1.isInterrupted());
输出:
开始执行 myThread1
开始打断 myThread1
打断标记:true
此时虽然打断标记是true,但是,myThread1并没有停止运行,此时要想停止运行,就需要myThread1判断打断标记,自己退出运行。
优雅的解决方案:两阶段停止
package com.example.demo;
import java.util.concurrent.TimeUnit;
public class Juc {
public static void main(String[] args) throws InterruptedException {
TowPhaseTermination towPhaseTermination = new TowPhaseTermination();
towPhaseTermination.startMonitor();
TimeUnit.MILLISECONDS.sleep(3500);
towPhaseTermination.stop();
}
}
class TowPhaseTermination{
private Thread monitorThread;
public void startMonitor(){
monitorThread = new Thread(()->{
while(true){
Thread current = Thread.currentThread();
if(current.isInterrupted()){
System.out.println("线程" + current.getName() + "被打断,开始处理退出逻辑");
break;
}
try {
Thread.sleep(1000);
System.out.println("执行监控");
} catch (InterruptedException e) {
e.printStackTrace();
//如果在sleep期间当前线程被打断,这里会catch住异常
//但是也会清除打断标记,这里手动加上打断标记
current.interrupt();
}
}
});
System.out.println("启动监控线程");
monitorThread.start();
}
public void stop(){
System.out.println("调用了stop方法");
monitorThread.interrupt();
}
}
输出:
启动监控线程
执行监控
执行监控
执行监控
调用了stop方法
线程Thread-3被打断,开始处理退出逻辑
java.lang.InterruptedException
at java.lang.Thread.sleep(Native Method)
at java.lang.Thread.sleep(Thread.java:953)
at com.example.demo.TowPhaseTermination.lambda$startMonitor$0(Juc.java:31)
at com.example.demo.TowPhaseTermination$$Lambda$1/0x0000000000000000.run(Unknown Source)
at java.lang.Thread.run(Thread.java:823)
上面的线程明显是在monitorThread调用了sleep没有睡眠完成时被打断了,那么catch中就捕获了异常,然后再一次进行了打断,在while循环中被判断出来,就将当前线程退出了,好处就是在退出时可以处理一些退出逻辑,比如锁的释放,直接被打断立即停止掉线程可以有并发问题,比如CompareAndSet锁的状态再也没有机会被当前线程释放了,Thread.stop()就是因为这样被禁用了,两阶段停止是优雅停止一个线程的最佳时间方案。