【javaEE】——线程的多种构造方法和Thread类的介绍02

目录

一、线程的相关内容

1.1线程的概念

1.2进程和线程的区别

1.3Java 的线程 和 操作系统线程的关系

2.Java多线程编程(线程的构造方法)

2.1.创建子类,继承Thread类,重写run

2.2.实现Runnable接口,重run

2.3.继承Thread,重写run,使用匿名内部类(  的内部类)

2.4.使用lambda表达式

2.5 多线程编程

2.6 Thread常见构造方法

三、Thread中的重要方法

3.1线程中断

3.2 等待一个线程 join

3.3 休眠线程sleep

3.4线程的状态


一、线程的相关内容

1.1线程的概念

一个线程就是一个 "执行流". 每个线程之间都可以按照顺序执行自己的代码. 多个线程之间 "同时" 执行着多份代码.  线程是系统调度的最小单位(PCB)

进程是操作系统对一个正在运行的程序(可看做程序的一次运行过程);

  • 轻量级进程:创建/销毁/调度线程比进程更高效。
  • 进程需要资源的申请和释放,更为繁琐,成本较大

为什么引入线程???------> 

  • 频繁创建和销毁进程成本较大
  •  "并发编程" 成为 "刚需":
    • 单核 CPU 的发展遇到了瓶颈. 要想提高算力, 就需要多核 CPU. 而并发编程能更充分利用多核 CPU资源.
    • 有些任务场景需要 "等待 IO", 为了让等待 IO 的时间能够去做一些其他的工作, 也需要用到并发编程.

1.2进程和线程的区别

  • 1.进程包含线程: 每个进程至少有一个线程(主线程)存在,同时也可包含多个线程,线程和进程共用一样的资源。
  • 2.进程和线程都是为了处理并发编程的场景,但是进程效率更低,线程更为高效(少了申请释放资源的过程)   
  • 3.OS创建进程,需要给其分配资源,进程是系统分配资源的最小单位;OS创建线程,要在CPU上执行调度,线程是系统调度的最小单位。
  • 4.进程具有独立性,(进程之间不共享内存空间),一个进程出问题,不会影响其他;同一个进程的线程之间共享同一个内存空间(虚拟地址),一个线程出现问题,会影响其他线程,进而导致整个进程崩溃。                                      

1.3Java 的线程 和 操作系统线程的关系

  • 操作系统中的线程描述:使用Thread类的对象来表示
  • java代码中描述线程:Thread类
  • PCB是在操作系统OS内核中描述线程

2.Java多线程编程(线程的构造方法)

Java标准库中,使用Thread类(API)操作线程。

操作系统提供一组关于线程的API(C语言风格),Java对API进行封装,就成了Thread类

创建线程的方法:

1.创建子类,继承Thread类,重写run

2.实现Runnable接口,重run  ---->此方法常用,能让线程之间更好的解耦(符合代码要求:高内聚低耦合)

3.继承Thread,重写run,使用匿名内部类(一个没有类名的内部类)

4.实现Runnnable匿名内部类

5.lambda表达式

  • 2.1.创建子类,继承Thread类,重写run

class myThread extends Thread {
    @Override
    public void run() {
        System.out.println("hello");
    }
}

(run方法内部为线程要执行的代码,新创建的线程要执行的逻辑,非主线程逻辑),run方法创建后并没有创建线程,定义子类实例后,调用start才创建了线程,再执行run方法的逻辑

Thread t = new myThread();  //此处创建的线程是在同一个进程里创建的
t.start();

 Java进程中,至少会有一个调用main方法的线程(主线程),自己创建的 t 线程和自动创建的main线程属于并发执行的关系


class myThread2 extends Thread {
    @Override
    public void run() {
        System.out.println("hello2");
        try {
            Thread.sleep(1000);  //1s内休眠1000ms
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
    }
}

public class Thread2 {
    public static void main(String[] args) throws InterruptedException {
        Thread t2 = new myThread2();
        t2.start();
        while (true) {
            System.out.println("hello main");
            Thread.sleep(1000);
        }

    }
}

 输出结果:随机输出两个线程的执行逻辑(因为系统对线程的调度(抢占式执行)随时都在发生变化,并发和并行都可能会执行)


