Java多线程
进程,线程以及多线程
想学习线程首先要搞懂进程,进程指的就是我们正在运行的某个应用程序,例如 QQ,微信以及自己编写的 java 程序运行起来之后都属于一个进程
线程是最小的运算调度单位,一个进程要求至少拥有一个线程,在单核处理器的情况下多线程是以交替形式运行的,并不是同时运行,只是切换的速度比较快而已,多核处理器也是同理,每个处理器都可以调用线程,可以实现同时运行
我们用 java 书写的控制台程序,它本身就是一个进程,mian 函数就是主线程,我们所有的代码都是基于 mian 线程按照顺序往下跑的,有些时候有那种计算量比较大的函数就会很浪费时间,这个时候我们就可以在创建一个线程将这个函数丢给他调用,就不影响主函数的执行时间了,例如发送邮件的时间延迟,我们就可以通过多线程的异步特点来实现
Thread 线程类常用方法
类型 |
方法 |
作用 |
实例 |
start(); |
激活线程开始运行 |
实例 |
run(); |
线程类必须复写的函数,包含该线程需要完成的逻辑代码等 |
实例 |
setName(String name) |
修改当前线程名称 |
实例 |
setPriority(int priority) |
设置线程的优先级 |
实例 |
setDaemon(boolean on) |
设置守护守护线程 ( true ) 和用户线程 ( fasse ) |
实例 |
String getName() |
获取当前线程名称 |
实例 |
int getPriority() |
获取线程的优先级 |
实例 |
join() |
暂定当前线程,由调用者开始下一次执行 |
实例 |
isAlive() |
检测线程是否处于活动状态 |
—— |
———————————— |
—————————————————————————— |
静态 |
yield() |
礼让:停止当前正在运行的线程,调用者插队执行 |
静态 |
sleep(long time) |
睡眠:当前线程暂停运行,time 毫秒后继续执行 |
静态 |
boolean holdsLock(Object x) |
当前线程在指定的对象上保持监视锁时反会 true |
静态 |
Thread currentThread() |
返回当前正在执行的线程 |
线程的创建方式
线程的创建有很多种,这里简单介绍常用的几种
继承 Thread 类
四部曲:继承 Thread 类 → 复写 run 方法 → 创建当前类对象 ( 多态:Thread 类型 ) → start 启动线程
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| public class MyThread1 extends Thread{ public static int count = 300; public static void main(String[] args) { new MyThread1().start(); for (int i = 0; i < count; i++) { System.out.println("main 执行了" + (i + 1) + "次"); }
} @Override public void run() { for (int i = 0; i < count; i++) { System.out.println("【多线程执行】" + (i + 1) + "次"); } } }
|
实现 Runnable 接口
四部曲:实现 Runnable 接口 → 复写 run 方法 → 创建线程类对象 ( 将当前类示例交给他 ) → start 启动线程
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| public class MyThread2 implements Runnable { @Override public void run() { for (int i = 0; i < 200; i++) { System.out.println("【多线程执行】" + (i + 1) + "次"); } } public static void main(String[] args) { new Thread( new MyThread2() ).start(); for (int i = 0; i < 200; i++) { System.out.println("main 执行了" + (i + 1) + "次"); } } }
|
上述代码可以使用 Lambda 表达式快速创建线程:
1 2 3 4 5 6 7 8 9 10 11 12 13
| public class MyThread2 { public static void main(String[] args) { int count = 300; new Thread( ()->{ for (int i = 0; i < count; i++) { System.out.println("【多线程执行】执行了" + (i + 1) + "次"); } }).start(); for (int i = 0; i < count; i++) { System.out.println("main 执行了" + (i + 1) + "次"); } } }
|
继承 Thread 类和实现 Runnable 接口的区别
我们来写一个卖票的小练习来了解一下两种线程创建的区别
Runnable 接口实现的多线程:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| public class MyBuyTicket implements Runnable{
public int ticketNum = 20;
public static void main(String[] args) { MyBuyTicket mb = new MyBuyTicket(); Thread t1 = new Thread(mb); Thread t2 = new Thread(mb); Thread t3 = new Thread(mb); t1.setName("老八"); t2.setName("卢本伟"); t3.setName("菜虚鲲"); t1.start(); t2.start(); t3.start(); }
@Override public void run() { while ( ticketNum > 0 ){ ticketNum--; System.out.println("【" + Thread.currentThread().getName() + "】抢到了一张票"); } }
}
|
Thread 类实现的多线程
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| public class MyBuyTicket extends Thread{
public int ticketNum = 20;
public static void main(String[] args) { Thread t1 = new MyBuyTicket(); Thread t2 = new MyBuyTicket(); Thread t3 = new MyBuyTicket(); t1.setName("老八"); t2.setName("卢本伟"); t3.setName("菜虚鲲"); t1.start(); t2.start(); t3.start(); }
@Override public void run() { while ( ticketNum > 0 ){ ticketNum--; System.out.println("【" + Thread.currentThread().getName() + "】抢到了一张票"); } }
}
|
通过这个小练习我们可以看出,Thread 每创建一个线程相应的就必须创建一个对象,而 Runnable 可以实现多个线程操作同一个对象,共享对象内的数据,使用多线程的情况下,推荐优先使用 Runnable 接口
线程的生命周期
线程从创建到结束共分为大概五个生命周期:
- 【新建】:线程被创建出来,也就是
new Thread()
- 【就绪】:线程已经创建完毕并且启动了,也就是已经调用
start()
方法了,随时等待 CPU 的调度
- 【运行】:线程被 CPU 调度,正在执行 run 方法内的逻辑
- 【阻塞】:没有执行的资格和执行权利,例如
sleep(long time)
- 【销毁】:线程任务已经完成,没有再次使用的地方,线程的对象变成垃圾,释放资源。
正常线程的流程就是【新建】→【就绪】→【运行】→【可能会有阻塞】→【就绪】→【运行】…→【销毁】
线程睡眠
线程睡眠是指暂时让线程停止运行,在约定时间内不接收 CPU 的调度,用法:Thread.sleep(long time)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| public class MyThread3 implements Runnable{ @Override public void run() { for ( int time = 1; time <= 10; time++ ){ System.out.println( "本站已不间断运行" + time + "秒"); try { Thread.sleep(1000); } catch (InterruptedException e) { System.out.println("线程停止失败!"); e.printStackTrace(); } } } public static void main(String[] args) { new Thread(new MyThread3()).start(); } }
|
结束运行
让线程停止运行,JDK 提供了 stop 实例函数进行停止:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| public class MyThread3 implements Runnable{ @Override public void run() { for ( int count = 1; true; count++ ) System.out.println("线程跑了" + count + "次"); } public static void main(String[] args) throws InterruptedException { MyThread3 mt = new MyThread3(); Thread t1 = new Thread(mt); t1.start(); Thread.sleep(1000); t1.stop(); } }
|
经测试这样是可以停止线程的,但是官方并不建议我们这么使用,推荐自己定义一个标识在外部控制停止:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| public class MyThread3 implements Runnable{ public boolean flag = true; @Override public void run() { for ( int count = 1; flag; count++ ) System.out.println("线程跑了" + count + "次"); } public void stop(){ this.flag = false; System.out.println("线程停止成功"); } public static void main(String[] args) throws InterruptedException { MyThread3 mt3 = new MyThread3(); new Thread(mt3).start(); Thread.sleep(1000); mt3.stop(); } }
|
线程礼让
我们都知道线程是被 CPU 随机调度的,两个线程在一起的时候,如果想让另一个执行的多一点可以使用线程礼让,线程礼让属于阻塞的函数,需要注意的是,礼让仅仅是放弃当此调度,CPU 会重新从两个线程中再选出一个进行调度,选中的可能还是之前的线程,也就是说,线程礼让并不会 100% 成功
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| public class MyThread3 implements Runnable{ @Override public void run() { for ( int i = 1; i <= 100; i++ ){ if( i >= 90 && Thread.currentThread().getName().equals("老八") ){ System.out.println("【老八选择礼让】"); Thread.yield(); } System.out.println( "【" + Thread.currentThread().getName() + "】运行了" + i + "次"); } } public static void main(String[] args) { MyThread3 mt3 = new MyThread3(); Thread t1 = new Thread(mt3); Thread t2 = new Thread(mt3); t1.setName("老八"); t2.setName("菜虚鲲"); t1.start(); t2.start(); }
|
线程等待
正在运行的线程中如果调用了 join 函数就会停止,下一个运行的函数就是 join 的调用者:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| public class MyThread3 implements Runnable{ @Override public void run() { for ( int i = 1; i <= 1000; i++ ){ try { Thread.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println( "【" + Thread.currentThread().getName() + "】运行了" + i + "次"); } } public static void main(String[] args) throws InterruptedException { Thread t1 = new Thread(new MyThread3()); Thread t2 = new Thread(new MyThread3()); t1.setName("老八"); t2.setName("菜虚鲲"); t1.start(); t2.start(); for (int i = 0; i < 1000; i++) { Thread.sleep(1); System.out.println( "【main线程】运行了" + i + "次"); if ( i == 500 ){ System.out.println("main已经跑到500,T来插个队"); t1.join(); } } } }
|
线程优先级
当我们创建了多个线程同时启动的时候,他们会被 CPU 随机调度,每次执行的顺序都不同,这里我们可以通过设置优先级来决定执行的先后顺序
setPriority(int priority)
:设置线程的优先级,需要 int 类型的值作为优先级参数,默认优先级为 5,取值范围为 1-10,数值越大对应的优先级越高,可以通过 setPriority()
来获取线程优先级,需要注意的是优先级高的线程会在调度时被给予优先,但这不代表启动后就会运行
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| public class MyThread3 extends Thread{
public String msg;
public MyThread3(String msg) { this.msg = msg; }
@Override public void run() { System.out.println(this.msg); }
public static void main(String[] args) { Thread t1 = new MyThread3("我想第1个执行"); Thread t2 = new MyThread3("我想第2个执行"); Thread t3 = new MyThread3("我想第3个执行"); Thread t4 = new MyThread3("我想第4个执行"); Thread t5 = new MyThread3("我想第5个执行"); t1.setPriority(5); t2.setPriority(4); t3.setPriority(3); t4.setPriority(2); t5.setPriority(1); t1.start(); t2.start(); t3.start(); t4.start(); t5.start(); }
}
|
误区:并不是优先级高执行结束后在执行优先级低的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| public class MyThread3 extends Thread{
public MyThread3(String name) { super(name); }
@Override public void run() { for (int count = 0; count < 1000; count++) System.out.println("【" + this.getName() + "】执行了" + count + "次"); }
public static void main(String[] args) { Thread t1 = new MyThread3("老八"); Thread t2 = new MyThread3("菜虚鲲"); t1.setPriority(6); t1.start(); t2.start(); }
}
|
用户线程和守护线程
线程中包含了两种类型,为用户线程和守护线程,用户线程可以在任务未完成的情况下可以无休止的运行,而守护线程则是主线程 ( mian ) 运行结束后关机 JVM 虚拟机时就跟着停止运行了,我们创建的线程没有修改过的话默认都是用户线程,修改线程类型的函数为 setDaemon()
,用户线程为 false,守护线程为 true
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| public class MyThread4 extends Thread{
@Override public void run() { for (int i = 1; i <= 100000; i++) System.out.println("线程运行了" + i + "次"); }
public static void main(String[] args) throws InterruptedException { Thread t1 = new MyThread4(); t1.start(); }
}
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| public class MyThread4 extends Thread{
@Override public void run() { for (int i = 1; i <= 100000; i++) System.out.println("线程运行了" + i + "次"); }
public static void main(String[] args) throws InterruptedException { Thread t1 = new MyThread4(); t1.setDaemon(true); t1.start(); Thread.sleep(500); }
}
|
结果:用户线程等待 10w 条数据打印完成才结束程序,而守护线程打印到 4w 多的时候就结束了程序