JAVAEE----多线程1

java中进行多线程编程,操作系统提供了一组关于线程的API(C语言风格),java对于这组API进一步封装之后,就成了 Thread类,Thread类来表示/操作线程
总结上面的这段话就是,我们在java中进行多线程编程,需要用到Thread类来和线程打交道
创建好的 Thread实例和操作系统中的线程是一一对应的


那下面我们就来说下Thread类的基本用法

通过Thread类创建线程写法有很多种

一种

第一步:创建子类继承自Thread,并重写run方法      run方法--->描述了这个线程内部要执行哪些代码

第二步:创建 MyThread 类的实例

第三步:main方法中调用start方法
调用这里的start()才是真正在系统中创建了线程哦
JAVAEE----多线程1_第1张图片
我们需要知道的是: 一个java进程中,至少会有一个线程---->调用main方法的线程(这是自带的,不是我们自己手动创建的)
那我们 自己创建的线程就和自动创建的main线程是并发执行的(此处的并发=并行+并发)

看下面这个例子,在创建的基础上,进行了两个线程的并发执行

注意我们这里的两个线程都进行了循环,我们在循环里加了Thread.sleep(),这个代表强制这个线程每打印一遍就休眠一会儿(进入阻塞状态),(try catch的出现是因为写sleep的时候会有异常,为了去掉异常加了他俩)

注意,线程之间的调度顺序(也就是每次先打印谁)是随机的

JAVAEE----多线程1_第2张图片
JAVAEE----多线程1_第3张图片

Thread类创建线程的方法

第二种

1.创建一个类,实现Runnable接口,
2.创建 Thread 类实例, 调用 Thread 的构造方法时将 Runnable 对象作为 target 参数.
3.调用 start 方法
JAVAEE----多线程1_第4张图片

JAVAEE----多线程1_第5张图片

第三种(是第一种的翻版)--->使用了匿名内部类继承自Thread类

JAVAEE----多线程1_第6张图片

第四种

(是第二种的翻版)----->new的Runnable,针对这个创建的匿名内部类,同时new出的Runnable实例传给Thread的构造方法
JAVAEE----多线程1_第7张图片

第五种

--->相当于第四种的延伸,使用lambda表达式代替了Runnable
JAVAEE----多线程1_第8张图片

多线程的作用和目的

----->提高任务完成的效率

我们通过一个例子来说明多线程并发执行为什么能提高效率:

package thread;

public class Demo7 {
    private static final long count = 100_0000_0000L;

    public static void serial() {//串行执行!!!!!!
        // 记录程序执行时间.
        long beg = System.currentTimeMillis();
        long a = 0;
        for (long i = 0; i < count; i++) {
            a++;
        }
        long b = 0;
        for (long i = 0; i < count; i++) {
            b++;
        }
        long end = System.currentTimeMillis();
        System.out.println("消耗时间: " + (end - beg) + "ms");
    }

    public static void concurrency() throws InterruptedException {//两个线程并发执行!!!!!
        long beg = System.currentTimeMillis();
        Thread t1 = new Thread(() -> {
            long a = 0;
            for (long i = 0; i < count; i++) {
                a++;
            }
        });
        t1.start();
        Thread t2 = new Thread(() -> {
            long b = 0;
            for (long i = 0; i < count; i++) {
                b++;
            }
        });
        t2.start();

        // 此处不能直接这么记录结束时间. 别忘了, 现在这个求时间戳的代码是在 main 线程中.
        // main 和 t1 t2 之间是并发执行的关系, 此处 t1 和 t2 还没执行完呢, 这里就开始记录结束时间了. 这显然是不准确的.
        // 正确做法应该是让 main 线程等待 t1 和 t2 跑完了, 再来记录结束时间.
        // join 效果就是等待线程结束. t1.join 就是让 main 线程等待 t1 结束. t2.join 让 main 线程等待 t2 结束.
        t1.join();
        t2.join();
        long end = System.currentTimeMillis();
        System.out.println("消耗时间: " + (end - beg) + "ms");
    }

    public static void main(String[] args) throws InterruptedException {
        // serial();
        concurrency();
    }
}

当使用一个线程的时候(串行执行)---->serial()方法---->时间大概是600多ms
当使用两个线程的时候(并行执行)----->concurrency()方法----->时间大概是400多ms
注意:并不是说一个线程是600ms,两个线程就是300ms,因为两个线程在的底层是并行执行还是并发执行是不确定的
我们发现使用两个线程比一个线程效率高, 因此我们真正知道了多线程能提高任务效率
但是!多线程也不是万能良药,多线程适合的场景是CPU密集型的程序,程序要进行大量的计算,使用多线程就可以更充分的利用CPU的多核资源

Thread的常见构造方法

这是给线程(thread对象)起一个名字,起一个什么样的名字不影响线程本身的执行,他的作用就是可以 让程序员在调试的时候对线程做出区分
举个例子:

这里插播一个