  • 2.2.实现Runnable接口,重run

class myRunnable implements Runnable{
    @Override
    public void run() {
        System.out.println("hello");
    }
}

public class Thread3 {
    public static void main(String[] args) {
        Thread t = new Thread(new myRunnable());
        t.start();
    }
}
  • 2.3.继承Thread,重写run,使用匿名内部类(  的内部类)

public class thread4 {
    public static void main(String[] args) {
        Thread t = new Thread() {
            @Override
            public void run() {
                System.out.println("hello thread");
            }
        };
        t.start();
    }
}
  • 2.4.使用lambda表达式

public class thread4 {
    public static void main(String[] args) {
        Thread t = new Thread(() ->{
            System.out.println("hello thread");
        });
        t.start();
    }
}

2.5 多线程编程

多线程提高效率,如有两个变量进行10亿次自增,分别使用一个线程和两个线程来实现,进行比较

1.串行消耗的时间(单线程)


public class thread4 {
    private static final long count = 10_0000_0000;
    public static void func() {
        //记录时间
        long begin = System.currentTimeMillis();
        long a = 0;
        for (int i = 0; i < count; i++) {
            a++;
        }
        long b= 0;
        for (int i = 0; i < count; i++) {
            b++;
        }
        long end = System.currentTimeMillis();
        long time = end - begin;
        System.out.println("消耗时间"+time+"ms");
    }
    
    public static void main(String[] args) {
        func();
    }
}

输出结果:28ms

2.多线程执行(要创建多个线程,同时引入join,main主线程等待创建的线程执行结束在执行计时)

//==============多线程执行=========================
public class thread4 {
    private static final long count = 10_0000_0000;
    public static void concurrent() throws InterruptedException {
        long begin = System.currentTimeMillis();
        Thread t1 = new Thread(() ->{
            long a = 0;
            for (int i = 0; i < count; i++) {
                a++;
            }
        });
        t1.start();
        Thread t2 = new Thread(() ->{
            long b = 0;
            for (int i = 0; i < count; i++) {
                b++;
            }
        });
        t2.start();
    //t1、t2  和main 是并发执行的关系,计算时间应该是main线程等待t1、t2执行结束之后,再计时

        t1.join();  //join()就是等待线程结束, t1.join();就是main线程等待t1结束
        t2.join();   //t2.join();就是main线程等待t2结束
        long end = System.currentTimeMillis();
        long time = end - begin;
        System.out.println("消耗时间"+time+"ms");
    }

    public static void main(String[] args) throws InterruptedException {
        concurrent();
    }
}

输出结果:61ms

2.6 Thread常见构造方法

重点掌握给线程命名:使用jconsole来观察线程的名字(使用本地连接,选择你当前的文件名(我的是Thread6)进行连接即可)

      此处的就是命名的线程名

public class Thead6 {
    public static void main(String[] args) {
        Thread t1 = new Thread(() ->{
            while (true) {
                System.out.println("hello thread1");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }

        }, "thread1");  //把线程命名为thread1
        t1.start();

        Thread t2 = new Thread(() ->{
            while (true) {
                System.out.println("hello thread2");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }

        },"thread2");  //把线程命名为thread2
        t2.start();
    }
}

注意!!!

  • ID 是线程的唯一标识,不同线程不会重复------>getId()
  • 名称是各种调试工具用到------>getName()
  • 状态表示线程当前所处的一个情况,下面我们会进一步说明------>getState()
  • 优先级高的线程理论上来说更容易被调度到------>getPriority()
  • 关于后台线程,需要记住一点:JVM会在一个进程的所有非后台线程结束后,才会结束运行。------>isDaemon()
  • 是否存活,即简单的理解,为 run 方法是否运行结束了------>isAlive()
  • 是否中断------>isInterrupted()

创建一个线程,默认不是后台线程:若main方法结束,线程还未结束,JVM进程不会结束;

若当前线程是后台线程:main方法结束,线程还未结束,JVM进程直接结束

创建对象(Thread t )之后,在调用start之前,系统中没有对应的线程;而在run方法之后,系统中的线程就销毁了,但是 t 对象可能还存在。(通过isAlive可判断当前系统中线程的运行情况):

