多线程(初阶)——多线程基础
文章目录
- 3.创建线程
- 3.1 继承Thread类
- 3.2实现Runnable接口
- 3.3 通过匿名内部类创建线程
- 3.4通过实现Runnable接口的匿名内部类的方式创建线程
- 3.5通过Lambda表达式的方式创建线程(推荐使用)
- 4.多线程的优点
1.认识线程
首先我们得知道线程是什么
进程内的执行单元,不分配单独的资源,执行一个单独的子任务。
线程是进程内调度和分派的基本单位,共享进程资源。每个线程有自己的独立的栈存储空间,保存线程执行的方法以及基本类型的数据。
为啥会有线程
- 我们需要进行“并发编程”(
CPU
单个核心已经发展到了极致,无法满足当前编程需求,想要提高算力,就要使用多个核心) - 引入并发编程的目的就是更充分利用多核
CPU
资源 - 使用多线程可以做到并发编程,并且能够使
CPU
多核被充分利用 - 虽然多进程也能实现并发编程,但是线程比进程更轻量
- 创建线程比创建进程更快
- 销毁线程比销毁进程更快
- 调度线程比调度进程更快
- 一个线程包含在进程之中(一个进程中可以有多个线程)
当我们创建一个进程时
- 创建
PCB
- 分配系统资源(尤其是系统资源)——特别消耗时间
- 把
PCB
加入的内核的双向链表中
而创建线程时
- 创建
PCB
- 把
PCB
加入到内核的双向链表中
节省了大量资源分配的时间,提高了效率
2.多线程程序
2.1 第一个Java多线程程序
Java中创建线程,离不开一个关键的类——Thread
JDK
中提供了一个叫Thread
的类,通过Thread
类创建对象,就可以创建一个线程
public class Demo1 { public static void main(String[] args) { MyTread myTread = new MyTread(); myTread.start(); } } class MyTread extends Thread { @Override //重写run方法 public void run() { System.out.println("thread..."); } }
重写run
方法,就是去指定线程要执行的任务
MyTread myTread = new MyTread();
创建自己的线程对象,本质就是Java
类的一个实例myTread.start();
这个方法让JVM
去操作申请一个真实的PCB
,这就与操作系统扯上关系了,操作系统就能执行我们自己定义的这个线程任务了。
2.2 观察线程的详细情况
当我们在主函数中添加一句这样的代码
public class Demo1 { public static void main(String[] args) { MyTread myTread = new MyTread(); myTread.start(); System.out.println("main..."); } } class MyTread extends Thread { @Override //重写run方法 public void run() { System.out.println("thread..."); } }
此时运行结果是
代码先执行的是myTread.start();
,但为什么先打印的是“main…”,而不是先打印“thread”呢?
public class Demo1 { public static void main(String[] args) { MyTread myTread=new MyTread(); myTread.start(); //在死循环中做打印 while (true){ System.out.println("main..."); try { MyTread.sleep(1000); } catch (InterruptedException e) { throw new RuntimeException(e); } } } } //通过继承Tread类的方式创建一个线程 class MyTread extends Thread{ @Override public void run() { while (true){ System.out.println("thread..."); try { Thread.sleep(1000); } catch (InterruptedException e) { throw new RuntimeException(e); } } } }
Thread.sleep(1000);
让线程休息一会
当代码进行死循环打印时会出现下面的结果
这时我们发现有时候先打印“main…”,有时候会先打印“thread…”
这是由于CPU
调度线程是抢占式执行的,这个现象是程序猿无法控制的,完全是由CPU
内部的执行机制造成的
线程的执行先后顺序,取决于操作系统、调度器的具体实现
此时我们还看不到Java线程,这时我们需要借助JDK
为我们提供的工具jconsole
打开jconsole
之后,我们就能查看线程的情况了(此时程序要处于运行状态)
其他线程都是JVM
自己创建的线程,例如:JC垃圾回收器
2.3 sleep方法
为了方便观察线程的详细情况,我们适当的让线程"休息"一下,减缓刚刚执行的死循环代码,我们可以用sleep
来进行操作
sleep是"休眠"的操作,让线程进入"阻塞"状态,放弃占有CPU
时间片,让给其他线程使用
使用方法:Thread.sleep();
,sleep
是Thread
静态成员方法,可直接通过 类名.方法名的方式调用
形参:毫秒
使用时需要使用try...catch...
处理中断异常
2.4 run和start方法的区别
作用功能不同:
- run方法的作用是描述线程具体要执行的任务;
- start方法的作用是真正的去申请系统线程
- 运行结果不同:
- run方法是一个类中的普通方法,主动调用和调用普通方法一样,会顺序执行一次;
- start调用方法后, start方法内部会调用Java 本地方法(封装了对系统底层的调用)真正的启动线程,并执行run方法中的代码,run 方法执行完成后线程进入销毁阶段。
3.创建线程
3.1 继承Thread类
例如上面的代码,创建一个类继承Thread
类,重写run
方法
public class Demo1 { public static void main(String[] args) { MyTread myTread=new MyTread(); //myTread.start(); myTread.run(); //在死循环中做打印 while (true){ System.out.println("main..."); try { MyTread.sleep(1000); } catch (InterruptedException e) { throw new RuntimeException(e); } } } } //通过继承Tread类的方式创建一个线程 class MyTread extends Thread{ @Override public void run() { while (true){ System.out.println("thread..."); try { Thread.sleep(1000); } catch (InterruptedException e) { throw new RuntimeException(e); } } } }
3.2实现Runnable接口
public class Demo2 { public static void main(String[] args) { //实例化Runnable Runnable myRunnable=new MyRunnable(); //通过Thread构造方法传入参数myRunnable Thread thread=new Thread(myRunnable); thread.start(); } } class MyRunnable implements Runnable{ @Override public void run() { while (true){ System.out.println("MyRunnable"); try { Thread.sleep(1000); } catch (InterruptedException e) { throw new RuntimeException(e); } } } }
Runnable 接口内唯一声明了 run 方法,由 Thread 类实现。使线程与任务分离开,可以更好的解耦合,“高内聚,低耦合”,使用实现Runnable
接口的方法更优
让任务与线程分离,以便后面在修改代码时影响较小,就可以不用关注线程创建代码,只关心线程任务中的代码,方便代码的维护
新建相同任务的线程时,就不用重写run
方法,直接将定义好的Runnable
传入就可以了
3.3 通过匿名内部类创建线程
public class Demo4 { public static void main(String[] args) { Thread t1=new Thread(){ @Override public void run() { while (true){ System.out.println("通过创建Thread类的匿名内部类创建线程"); try { Thread.sleep(1000); } catch (InterruptedException e) { throw new RuntimeException(e); } } } }; t1.start(); } }
3.4通过实现Runnable接口的匿名内部类的方式创建线程
public class Demo5 { public static void main(String[] args) { Thread thread=new Thread(new Runnable() { @Override public void run() { int count=0; while (true){ count++; System.out.println("通过实现Runnable接口的匿名内部类的方式创建线程"+count); try { Thread.sleep(1000); } catch (InterruptedException e) { throw new RuntimeException(e); } } } }); thread.start(); } }
3.5通过Lambda表达式的方式创建线程(推荐使用)
public class Demo6 { public static void main(String[] args) { Thread thread=new Thread(()->{ int count =0; while (true) { count++; System.out.println("通过Lambda表达式的方式创建线程 " + count); try { Thread.sleep(1000); } catch (InterruptedException e) { throw new RuntimeException(e); } } }); thread.start(); } }
4.多线程的优点
增加运行速度
分别使用串行和并行实现10亿次累加
public class Demo7 { private static final long count=10_0000_0000L; public static void main(String[] args) { //串行 serial(); //并行 parallel(); } //并行 private static void parallel() { long begin=System.currentTimeMillis(); Thread t1=new Thread(()->{ long a=0L; for (int i = 0; i < count; i++) { a++; } }); Thread t2=new Thread(()->{ long b=0L; for (int i = 0; i < count; i++) { b++; } }); t1.start(); t2.start(); try { t1.join(); t2.join(); } catch (InterruptedException e) { throw new RuntimeException(e); } long end=System.currentTimeMillis(); System.out.println("并行耗时:"+(end-begin)+"ms."); } //串行 private static void serial() { long begin=System.currentTimeMillis(); long a=0L; for (int i = 0; i < count; i++) { a++; } long b=0L; for (int i = 0; i < count; i++) { b++; } long end=System.currentTimeMillis(); System.out.println("串行耗时:"+(end-begin)+"ms."); } }
运行结果:
这时我们可以看到并行的耗时明显小于串行的耗时,当让任务量大时,多线程的效率会提高不大,但是当任务量不大时,可能多线程的效率还没有单线程的效率高,毕竟创建线程时CPU
的调度也是有开销的。