线程—并发编程(基础+1)
文章目录
一、线程相关属性
1、线程和调用栈
线程和调用栈的关系:每个线程都有自己独立的调用栈
public class addMain {
//暂且把主方法中的线程称为主线程
//一个Java应用总是从main ()方法开始运行,mian ()方法运行在一个线程内,它被称为主线程。
public static void main(String[] args) {
//创建一个线程对象(这个线程对象执行的是add方法)
addThread addT=new addThread();
addT.start();
//在主线程中也去调用add方法
Add.add(10,20);
}
}
//线程
class addThread extends Thread{
@Override
public void run() {
//这个线程要干的事就是调用Add方法
System.out.println(Add.add(1, 2));
}
}
//add方法
class Add{
public static int add(int a, int b){
return a+b;
}
}
在main方法中,创建并启动了一个addT线程,同时自己也调用add方法。所以现在共有两个线程:
1、主线程(包含main方法和add方法)
2、addThread线程(包含run方法和add方法)
虽然这两个线程都调用了add方法,但只能说明这两个线程执行的是同一批指令(程序=指令+数据),他们各自有各自的调用栈,意味着他们用同一批指令处理不同的数据。
来看看主线程栈帧的调用情况👇
接着再展示一下addThread线程的栈帧调用情况👇
调试器中看到的方法调用栈,每一行就是一个栈帧(frame),保存的就是方法运行中的临时数据,对我们来说最主要的就是临时变量。每个线程都是独立的执行流,并且都有自己的独立的栈,A线程调用了哪些方法,和B线程一点关系都没有。
2、线程的常见属性
- 线程 id :本线程(JVM进程)内部分配的唯一的 id,只能 get 不能 set。
- 线程的name:为了给开发人员看的,JVM并不需要这个属性。这个属性可以 get 也可以 set 。
- 线程状态:新建(NEW)、就绪(RUNNABLE)、运行(RUNNABLE)、阻塞(BLOCKED/WAITING/TIME_WAITING)、结束(TERMINATED),线程状态也是只能获取,不能设置,是被JVM内部设置的,但是可以通过某些方法改变它的状态(比如就可以通过调用线程.start()方法让某个线程变成RUNNABLE状态)。
3、前台线程&&后台线程(精灵线程/守护线程)
前台线程:一般做一些交互工作;
后台线程:一般做一些支持的工作;
我们自己创建的线程默认都是前台线程,除非把他修改成后台线程。
//如何修改前后台线程
public class daemon {
static class MyThread extends Thread{
@Override
public void run(){
System.out.println("哈哈哈哈");
}
}
public static void main(String[] args) {
MyThread t1 = new MyThread();
System.out.println(t1.isDaemon());//返回true代表是后台(daemon)线程
t1.setDaemon(true);//修改自己是前台还是后台线程
//设置为 true 就成了后台线程,设置为 false 就成了前台线程。
t1.start();
}
}
这里有很重要的一个问题,JVM进程什么时候会退出?
所有前台线程都退出了,JVM进程就退出了
- 必须要求所有前台线程都退出,和主线程没关系;
- 和后台线程没关系,即使后台线程还在工作,也正常退出。
二、线程之间的相互控制
1、Thread.join()方法
A线程:
- 创建B线程并启动B线程
- A线程现在在干别的事……
- 等待B线程完成所有工作(等B线程执行结束)
- 打印B线程已经结束
B线程:随便做一些工作;
举个例子,总裁现在去吃饭,但忘记带钱包,于是他吩咐他的秘书去办公室拿钱包(总裁只有等秘书拿来了钱包付了钱才可以离开餐厅,可不能吃霸王餐啊)。等秘书拿钱包的过程就是join()的用法及作用。
public class Main {
static class MyThread extends Thread{
@Override
public void run() {
try {
TimeUnit.SECONDS.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
println("秘书说:钱包拿来啦");
}
}
public static void main(String[] args) throws InterruptedException {
MyThread b = new MyThread();
b.start();
println("总裁自己先去吃饭");
// 看一下有 join 和没有 join 的区别
b.join();
println("总裁说:秘书给我把钱送来了,结账走人");
}
//这个方法是为了让我们更清楚的看到有join和没有join的区别
public static void println(String msg) {
Date date = new Date();
DateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
System.out.println(format.format(date) + ": " + msg);
}
}
来看一下运行结果:
有join()的时候👇,总裁非常有素质讲道德,结账之后才离开餐厅
没有join()的时候👇,总裁不讲武德,吃霸王餐,不给钱!
注意注意:线程A(总裁)和线程B(秘书)在没有join()之前是没有关系的,是互相独立的执行流(总裁专心吃饭,秘书就去钱包),有了join()之后这两个线程才被关联到一块,总裁必须等秘书拿来钱包之后才能结账走人。
2、Thread.sleep()方法
TimeUnit.SECONDS.sleep(5);
//这俩是等价的,随便挑一个用就行
Thread.sleep(5);
从线程调度的角度来看,调用sleep(多长时间)就是将当前线程从“运行状态 ”改变到“阻塞状态”,阻塞状态就是线程在等某个资源或事件,当条件满足(即过去了多长时间,sleep()结束),线程就从“阻塞状态”变成“就绪状态”,(万事俱备之前CPU),当这个线程被调度器选中时,就接着之前的指令执行。
3、Thread.currentThread()方法
返回一个当前线程的Thread引用,指向一个线程对象(在哪个线程中调用的该方法,就返回哪个线程的对象)。
public class Main {
static class MyThread extends Thread {
@Override
public void run() {
printCurrentThreadAttributes();
}
}
private static void printCurrentThreadAttributes() {
// 返回当前(这个方法是在哪个线程中被调用的)线程的引用
Thread t = Thread.currentThread();
System.out.println("当前线程的name是:"+t.getName());
}
public static void main(String[] args) {
MyThread t1 = new MyThread();
t1.setName("t1");
t1.start();
MyThread t2 = new MyThread();
t2.setName("t2");
t2.start();
MyThread t3 = new MyThread();
t3.setName("t3");
t3.start();
printCurrentThreadAttributes();
}
}
看一下结果吧👇
总结
哈哈哈哈哈今天就到这里吧,我累了