Thread类是JVM用来管理线程的一个类,换句话说,每个线程都有一个唯一的Thread对象与之关联。
在我们创建线程的时候会遇到run方法与start方法,这里大家先做一个区分,它们的区别如下:
在这里介绍五种创造线程的方法
第一种:
继承 Thread, 重写 run:
class MyThread extends Thread{
@Override
public void run(){
System.out.println("Hello thread");
}
}
public class Demo1 {
public static void main(String[] args) {
Thread t = new MyThread();
t.start();//启动线程
}
}
第二种:
实现Runnable,重写run
class MyRunnable implements Runnable{
@Override
public void run() {
System.out.println("hhhh");
}
}
public class Demo3 {
public static void main(String[] args) {
Thread t = new Thread(new MyThread());
t.start();;
}
}
第三种:
继承Thread,重写run,使用匿名内部类
创建一个匿名内部类,继承自Thread类,同时重写run方法,同时再new出这个匿名内部类的实例
public class Demo4 {
public static void main(String[] args) {
Thread t = new Thread(){
@Override
public void run() {
System.out.println("hell");
}
};
t.start();
}
}
第四种:
实现 Runnable, 重写 run, 使用匿名内部类
public class Demo5 {
public static void main(String[] args) {
Thread t = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("xixixixi");
}
});
t.start();
}
}
使用lambda表达式
public class Demo6 {
public static void main(String[] args) {
Thread t = new Thread(()->{
System.out.println("33333");
});
t.start();
}
}
这五种创建线程的表达式很常见,我们应该要熟练掌握。
通过上面五种的方式,创建线程对象,不知大家有没发现,都有一个t.strat();表达式在里面。
为什么呢? 这是因为线程被创建出来并不意味着线程就开始运行了。就比如说,我们要去旅游,我们准备好了衣物这些出行物件,不代表我们已经出发了。只有我们坐上了车,出发了,才叫出发。
所以说,只有调用了start();方法,线程才真正执行起来。
上面我们也应该注意到,我们都覆写了run方法,覆写 run 方法是提供给线程要做的事情的指令清单。
线程中断就是让一个线程停下来
线程停下来的关键,是要让对应的run方法执行完 。
对于main这个线程,则是得先让main方法执行完。
因此,让线程中断,一般有以下几种方法。
方法一:
手动的设置一个标志位,来控制线程是否要执行
这个标志位实际上是我们自己创建的一个Boolean型变量
public class Demo10 {
private static boolean isQuit = false;
public static void main(String[] args) {
Thread t = new Thread(()->{
while(!isQuit) {
System.out.println("hello world!");
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
t.start();
//isQuit 为true,则此时循环退出,进一步run就执行完了,进一步则线程执行结束
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
isQuit = true;
System.out.println("终止t线程");
}
}
 可以看到,当while里面的条件不满足之后,循环结束了,而t线程也跟着结束了,之后main线程也跟着结束了,进程随之退出。
上面的这种让线程中断的写法其实是不严谨的,因为它仅仅满足于刚刚的特定情况。更好的方法我们下面继续介绍。
方法二:
使用Thread中内置的标志位来进行判断
通过以下两种形式:
二者区别在于前者是一个静态方法,后者是一个实例方法,其中currentThread能获取到当前线程的实例。更推荐使用后者,下面我们用第二种方法来演示。
public class Demo11 {
public static void main(String[] args) {
Thread t = new Thread(()->{
//如果这个标志被设置了,它是被中断的状态 ,则返回true,中断 线程
while (!Thread.currentThread().isInterrupted()){
System.out.println("hello world!");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
t.start();
//在主线程中,条用interrupt方法,来中断这个线程
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//在主线程中,条用interrupt方法,来中断这个线程
//t.interupt的意思就是让t线程被中断
t.interrupt();
}
}
为什么会产生这种情况呢?要弄明白这个东西,我们得先来知道调用interrupt方法可能会产生的两种情况:
回到我们的程序当中,我们分析:
完成这样的工作之后,发生异常之后,程序就结束了。
多个线程之间的调度顺序是不确定的,因为我们线程之间的执行是按照调度器来安排的,这个过程可以视为是“无序的,随机的”。
但有时我们需要它是有序的,我们希望能够控制线程之间的顺序,而线程等待就是我们控制线程执行顺序的一种手段。这里的线程等待,主要控制的是线程结束的先后顺序。
我们这里一般使用的是join方法:
方法 | 说明 |
---|---|
public void join() | 等待线程结束 |
public void join(long millis) | 等待线程结束,最多等 millis 毫秒 |
public void join(long millis, int nanos) | 同理,但可以更高精度 |
在调用join方法的时候,哪个线程调用的join,那个线程就会阻塞等待,直到对应的线程执行完毕为止(也就是对应线程的run执行完)。
public class Demo12 {
public static void main(String[] args) {
Thread t = new Thread(()->{
for (int i = 0; i < 5; i++) {
System.out.println("hello hhh");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
t.start();
//在主线程中使用一个等待操作,来等待t线程执行结束
try {
t.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
这样,通过线程等待,就可以控制让t线程先结束,main线程后结束,这在一定程度上干预了这两个线程的执行顺序。
可是,这里有一个问题,就是我们的join操作在默认情况下,是死等操作,也就是说,不见不散的那种。那么这种形式的等待,在我们的程序运行当中,实际上是不合理的。
因此,这里join提供了另外一种版本,就是可以指定等待时间,当时间过了,就不等了。它的写法就是在join里面加一个参数。
方法 | 说明 |
---|---|
public static Thread currentThread(); | 返回当前线程对象的引用 |
哪个线程调用这个currentThread,就获取的是哪个线程的实例
public class Demo13 {
public static void main(String[] args) {
Thread t = new Thread(){
@Override
public void run() {
System.out.println(Thread.currentThread().getName());//获取到线程的名字
}
};
t.start();
}
}
public class Demo13 {
public static void main(String[] args) {
Thread t = new Thread(){
@Override
public void run() {
// System.out.println(Thread.currentThread().getName());//获取到线程的名字
}
};
t.start();
//这个操作是在main线程中调用的,因此拿到的就是main这个线程的实例
System.out.println(Thread.currentThread().getName());
}
}
因此,哪个线程调用这个currentThread,就获取的是哪个线程的实例。
此外,我们还可使用this关键字。
这里我们采用的sleep方法
方法 | 说明 |
---|---|
public static void sleep(long millis) throws InterruptedException | 休眠当前线程 millis毫秒 |
public static void sleep(long millis, int nanos) throws InterruptedException | 可以更高精度的休眠 |
【注意】因为线程的调度是不可控的,所以,这个方法只能保证实
际休眠时间是大于等于参数设置的休眠时间的。
我们的线程是通过PCB(Process Control Block)描述的,通过双向链表进行组织。以上说法是针对我们只有一个一个线程的进程描述的。
如果一个进程有多个线程,此时每个线程都有一个PCB。换句话说,就是一个进程对应的是一组PCB,PCB上有一个字段——tgroupId,这个字段就相当于进程的id,同一个进程里面的若干个线程的tgroupId是相同的。
这里可能有人会有疑问,Process Control Block进程控制块和线程有啥关系?
实际上Linux内核不区分进程和线程,进程线程是程序猿写应用程序代码搞出来的词.实际上Linux内核只认PCB !!在内核里Linux把线程称为轻量级进程。
行百步者半九十,我感觉到,自己可能到了这个阶段了,尽管我可能连五十步都还没走到。茫然,真茫然,自己学不到家,学得太烂。我知道自己面对的是怎样的境地,无论如何,我都必须得走下去了,凡能以人的主观意志达成的事,我绝不能就此放弃,只要我还是我,我会走下去…