多线程(初阶)——多线程基础
文章目录
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进程的情况(需要执行死循环操作)
此时我们还看不到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
的调度也是有开销的。
觉得不错就给个三连吧