1.认识线程
1.1概念
1)线程是什么
线程是在进程内部中进行运行的,可以把它想成一个“执行流“,每个线程负责执行线程内的部分代码,多个线程之间可以”同时“执行多个代码。
“同时”:指并行,采用分时复用,并不是真正意义上的同时,而是他一会运行a线程,一会运行b线程,只是运行的速度过快,让人觉的a和b是在同时运行。
我们可以先把进程理解为一个医院的门诊部,而门诊部是一个大类,这里面又有多个门诊,例如:内科门诊,外科门诊,妇科门诊,如果让张三一个人来做是负责不过来的,所以张三叫来了李四,王五,一个人负责一个门诊,这样就大大提高了效率。
而张三,李四,王五就是线程,即多线程,其中张三是主线程,因为李四线程和王五线程是由张三线程叫来的。
2)线程的作用
a.如果一个cpu想要提高效率,那么就需要多核cpu,而并发编程能够充分利用多核cpu。
b.进程虽然也能实现并发编程,但是线程比进程更轻量
a) 创建线程比创建进程跟快
b) 删除线程比删除进程更快
c) 调度线程比调度进程更快
3)进程和线程的区别
a.进程包含线程,每个进程内至少有一个线程
b.不同的进程不在一个空间内,但一个进程内的不同线程都在一个空间内
依然是用门诊部来举例,虽然张三 李四 王五,三个线程在门诊部的不同门诊工作,但是都隶属于门诊部,负责的也都是患者的初步诊断,然后转给其他科室。
一个重伤的患者进入医院会由急诊部这进程来直接接收,不会交到门诊部这个线程;
一个普通患者来医院做检查会由门诊部这个进程做初检,不会交到急诊部这个线程。
c.一个进程出现问题不会影响到别的进程,但一个进程内的一个线程出问题可能会影响到同一个进程的线程(或者导致整个进程奔溃)。
例如有一天门诊部很忙,但这时张三因为高强度的工作累倒了,不能工作了,这个时候张三线程负责的任务就没人来执行,而这个门诊又堆满了人,要检查的人卡在了这个部门,越来越多,如果有其他医生来帮忙还能解决,但是如果没有,那么这就像一段代码卡在这运行不了,最后越积越多,直至崩溃。
4)JAVA的线程和操作系统线程的关系
a.线程是操作系统中的概念,操作系统内容实现了线程的概念。
b.JAVA中的Thread可以视为对线程的进一步的抽象和分封装
1.2 创建线程
1)继承Thread类
package thread;
//自定义线程类继承Thread类
public class text1 {
//自定义线程类继承Thread类
public static class MyThread1 extends Thread{
@Override
public void run(){
//打印10次
for(int i=0;i<10;i++){
System.out.println("继承Thread的线程");
}
//每隔一秒打印一次
//因为该写法可能会抛异常,所以需要 try和catch联合使用
try {
Thread.sleep(1000);
}catch(InterruptedException e){
e.printStackTrace();
}
}
}
public static void main(String[] args) {
//实例化对象
MyThread1 t = new MyThread1();
//启动线程
t.start();
}
}
2) 实现Runnable接口
package thread;
//自定义实现Runnable接口的线程
public class text2 {
public static class MyRunnable1 implements Runnable {
@Override
public void run(){
//打印
for(int i=0;i<10;i++){
System.out.println("实现Runnable接口的线程");
}
//每隔一秒打印一次
try{
Thread.sleep(1000);
}catch(InterruptedException e){
e.printStackTrace();
}
}
}
public static void main(String[] args) {
//创建线程
MyRunnable1 myRunnable1=new MyRunnable1();
Thread t1=new Thread(myRunnable1);
//启动线程
t1.start();
}
}
3)Lambda表达式
- Lambda为最常用的创建线程方式
package thread;
public class text3 {
public static void main(String[] args) {
// 创建Lambda线程实例
Thread t=new Thread(()->{
for(int i=0;i<10;i++){
System.out.println("Lambda线程");
}
});
//启动线程
t.start();
}
}
1.3)多线程的优势
- 提高速率
2.Thread类及常见方法
2.1 Thread的常见构造方法
Thread() | 创建线程对象 |
Thread(Runnable runnable) | 利用Runnable对象创建线程对象 |
Thread(String name) | 创建线程对象并命名 |
Thread(Runnable runnable String name) | 利用Runnable对象创建线程对象并命名 |
- Thread t1=new Thread();
- Thread t2 =new Thread(new Runnable());
- Thread t3=new Thread("名字");
- Thread t4=new Thread(new Runnable(),"名字");
2.2 Thread的几个常见属性
ID | getId() |
名称 |
getName() |
状态 | getState() |
优先级 | getPriority() |
是否为后台程序 | isDaemon() |
是否存活 | isAlive() |
是否被中断 | isInterrupted() |
//Thread的常见属性简介
package thread;
public class text12 {
public static void main(String[] args) {
//创建线程
Thread t=new Thread(()->{
System.out.println("线程");
});
//取得当前的线程id
System.out.println("id: "+ t.getId()); //23
//取得当前线程的名称 因为我们没有为线程初始命名 所以系统会自动分配一个名称
System.out.println("name: "+ t.getName()); //Thread-0
//取得当前线程的状态
System.out.println("state: "+ t.getState());; //NEW
//取得当前线程的优先级
//java线程的优先级范围为1-10
//1最低 10最高
//默认情况下,线程的优先级为5
System.out.println("priority: "+ t.getPriority()); //5
//取得当前线程是否为后台程序
//前台程序:就是我们当前我们所创建的线程
//后台程序:在我们看不到的地方,系统自己会有多线程运行
System.out.println("daemon: "+ t.isDaemon());
//取得当前线程是否存活
//如果当前的线程没有启动,则显示不存活为false
//如果当前的线程启动了,则显示存活为true
System.out.println("运行线程前的live: "+ t.isAlive()); // false
t.start();
System.out.println("运行线程后的live: "+ t.isAlive()); //true
//取得当前线程是否被中断的信息
System.out.println("interrupted: "+ t.isInterrupted()); //false
}
}
2.3 启动一个线程—start()
前面我们已经使用了复写一个run方法来创建Thread对象,但创建了并不代表线程开始运行了。
- 我们使用run方法是为了给一个指令让该线程做什么;
- 线程对象可以把其他线程叫了过来;
- 而调用start()方法则是正在让线程运行起来;
调用start()方法才是真正的在操作系统中创建了一个线程出来。
2.4 中断一个线程
中断就如字面意思,就比如当我们正在做一道题目的时候,发现写错题目了,这个时候我们就要停止了,而线程中的中断则是让一段代码停下来。
方法1:自定义变量
package thread;
public class class13 {
//自定义flag变量
public static boolean flag=true;
public static void main(String[] args) throws InterruptedException {
Thread t=new Thread(()->{
//flag为true时,循环打印线程
while(flag){
System.out.println("线程");
}
try{
Thread.sleep(1000);
}catch(InterruptedException e){
e.printStackTrace(); }
});
t.start();
System.out.println("线程结束前");
Thread.sleep(2000);
//将falg变为false 结束线程
flag=false;
System.out.println("结束线程");
}
}
方法2:使用Thread自带方法interrupted()
package thread;
public class class14 {
public static void main(String[] args) throws InterruptedException {
Thread t=new Thread(()->{
//isInterrupted() 是判断是否被中断
//currentThread() 是取得当前Thread的应用
//因为在lambad表达式中isInterrupted() 是在new Thread之前就实现了
//所以我们需要将两个方法搭配使用
while(!Thread.currentThread().isInterrupted()){
System.out.println("线程");
}
});
System.out.println("线程开始前");
t.start();
Thread.sleep(1000);
t.interrupt();
System.out.println("线程终止");
}
}
当在lanmbad表达式中使用的sleep时,main方法中的t.interrupt会提前唤醒sleep导致程序报错,我们可以把它分为三种情况。
a.直接停止
b.不管你
c.我先把事做完在停止
具体情况是程序员根据实际情况法运行。
2.5 等待一个线程—join()
有两段线程时,如果我们想让其中的一段代码先运行,那么就需要用到join()方法。
假设有t1和t2两段线程,如果我们想让t1先运行完,
那么我们就可以对t1使用join()方法,让t2成为等的那一方,t1成为被等的一方
反之我们想让t2先运行
那么我们就可以对t2使用join()方法,让t1成为等的那一方,t2成为被等的一方
package thread;
public class text {
public static void main(String[] args) throws InterruptedException {
Thread t1=new Thread(()->{
for(int i=0;i<5;i++){
System.out.println("线程t1");
}
});
Thread t2=new Thread(()->{
for(int i=0;i<5;i++){
System.out.println("线程t2");
}
});
t1.start();
t2.start();
t1.join();//等待t1线程执行完
}
}
2.6 获取当前线程的引用
返回当前线程的引用 | currentThread() |
package thread;
public class text17 {
public static void main(String[] args) {
//获取当前线程对象
Thread t=Thread.currentThread();
//获取线程名称
System.out.println(t.getName());
}
}
2.7 休眠当前的线程
Thread.sleep() | 让当前线程进入休眠状态 |
- 休眠的本质是让线程的状态变为”堵塞“
- 此线程就参与cpu的调度
- 直到时间到,该线程恢复于就绪状态(不是立即执行),才能参与cpu调度
3.线程的状态
3.1 观察线程的所有状态
线程的状态是枚举类型的Thread.State
- //获取线程的各类状态
- public static void main(String[] args) {
- for(Thread.State state : Thread.State.values()){
- System.out.println(state);
- }
- }
NEW | 还没开始工作的状态 |
RUNNABLE | 可工作状态 可分为正在工作或准备工作 |
BLOCKED | 等待状态(被上锁) |
WAITING | 等待状态 |
TIMED_WAITING | 等待状态(有时间限定) |
TERMINATED | 工作完成状态 |