【JavaEE 初阶(一)】初识线程

发布于:2024-05-05 ⋅ 阅读:(28) ⋅ 点赞:(0)

❣博主主页: 33的博客
▶️文章专栏分类:JavaEE◀️
🚚我的代码仓库: 33的代码仓库🚚
🫵🫵🫵关注我带你了解更多线程知识

在这里插入图片描述

1.前言

我们设想:一家公司要去银行办理业务,既要进行财务转账,又要进行福利发放,还得进行缴社保。如果只有张三一个会计就会忙不过来,耗费的时间特别长。为了让业务更快的办理好,张三又找来两位同事李四、王五一起来帮助他,三个人分别负责一个事情,分别申请一个号码进行排队,自此就有了三个执行流共同完成任务,但本质上他们都是为了办理一家公司的业务。此时,我们就把这种情况称为多线程,那么在Java中该如何实现多线程呢?我们一起来学习吧!!


2.进程

提到线程,我们就不得不先了解进程了,那么什么是进程呢?进程就是运行起来的一个个程序。 引入进程有什么作用呢?是为了实现并发编程。但进程有很多,系统是如何管理它们的呢?那就是"先描述,再组织"

描述:使用PCB结构表示进程的各种属性。
PCB中的重要特性: 1.pid(进程标识符)2.内存指针(表示进程持有的内存资源,比如内存空间具体在哪里,有哪些部分组成,每个部分干啥的等等)3.文件描述表(表示进程持有的硬盘资源,类似于顺序表)4.CPU资源(状态、优先级、上下文、记账消息,这些都主要来完成进程调度)

组织:利用双显链表,把PCB的这些结构串起来。
进程是资源分配的基本单元,创建进程需要给他分配内存和硬盘资源需要消耗的时间太多,销毁一个进程销毁的时间很多,调度一个进程消耗的时间也很多,如果频繁的创建或者销毁一个资源,这个时候的开销就是非常大的,为了解决这个问题,我们引入了线程(Thread)。


3.线程

线程也叫做“轻量级进程”,创建线程,销毁线程,调度线程都比进程更快。但线程不能独立存在,要依附于进程,即一个进程至少包含一个线程!但每一个线程都是独立执行的,一个进程,最开始的时候必须要包含一个线程,这个线程负责完成执行代码的工作,也可以创建出多个线程,完成并发执行的效果。

线程的特点
1.每一个线程都可以独立的去cpu上执行
2.同一个进程的多个线程之间公用一份内存空间和文件资源
3.同一个进程中,采用多线程的方式能提高效率,但有一定限度,如果无限度的引入线程可能是调度开销增大,导致效率反而降低
4.同一个进程中,一个线程抛出异常,如果没有妥善处理可能会导致整个进程崩溃。


4.线程和进程的区别

1.一个进程包含一个或多个线程
2.进程和线程都是用来实现并发编程的,但线程比进程更轻量,更高效
3.进程和进程之间具有独立性,如果一个进程异常不会影响到其他进程,但线程和线程之间可能会相互影响
4.进程是资源分配的基本单位,线程是调度执行的基本单位


5.Thread创建线程

线程是操作系统的概念,操作系统提供了一些API可以操作线程,Java对系统API进行了封装,使用Thread类就可以创建出一个线程.

5.1继承Thread创建线程

方法1:

package Thread;
class MyThread extends Thread{
    @Override
    //线程的入口
    public void run() {
        while (true) {
            System.out.println("hello thread");
			//隔1s轮转
            try {
                Thread.sleep(1000);//1s
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}
public class Demo1 {
    public static void main(String[] args) throws InterruptedException {
        Thread t=new MyThread();
        //start和run都是Thread的成员
        //run只是描述线程的入口,线程主要做什么
        //start则是真正的调用了系统API创建了线程,让线程再调用run()
        t.start();
        while (true){
            System.out.println("hello main");
            Thread.sleep(1000);
        }

    }
}

方法2:匿名内部类

package thread;
public class Demo3 {
    public static void main(String[] args) {
        Thread t = new Thread() {
        //匿名内部类
            @Override
            public void run() {
                while (true) {
                    System.out.println("hello thread");
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        };
        t.start();
        while (true) {
            System.out.println("hello main");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

注意
1.Thread在java.lang这个包下并不用导包
2.start方法内部会调用系统的API生成一个线程再调用run函数,和run函数的效果类似,但本质区别为:是否会创建一个线程


5.2实现Runnable接口

方法1:

package thread;
class MyRunnable implements Runnable {
    @Override
    public void run() {
        while (true) {
            System.out.println("hello thread");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}
public class Demo2 {
    public static void main(String[] args) {
        Runnable runnable = new MyRunnable();
        Thread t = new Thread(runnable);
        t.start();
        while (true) {
            System.out.println("hello main");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

方法2:匿名内部类

package thread;
public class Demo4 {
    public static void main(String[] args) {
        Thread t = new Thread(new Runnable() {
            @Override
            public void run() {
                while (true) {
                    System.out.println("hello thread");
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        });
        t.start();
        while (true) {
            System.out.println("hello main");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

5.3lambda表达式

package thread;
public class Demo6 {
    public static void main(String[] args) {
     //lambda表达式
        Thread t = new Thread(() -> {
            while (true) {
                System.out.println("hello thread");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"第一个线程");
       t.start();
       while (true) {
            System.out.println("hello main");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

6.Thread常见方法

6.1Thread常见属性

属性 获取方法
ID getId
名称 getName
状态 getState
优先级 getPriority
是否后台线程 isDaemon
是否存活 isAlive
是否被中断 isInterrupted

ID 是线程的唯一标识,不同线程不会重复
名称是各种调试工具用到
状态表示线程当前所处的一个情况,下面我们会进一步说明
优先级高的线程理论上来说更容易被调度到
后台线程:守护线程,后台线程结束与否不影响整个程序,但如果前台线程没有结束,进程也不会结束。
是否存活:判断内核线程是否已经销毁

6.2 中段一个线程- interrupt()

首先我们要知道,一个线程如果时间特别长,那么大概率在线程的内部有循环再一直执行。如果想要中断此线程,那么我们就想办法尽快让run函数执行完成,那么怎么能让循环快速执行完呢?其实我们只需要在循环处添加一个条件,条件不成立就结束了。
例如:

public class Demo7 {
    public static boolean  flag=false;
    public static void main(String[] args) throws InterruptedException {
        Thread t=new Thread(()->{
            while (!flag){
                System.out.println("t进程");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println("工作完毕");
        });
        t.start();
        Thread.sleep(5000);
        flag=true;
        System.out.println("打断进程");
    }
}

注意
定义flag的时候只能定义为成员变量,不能定义为局部变量,因为在lambda有一个语法规则:变量捕获。把当前作用域的变量在lambda中复制了一份,在变量捕获时有一个前提限制:必须只能捕获final修饰的变量或者变量不能做任何修改。如果把flag设置为成员变量,就不再是变量捕获的与法律,而是内部类访问外部类的属性。

但是如果我们每次都专门定义一个标志位来打断线程是非常麻烦的,而且当处于睡眠模式下还不能立即就想应,在Thread类中有有一个标志位isInterupted来判定线程是否被打断。

public class Demo8 {
    public static void main(String[] args) throws InterruptedException {
        Thread t=new Thread(()->{
            while (!Thread.currentThread().isInterrupted()){
                System.out.println("t进程");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                    //唤醒sleep有3种操作
                    //1.不管它,继续执行t线程
                    //2.立即执行打断
                    break;
                    //3.进行一些其他操作
                }
            }
        });
        t.start();
        System.out.println("main进程");
        Thread.sleep(5000);
        System.out.println("t进程打断");
        t.interrupt();//打断
    }
}

说明
1.Thread.currentThread()表示当前线程即t线程
2.在正常情况下,sleep休眠时间完成才会被唤醒,如果调用interrupt()那么就提提前唤醒它触发InterruptedException异常
观察下图:虽然打断了t线程并且触发了InterruptedException异常,但t线程依然在执行。
在这里插入图片描述
出现上述情况是因为:interr唤醒sleep之后会抛出异常当同时也会清除标志位,这就使打断效果像没有生效一样。Java期望当线程收到“要中断”的信号使,由本身来决定接下来该怎么做。

6.3等待线程-jion()

等待一个线程即使指,当一个线程执行完毕时才能执行另外一个线程。

public class Demo9 {
    public static void main(String[] args) throws InterruptedException {
        Thread t=new Thread(()->{
            for (int i=0;i<5;i++){
                System.out.println("t线程正在执行");
            }
        });
        t.start();
        //等待t线程执行结束再执行main线程
        t.join();
        System.out.println("t线程执行结束");
        System.out.println("main线程");

    }
}

7.线程的状态

在进程中最核心的状态就是就绪和阻塞状态,在线程中同样适用,同时java又赋予了线程一些其他的状态:

NEW:创建Thread对象安排了工作,但没有启动。
RUNNABLE:指就绪状态,即线程正在执行或者线程等待执行
TERMINATED: 工作完成了
TIMWD_WAITING:阻塞,由于sleep这种固定的时间方式而阻塞
WAITING: 阻塞,由于wat这种不固定的时间方式而阻塞
BLOCKED: 阻塞,由于锁竞争导致的阻塞
在Java中可以通过getState()方法来查看此时线程的状态。

2.1代码实现

public class Demo10 {
    public static void main(String[] args) throws InterruptedException {
        Thread t=new Thread(()->{
            for (int i=0;i<3;i++){
                System.out.println("t线程在执行");
            }
        });
        System.out.println(t.getState());
        t.start();
        System.out.println(t.getState());
       Thread.sleep(3000);
        System.out.println(t.getState());
    }
}

2.2运行结果

在这里插入图片描述

总结:本篇文章主要介绍了线程的概念,为什么要引入线程,要理解线程和进程的区别,创建线程的几种方法,会运用Thread的一些常见方法。

下期预告:线程安全问题