  • 调用start之后,run执行之前,isAlive返回true
  • 调用start之前,run执行之后,isAlive返回false

三、Thread中的重要方法

run方法只是一个普通的调用方法,描述任务的执行逻辑。start是一个特殊的方法,内部会在系统中创建线程。(main线程中调用run方法,并没有创建新的线程,仍然是在main线程中执行,故代码遵循顺序执行,先运行完第一个循环,再运行第二个循环,此处第一个循环为死循环,故只输出hello thread1)

public class thread7 {
    public static void main(String[] args) throws InterruptedException {
        Thread t = new Thread(() ->{
           while (true) {
               System.out.println("hello thread1");
               try {
                   Thread.sleep(1000);
               } catch (InterruptedException e) {
                   throw new RuntimeException(e);
               }
           }
        });
//        t.start();  //hello thread1和hello main交替输出
        t.run();    //只输出hello thread1
        while (true) {
            System.out.println("hello main");
            Thread.sleep(1000);
        }
    }
}

3.1线程中断

线程中断的关键:run方法执行完毕或者main执行完毕

(1) 设置标志位(isQuit)中止线程:

public class Thread {

 private static boolean isQuit = false;
    public static void main(String[] args) {
        Thread t = new Thread(() ->{
           while (!isQuit) {
               System.out.println("hello thread");
               try {
                   Thread.sleep(1000);
               } catch (InterruptedException e) {
                   throw new RuntimeException(e);
               }
           }
        });
        t.start();
        Thread.sleep(1000);
        isQuit = true;
        System.out.println("终止 t 进程");
    }
}

(2)使用Thread中·内置的一个标志位来判断:

  • Thread.interrupted()静态方法
  • Thread.currentThread().isInterrupted()实例方法,currentThread可以获取到当前线程的实例
public class thread8 {
    public static void main(String[] args) {
        Thread t = new Thread(() ->{
            while (!Thread.currentThread().isInterrupted()) {
                System.out.println("hello thread");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        });
        t.start();  //上述为线程逻辑
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        t.interrupt();  //中断逻辑
    }
}

 t.interrupt();  //中断逻辑,调用此方法产生2种情况:

  • 线程处于就绪状态,设置标志位为true
  • 线程处于阻塞状态(sleep休眠了),就会触发一个interruptException (常用)

3.2 等待一个线程 join

多个线程之间,调度顺序是不确定的,可通过线程等待控制线程执行顺序。

哪个线程调用 join ,哪个线程就会阻塞等待 (join是死等),可设置等待时间join(10000):等待10s

 public static void main(String[] args) throws InterruptedException {
        Thread t = new Thread(() -> {
            for (int i = 0; i < 5; i++) {
                System.out.println("hello thread");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                    break;
                }
            }
        });
        t.start();  //首先是main线程执行
        t.join();   // main线程等待t线程执行完再执行
 }

代码执行到 join 之后,main线程进入阻塞状态(暂时不运行),而t线程开始执行run方法·直至完毕。


3.3 休眠线程sleep

一个进程对应一组PCB,一个线程对应一个PCB

sleep()方法本质 : 把线程PCB从就绪队列移动到阻塞队列

public class ThreadDemo {
public static void main(String[] args) throws InterruptedException {
System.out.println(System.currentTimeMillis());
Thread.sleep(3 * 1000);
System.out.println(System.currentTimeMillis());
}
}

3.4线程的状态

线程的状态决定系统按照什么样的方式来调度(状态其实是绑定在线程上的)线程对应一个PCB,而进程对应的是一组PCB。

线程的6种状态(java中Thread类的状态):

  • 1.NEW: 安排了工作,但还没开始行动。创建了Thread t对象,但还没具体执行(未调用start),(内核PCB还没创建);
  • 2.RUNNABLE: 就绪状态。可工作的, 又可以分成正在工作中和即将开始工作(等待随时被调度到CPU)
  • 3.TERMINATED: 当前pcb已经结束(run方法已经执行完毕,并销毁),Thread对象还在。

3种阻塞等待:

  • 1.TIMED_WAITING:  当前pcb在阻塞队列中等待,带有时间的等待(Thread.sleep(1000))
  • 2.WAITING:   线程中调用 wait 方法,也会阻塞等待,(死等)除非被唤醒
  • 3.BLOCKED:  线程中尝试进行加锁,若发现锁被其他进程占用,出现的阻塞等待(使用synchronized进行加锁时可能出现的等待)

!!!开发过程中,程序出现"卡死"的现象,可能是由于一些关键线程出现阻塞:可查看关键线程的一个当前状态:Thread.currentThread()获取到当前线程,并获取状态:getState()

 ​​​​​​

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值