比如你在Demo8.Java中写完,可以通过jconsole来调试

jconsole是jdk自带的一个调试工具

JAVAEE----多线程1_第9张图片

JAVAEE----多线程1_第10张图片

JAVAEE----多线程1_第11张图片


 Thread的常见属性 

ID 是线程的唯一标识,不同线程不会重复
名称是各种调试工具用到

 状态表示线程当前所处的一个情况

 优先级高的线程理论上来说更容易被调度到

JAVAEE----多线程1_第12张图片
JAVAEE----多线程1_第13张图片
这个下面会讲到
JAVAEE----多线程1_第14张图片

启动一个线程--->start()

start()----->(这个我们上面提到了,但是还要具体说一下)它 决定了系统中是不是真的创建出线程
这里会出现一个面试题---->start和run的区别:
start()是一个特殊的方法,内部会在系统中创建线程
run()是一个普通的方法,描述了任务的内容
再细说一下他们两个的区别:
先说run,比如下面这个例子,执行结果是先打印5遍"hello thread",之后再打印"hello main"
这说明 在main线程里调用run,没有创建新的线程,这个循环仍然是在main线程中执行的.既然是在一个线程中执行,代码就得从前到后按顺序运行,run是串行执行,也就是下图这样先运行完第一个循环线程,再运行第二个循环线程
JAVAEE----多线程1_第15张图片
说完run,说start,start在下面的例子里就会创建一个新线程,两个线程进行并发执行,"hello thread"和"hello main"交替着打印
JAVAEE----多线程1_第16张图片
至此,我们就说完run和start的区别

中断线程--->让一个线程停下来

一般情况线程停下来的关键是让线程对应的run方法执行完
但有一个特殊情况就是,main线程.对于main线程来说,main方法执行完,线程就完了

中断线程的方法

1.

JAVAEE----多线程1_第17张图片
JAVAEE----多线程1_第18张图片

注意:此处因为多个线程共用一个虚拟地址空间,因此main线程修改的isQuit才和t线程判定的isQuit是同一个值

2.更好的方法:

有两种方法可以选择:

(1)通过Thread.interrupted()------->这是静态的方法,但这种方法一般不用

(2)通过Thread.currentThread().isInterrupted()---------->这是实例方法,其中currentThread能够获得当前线程的实例,我们一般都用这种!!!

下面我们来看一个中断线程的例子(我们使用的是第二种方法):

JAVAEE----多线程1_第19张图片

由于上面图中代码绝大多数情况都在休眠状态,对应着t.interrupt();的第二种情况,因此我们需要让他报完异常就尽快中断线程
我们有两种方法
1.这种就是报完异常立即中断(方法是加一个break)
JAVAEE----多线程1_第20张图片
2.这种是报完异常,完成一下收尾工作就中断
(方法是在1.的基础上,在break上面加一个System.out.println("这是收尾工作")
JAVAEE----多线程1_第21张图片

因此到此我们能够完整且正确的写出中断线程的第二种方法的代码

JAVAEE----多线程1_第22张图片

线程等待

线程之间的执行是按照调度器来安排的,这个过程可以视为线程执行顺序是随机的
但顺序随机并不好, 有时我们需要能控制线程之间的的顺序
线程等待就是其中一种控制线程执行顺序的手段
此处的线程等待主要是 控制线程结束的先后顺序
我们用什么方法来进行线程等待呢?---> join
join提供了两个版本
1. 调用t.join()这个方法的线程是main线程,针对t这个线程对象调用的,此时就是让 main等t
调用join后,main线程就会进入阻塞状态
代码执行到join这一行就暂停下来,不继续执行了
然后join就等啊等,等到t线程执行完毕(t的run方法跑完)才恢复就绪状态
通过线程等待就是在控制让t先结束,main后结束,一定程度上干预了这两个线程的执行顺序
但这个版本并不好,因为这个版本的join是死等,不合理
因此我们就出来了第二个版本
2.这个版本在1.的基础上,这个版本的join是可以执行等待时间,join有一个最长等待时间,如果过了这个时间就不等了
日常涉及的都是第二个版本
JAVAEE----多线程1_第23张图片

获取当前线程引用

JAVAEE----多线程1_第24张图片


线程休眠(sleep)

进程是 "PCB+双链表"这个说法---->针对 只有一个线程的进程
如果 一个进程有多个线程,此时每个线程都有一个PCB
PCB上有一个字段tgroupld,这个id其实就相当于进程的id,同一个进程中的若干线程的tgroupId是相同的

双向链表中的PCB都有各自的状态--->就绪状态,阻塞状态

操作系统调度线程的时候,就只是从就绪队列中挑选适合的PCB到CPU上运行,而阻塞队列里(某个线程调用了sleep方法)的PCB只能等着,当睡眠时间到了.系统会把阻塞队列里的PCB挪到就绪队列中,在就绪队列中被挑选到CPU上

你可能感兴趣的:(JAVAEE,java-ee)