一、认识线程
1.线程是什么
一个线程就是一个 "执行流". 每个线程之间都可以按照顺讯执行自己的代码. 多个线程之间 "同时" 执行 着多份代码
2.线程存在的原因
首先, "并发编程" 成为 "刚需"。 (单核 CPU 的发展遇到了瓶颈. 要想提高算力, 就需要多核 CPU. 而并发编程能更充分利用多核 CPU 资源. 有些任务场景需要 "等待 IO", 为了让等待 IO 的时间能够去做一些其他的工作, 也需要用到并发编程)
其次, 虽然多进程也能实现并发编程, 但是,线程比进程更轻量;创建线程比创建进程更快;销毁线程比销毁进程更快;调度线程比调度进程更快。
3.进程与线程的区别:
(1)进程是操作系统资源分配的最小单位,线程是操作系统调度的最小单位。线程是进程的执行单位
(2)一个进程至少会有一个线程。
(3)进程的资源是互相隔离的,同属于一个进程的不同线程,是共享资源的(也正是因为资源共享,给我们带来了很大的麻烦)。
4.Java线程 和 操作系统线程 的关系
线程是操作系统中的概念. 操作系统内核实现了线程这样的机制, 并且对用户层提供了一些 API 供用户使用.。Java 标准库中 Thread 类可以视为是对操作系统提供的 API 进行了进一步的抽象和封装.
5.创建线程的方式
(1)通过继承Thread类,重写run方法,创建线程
线程是操作系统的概念,操作系统内核提供了对线程的操作,并且提供了相应的api,Java对这些api进行了封装,就是Thread类。(继承:子类去继承父类的一些行为及特征,可以去重写父类的方法,重写父类的方法时,定义和参数要和父类保持一样,重载是方法名相同但参数不同)
class MyThread3 extends Thread{ //继承Thread类
@Override
public void run() { //重写run方法
while (true){
System.out.println("hello thread");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
//创建MyThread类的实例,调用start方法启动线程
public class Demo2 {
public static void main(String[] args) {
Thread thread = new MyThread3(); //创建新线程
thread.start(); //启动线程
}
}
(2)通过实现Runnable接口,重写run方法(run方法是描述了该线程具体需要做的事情),创建线程
class MyRunnable implements Runnable{ //通过实现Runnable接口 创建线程
@Override
public void run() {
System.out.println("my runnable");
}
}
public class Demo3 {
public static void main(String[] args) {
MyRunnable runnable = new MyRunnable(); //创建Runnable的子类
Thread thread = new Thread(runnable); //实现该子类的实例,并交给一个线程的实例
thread.start(); //启动该线程
}
}
(3)匿名内部类的方式,创建Thread的子类,创建线程
public class Demo4 {
public static void main(String[] args) {
Thread thread = new Thread(){
@Override
public void run() {
System.out.println(4444);
}
};
thread.start();
}
}
(4)匿名内部类的方式,创建Runnable的子类,创建线程
(实际中用Runnable方法更多些,Runnable只是描述了一个任务,我们可以把这个任务交给线程执行,也可以把这个任务交给线程池执行)
public class Demo5 {
public static void main(String[] args) {
Runnable runnable = new Runnable() {
@Override
public void run() {
System.out.println(5555);
}
};
Thread thread = new Thread(runnable);
thread.start();
}
}
(5)通过lambda表达式方式,创建线程
public class Demo6 {
public static void main(String[] args) {
Thread thread = new Thread(() -> {
System.out.println(5555);
});
thread.start();
}
}
二、Thread类及常见方法
1.Thread的常见构造方法

具体使用构造方法如下:
2.Thread类的几个常见属性

其中我们手动创建的线程默认是前台线程,后台线程也成为守护线程。前台线程会影响主线程结束(也就是前台线程不结束,主线程就不会结束(联系到后面的join),如果主线程结束了,后台线程也就随着结束了)后台线程不影响我们的主线程退出,当主线程结束了,后台线程也跟着自动结束
3.启动线程-start方法
前面我们已经介绍了如何去创建一个线程(通过覆写run方法创建线程对象),但线程对象被创建出来并不意味着线程就开始运行了,而当一个线程调用start方法后,线程才真正的在操作系统的底层创建除一个线程。
main线程,start方法与run方法的区分
public class Demo9 {
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(()->{
for (int i = 0; i < 5; i++) {
System.out.println(9999);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
} //for循环 是t1线程具体要做的事情
}
});
t1.setDaemon(true);//设置t1为后台线程
t1.start(); //main线程启动了t1线程
Thread.sleep(2000); //是main线程的代码
System.out.println("主线程结束"); //是main线程的代码
}
}
t1线程打印:5个“9999”,main线程打印:“主线程结束”
运行截图:
,可以看出main线程等待2时,t1线程打印了三个“9999”,结果是随机的,因为线程是抢占式执行
运行main方法,本身就会启动一个线程,称之为主线程,也就是main线程
start方法与run方法的区别:
(1)start是线程的一个特殊方法,哪个线程调用star就会启动该线程,并执行run方法
(2)run方法 就是一个普通的方法,描述了一个线程主要需要执行的任务内容
public class Demo10 {
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(){
@Override
public void run() {
for (int i = 0; i < 5; i++) {
System.out.println(10);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
};
// thread.start(); //Thread线程执行的
thread.run(); //其实是main线程执行的
for (int i = 0; i < 5; i++) {
System.out.println("main");
Thread.sleep(1000);
}
}
}
分别调用start方法与run方法的运行结果:

其中“main”字符串是在main线程打印的,“10”是在thread里面打印的。
main线程和thread 线程是并发执行的,调用sleep方法之后,会进入阻塞状态。run方法就是一个普通的方法,调用run方法并不会启动线程,启动线程得用start方法。
4.中断线程方法
正常情况下,一个线程会按照从上往下,按照run方法来执行,一直到结束;一些情况下,线程需要其他线程来通知,中断本身线程的执行,中断线程的执行的方法有:
(1)通过标志位(通过改变flag的值)
public class Demo11 {
static int flag = 0;
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(){
@Override
public void run() {
while (flag == 0){
System.out.println("转账中");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("已停止");
}
};
t1.start();
Thread.sleep(2000);
System.out.println("停止转账");
flag = 1;
}
}
(2)通过interrupt去中断
public class Demo12 {
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
while (!Thread.currentThread().isInterrupted()){
System.out.println("转账中");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
break;
}
}
System.out.println("已停止");
}
});
t1.start();
Thread.sleep(2000);
System.out.println("停止转账");
t1.interrupt();
}
}
5.等待线程- join方法
一些场景下,线程需要一定的顺序
| 方法 | 说明 |
| piblic void join() | 等待线程结束(死等) |
| public void join(long mills) | 等待线程结束,最多等待mills毫秒(等固定的时间) |
| public void join(long mills,int nanos) | 同理,但可以更高精度(m+n) |
public class Demo15 {
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(()->{
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("包包安检通过");
});
t1.start();
Thread t2 = new Thread(()->{
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("人安检通过");
});
t2.start();
//main线程要等待t1和t2 执行完
t1.join(5000);//等待t1 执行完成
t2.join();//等待t2 执行完成
System.out.println("全部安检通过");
}
}
t1.join()是main线程等待t1线程执行完成,t1未执行完成时,main线程阻塞
t2.join()是main线程等待t2执行完成,t2未执行完成时,main线程也是阻塞
谁调用了join,谁阻塞,join在谁的代码块里,谁阻塞,main调用了join,join在main的代码块里,所以main阻塞
阻塞等待的时间可以粗略的看做max(t1,t2),t1 线程和t2线程没有先后顺序
(1)当t1在上,t2在下
t1.join();
t2.join();
(1)假如t1先执行完
t1.join() main线程就会一直阻塞,阻塞到t1执行完
t2.join() main线程继续阻塞,阻塞到t2执行完
(2)假如t2 先执行完
t1.join()main线程一直阻塞,阻塞到t执行完了
t2.join()此时t2已结执行完了,main直接就返回了
(2)当 t2在上,t1在下
t2.join();
t1.join();
(1)假如t1先执行完
t2.join()main线程一直阻塞,等待t2执行完了
t1.join()此时,t1已经执行完了,main就直接返回了
(2)假如t2先执行完
t2.join()main就会一直阻塞,等待t2执行完了
t1.join()main继续等待t1执行完
所以main线程最终等待的时间是t1和t2两个线程的最大值。
6.获取当前线程的引用方法
返回当前线程对象的引用:
在哪分线程里调用这个代码,返回的就是谁
public class ThreadDemo {
public static void main(String[] args) {
Thread thread = Thread.currentThread();
System.out.println(thread.getName());
}
}
Thread t3 = new Thread(new Runnable(){
@Pverride
public void run(){
System.out.println(Thread.currentThread().getName());
System.out.println(this.getName());
}
});
这里this.getName会报错,因为Runnnable没有name这个属性,所以要想获取名称,使用Thread.currentThread
7.休眠当前线程
因为线程的调度是不可控的,所以,这个方法只能保证 实际休眠时间 大于等于 参数设置的休眠时间。
| 方法 | 说明 |
| public static void sleep(long millis) throws InterruptedException | 休眠当前线程 millis 毫秒 |
| public static void sleep(long millis, int nanos) throws InterruptedException | 可以更高精度的休眠 |
public class ThreadDemo {
public static void main(String[] args) throws InterruptedException {
System.out.println(System.currentTimeMillis());
Thread.sleep(3 * 1000);
System.out.println(System.currentTimeMillis());
}
}
三、线程的状态
就绪:随叫随到,已经准备好了,就差CPU,等待操作系统的调度
运行:获取到CPU资源,开始干活啦
阻塞:运行的过程中,因为各种原因(比如IO,资源的等待),导致进程不能在继续运行
一共6种状态:(重点理解状态的意义和各个状态的具体意思)
NEW: 安排了工作, 还未开始行动
RUNNABLE: 可工作的. 又可以分成正在工作中和即将开始工作.
BLOCKED: 这几个都表示排队等着其他事情
WAITING: 这几个都表示排队等着其他事情
TIMED_WAITING: 这几个都表示排队等着其他事情
TERMINATED: 工作完成了


本文详细介绍了线程的概念、存在的原因,以及进程与线程的区别。在Java中,线程可以通过继承Thread类或实现Runnable接口创建。讨论了Thread类的构造方法、属性、start方法、中断和等待线程的方法。此外,还阐述了线程的六种状态,包括NEW、RUNNABLE、BLOCKED、WAITING、TIMED_WAITING和TERMINATED。

912

被折叠的 条评论
为什么被折叠?



