目录
线程上下文切换(Thread Context Switch)
join(Long time):有时间限制的等待,超过这个等待时间,如果等待的线程还未执行结束,不会继续等待。
线程创建的方式
方式一
public static void main(String[] args) {
//我们创建的线程
Thread t = new Thread(){
@Override
public void run(){//重写run方法
log.debug("running");
}
};
t.setName("线程1");//给自己线程起名字
t.start();//创建完之后启动线程
log.debug("run");
}
方式二
使用Runnable配合Thread
Thread代表线程,Runnable可运行的任务(线程要执行的代码)
public static void main(String[] args) {
Runnable runnable = new Runnable() {
@Override
public void run() {
log.debug("第二种创建线程的方法");//要执行的任务
}
};
Thread t = new Thread(runnable);//创建一个线程对象,把要执行的任务丢到里面
t.start();//启动线程
log.debug("run");
}
方式三
FutureTask配合Thread,用来处理有返回结果的情况
public static void main(String[] args) throws ExecutionException, InterruptedException {
FutureTask<Integer> task = new FutureTask<>(new Callable<Integer>() {
@Override
public Integer call() throws Exception {
log.debug("running");
Thread.sleep(1000);
return 100;
}
});
Thread t = new Thread(task);
t.start();//启动我们的线程
log.debug("{}",task.get());//打印我们线程返回的结果,因为我们的线程睡了1秒钟,主线程会阻塞1秒钟
log.debug("run");//主线程
}
线程运行的原理
栈与栈帧
我们都知道JVM中由堆,栈,方法区组成。其中栈内存就是给线程用,每个线程启动后,虚拟机就会为其分配一块栈内存。
每个栈由多个栈帧组成,对应着每次方法调用时所占用的内存。
每个线程只能有一个活动栈帧,对应着当前正在执行的那个方法。
举一个例子:栈帧图解
public class ThreadEx {
//main主线程
public static void main(String[] args) {
method1(10);
}
public static void method1(int x) {
int y = x+1;
Object m =method2();
System.out.println(m);
}
public static Object method2() {
Object n = new Object();
return n;
}
}
线程上下文切换(Thread Context Switch)
因为下面一些原因导致cpu不再执行当前的线程,转而执行另一个线程的代码:
- 线程的cpu时间片用完
- 垃圾回收
- 有更高优先级的线程需要运行
- 线程自己调用了sleep,yield,wait,join,park,synchronized,lock等方法
当Context Switch发生时,需要由操作系统保存当前线程的状态,并恢复另一个线程的状态,Java中对应的概念就是程序计数器,它的作用就是记住下一条jvm指令的执行地址,是线程私有的。
Context Switch频繁发生时,会影响性能。
线程常见方法
start
启动线程,前面我们已展示过,注意,一个线程只能一次,只能调用一次start()方法。
sleep
- 调用sleep会让当前线程从Running进入Timed Watting状态(阻塞状态)
- 其他线程可以使用interrupt方法打断正在睡眠的线程,这时sleep方法会抛出InterruptException
- 睡眠结束后的线程未必会立刻得到执行
示例
new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(2000);//调用sleep方法,使当前线程进入休眠状态
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
log.debug("我是sleep方法,我睡了两秒钟");
}
}).start();
log.debug("我是主线程");
}
20:48:08.706 [main] DEBUG com.example.com.yunlong.test1.Test1 - 我是主线程
20:48:10.710 [Thread-0] DEBUG com.example.com.yunlong.test1.Test1 - 我是sleep方法,我睡了两秒钟
唤醒正在睡眠的线程
public static void main(String[] args) throws InterruptedException {
Thread t1=new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(2000);//调用sleep方法,使当前线程进入休眠状态
} catch (InterruptedException e) {
log.debug("我被唤醒了");
throw new RuntimeException(e);
}
log.debug("我是sleep方法,我睡了两秒钟");
}
});
t1.start();
Thread.sleep(1000);
t1.interrupt();//唤醒正在睡眠的t1线程
log.debug("我是主线程");
}
yield
- 调用yield会让当前线程从running进入runnable就绪状态,提示线程调度器让出当前线程对cpu的使用,然后调度执行其他线程
- 具体的实现依赖操作系统的任务调度器(想让出cpu,但是没有其他可执行的线程,没有让出去)
线程优先级(priority:1,5,10)
- 线程优先级会提示(hint)调度器优先调度该线程,但它仅仅是一个提示,调度器可以忽略它
- 如果cpu比较忙,那么优先级高的线程会获得更多的时间片,但cpu闲时,优先级几乎没作用
join
等待线程运行结束。
为什么需要join方法,示例:
@Slf4j
public class Test2 {
static int n;
public static void main(String[] args) throws InterruptedException {
method();
}
public static void method() throws InterruptedException {
Thread t = new Thread(()-> {
try {
Thread.sleep(1000);//当前线程睡一秒钟
n =10;
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
});
t.start();
t.join();//此方法就是让当前线程等待t线程执行完,再执行之后的代码
log.debug("{}",n);//如果没有join方法,打印结果为0;加了join方法,打印结果为10
}
}
应用之同步
以调用角度来讲,如果
- 需要等待结果返回,才能继续运行就是同步
- 不需要等待结果返回,就能继续运行就是异步
我们刚刚代码示例:
join(Long time):有时间限制的等待,超过这个等待时间,如果等待的线程还未执行结束,不会继续等待。
interrupt
- 打断sleep,wait,join的阻塞线程,线程的isInterrupted会被清楚(false);(可以在catch语句块重置打断标记,使得isInterrupted为true)
- 打断正常执行的线程,该线程的isInterrupted打断标记不会被清除为true;
- park的线程被打断,也会设置打断标记true;
示例:
@Slf4j
public class Test3 {
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
try {
log.debug("我要进入睡眠了");
Thread.sleep(5000);
} catch (InterruptedException e) {
log.debug("我被打断了");
boolean interrupted = Thread.currentThread().isInterrupted();
log.debug("获取t1的被打断标记{}",interrupted);
throw new RuntimeException(e);
}
}
});
t1.start();
Thread.sleep(1000);
log.debug("开始打断");
t1.interrupt();
}
}
22:45:31.828 [main] DEBUG com.example.com.yunlong.test1.Test3 - 开始打断
22:45:31.828 [Thread-0] DEBUG com.example.com.yunlong.test1.Test3 - 我被打断了
22:45:31.828 [Thread-0] DEBUG com.example.com.yunlong.test1.Test3 - 获取t1的被打断标记false
示例:
@Slf4j
public class Test3 {
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
while (true){
boolean interrupted = Thread.currentThread().isInterrupted();
if(interrupted){
log.debug("获取t1的否是被打断标记{}",interrupted);
break;
}
}
}
});
t1.start();
Thread.sleep(1000);
log.debug("开始打断");
t1.interrupt();
}
}
22:42:46.282 [main] DEBUG com.example.com.yunlong.test1.Test3 - 开始打断
22:42:46.293 [Thread-0] DEBUG com.example.com.yunlong.test1.Test3 - 获取t1的否是被打断标记true
注意点:isInterrupted()调用之后,不会清除打断标记;interrupted()调用之后,会清楚打断标记;
常见方法-过时方法
- stop():停止线程运行
- suspend():挂起(暂停)线程
- resume():恢复线程运行
原因:容易破坏同步代码块,造成线程死锁
主线程与守护线程
默认情况下,Java进程需要等待所有线程都运行结束,才会结束。有一种特殊的线程叫做守护线程,只要其他非守护线程运行结束了,即使守护线程的代码没有执行完,也会强制结束。
示例
@Slf4j
public class Test4 {
public static void main(String[] args) throws InterruptedException {
Thread t = new Thread(()-> {
while (true){
if(Thread.currentThread().isInterrupted()){
break;
}
}
});
t.setDaemon(true);//设置t线程为守护线程,当主线程(非守护线程)结束的时候,守护线程也结束
t.start();
Thread.sleep(1000);
log.debug("主线程结束");
}
}
注意:垃圾回收器线程就是一种守护线程,Tomcat中的Acceptor和Poller线程都是守护线程,所以Tomcat接收到shutdown命令后,不会等它们处理完当前请求