• Java--多线程


    线程的生命周期

    新建 :从新建一个线程对象到程序start() 这个线程之间的状态,都是新建状态;
    就绪 :线程对象调用start()方法后,就处于就绪状态,等到JVM里的线程调度器的调度;
    运行 :就绪状态下的线程在获取CPU资源后就可以执行run(),此时的线程便处于运行状态,运行状态的线程可变为就绪、阻塞及死亡三种状态。
    等待/阻塞/睡眠 :在一个线程执行了sleep(睡眠)、suspend(挂起)等方法后会失去所占有的资源,从而进入阻塞状态,在睡眠结束后可重新进入就绪状态。
    终止 :run()方法完成后或发生其他终止条件时就会切换到终止状态。

    创建线程的方法

    1、继承Thread类:
      步骤:①、定义类继承Thread;
         ②、复写Thread类中的run方法;
      目的:将自定义代码存储在run方法,让线程运行
         ③、调用线程的start方法:
      该方法有两步:启动线程,调用run方法。
    2、实现Runnable接口: 接口应该由那些打算通过某一线程执行其实例的类来实现。类必须定义一个称为run 的无参方法。
     实现步骤: ①、定义类实现Runnable接口
           ②、覆盖Runnable接口中的run方法,将线程要运行的代码放在该run方法中。
           ③、通过Thread类建立线程对象。
           ④、将Runnable接口的子类对象作为实际参数传递给Thread类的构造函数。
     自定义的run方法所属的对象是Runnable接口的子类对象。所以要让线程执行指定对象的run方法就要先明确run方法所属对象
           ⑤、调用Thread类的start方法开启线程并调用Runnable接口子类的run方法。
    3、通过Callable和Future创建线程:
    实现步骤:①、创建Callable接口的实现类,并实现call()方法,改方法将作为线程执行体,且具有返回值。
          ②、创建Callable实现类的实例,使用FutrueTask类进行包装Callable对象,FutureTask对象封装了Callable对象的call()方法的返回值
          ③、使用FutureTask对象作为Thread对象启动新线程。
          ④、调用FutureTask对象的get()方法获取子线程执行结束后的返回值。

    继承Thread类和实现Runnable接口、实现Callable接口的区别。

    继承Thread:线程代码存放在Thread子类run方法中。
    优势:编写简单,可直接用this.getname()获取当前线程,不必使用Thread.currentThread()方法。
    劣势:已经继承了Thread类,无法再继承其他类。
    实现Runnable:线程代码存放在接口的子类的run方法中。
    优势:避免了单继承的局限性、多个线程可以共享一个target对象,非常适合多线程处理同一份资源的情形。
    劣势:比较复杂、访问线程必须使用Thread.currentThread()方法、无返回值。
    实现Callable:
    优势:有返回值、避免了单继承的局限性、多个线程可以共享一个target对象,非常适合多线程处理同一份资源的情形。
    劣势:比较复杂、访问线程必须使用Thread.currentThread()方法
    建议使用实现接口的方式创建多线程。

    线程状态管理

    1、线程睡眠—sleep:
     线程睡眠的原因:线程执行的太快,或需要强制执行到下一个线程。
    线程睡眠的方法(两个):sleep(long millis)在指定的毫秒数内让正在执行的线程休眠。
    sleep(long millis,int nanos)在指定的毫秒数加指定的纳秒数内让正在执行的线程休眠。
    线程睡眠的代码演示:

    public class SynTest {
        public static void main(String[] args) {
            new Thread(new CountDown(),"倒计时").start();
        }
    }
    
    class CountDown implements Runnable{
        int time = 10;
        public void run() {
            while (true) {
                if(time>=0){
                    System.out.println(Thread.currentThread().getName() + ":" + time--);
                    try {
                        Thread.sleep(1000);    //睡眠时间为1秒
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    结果:
    在这里插入图片描述
    说明:实际开发中不建议使用此方法 使用sleep只是为了放大问题发生的概率,是的问题更容易解决
    2、线程让步—yield:
    该方法和sleep方法类似,也是Thread类提供的一个静态方法,可以让正在执行的线程暂停,但是不会进入阻塞状态,而是直接进入就绪状态。相当于只是将当前线程暂停一下,然后重新进入就绪的线程池中,让线程调度器重新调度一次。也会出现某个线程调用yield方法后暂停,但之后调度器又将其调度出来重新进入到运行状态。

    public class SynTest {
        public static void main(String[] args) {
            yieldDemo ms = new yieldDemo();
            Thread t1 = new Thread(ms,"张三吃完还剩");
            Thread t2 = new Thread(ms,"李四吃完还剩");
            Thread t3 = new Thread(ms,"王五吃完还剩");
            t1.start();
            t2.start();
            t3.start();
        }
    }
    class yieldDemo implements Runnable{
        int count = 20;
        public void run() {
            while (true) {
                    if(count>0){
                        System.out.println(Thread.currentThread().getName() + count-- + "个瓜");
                        if(count % 2 == 0){
                            Thread.yield();   //线程让步
                        }
                }
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    sleep和yield的区别:
    ①、sleep方法声明抛出InterruptedException,调用该方法需要捕获该异常。yield没有声明异常,也无需捕获。
    ②、sleep方法暂停当前线程后,会进入阻塞状态,只有当睡眠时间到了,才会转入就绪状态。而yield方法调用后 ,是直接进入就绪状态。
    3、线程合并—join:
    当B线程执行到了A线程的.join()方法时,B线程就会等待,等A线程都执行完毕,B线程才会执行。
    join可以用来临时加入线程执行。
    以下为代码演示:

    public static void main(String[] args) throws InterruptedException {    
            yieldDemo ms = new yieldDemo();
            Thread t1 = new Thread(ms,"张三吃完还剩");
            Thread t2 = new Thread(ms,"李四吃完还剩");
            Thread t3 = new Thread(ms,"王五吃完还剩");
            t1.start();
            t1.join();
            
            t2.start();
            t3.start();
            System.out.println( "主线程");
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    4、停止线程:
    原stop方法因有缺陷已经停用了,那么现在改如何停止线程?现在分享一种,就是让run方法结束。
    开启多线程运行,运行的代码通常是循环结构,只要控制住循环,就可以让run方法结束,也就是线程结束。
    具体代码如下:

    public class StopThread {
    
        public static void main(String[] args) {
            int num = 0;
            StopTh st = new StopTh();
            Thread t1 = new Thread(st);
            Thread t2 = new Thread(st);
            t1.start();
            t2.start();
            //设置主线程执行50次,执行结束之后停止线程
            while (true) {
                if(num++ == 50){
                    st.flagChange();
                    break;
                }
                System.out.println(Thread.currentThread().getName() + "..." + num);
            }
        }
    }
    
    class StopTh implements Runnable{
    
        private boolean flag = true;
        public void run() {
            while(flag){
                System.out.println(Thread.currentThread().getName() + "stop run" );
            }
        }
        public void flagChange(){
            flag = false;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32

    5、设置优先级:
    每个线程执行时都有一个优先级的属性,优先级高的线程可以获得较多的执行机会,而优先级低的线程则获得较少的执行机会。与线程休眠类似,线程的优先级仍然无法保障线程的执行次序。只不过,优先级高的线程获取CPU资源的概率较大,优先级低的也并非没机会执行。
    六、线程同步与锁。
      为什么要进行线程同步?
      java允许多线程并发控制,当多个线程同时操作一个可共享资源变量时(如对其进行增删改查操作),会导致数据不准确,而且相互之间产生冲突。所以加入同步锁以避免该线程在没有完成操作前被其他线程调用,从而保证该变量的唯一性和准确性。
    同步方法1:
    同步函数:就是用synchronize关键字修饰的方法。因为每个java对象都有一个内置锁,当用synchronize关键字修饰方法时内置锁会保护整个方法,而在调用该方法之前,要先获得内置锁,否则就会处于阻塞状态。
    同步方法2:
      同步代码块:就是拥有synchronize关键字修饰的语句块,被该关键字修饰的语句块会自动被加上内置锁,从而实现同步。

    public void run() {
            while(true){
                synchronized (this) {                                 //同步代码块
                    if(tick>0){
                        try {
                            Thread.sleep(10);                                //执行中让线程睡眠10毫秒,
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        System.out.println(Thread.currentThread().getName() + " " + tick--);
                    }
                }
            }
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    结果:
    在这里插入图片描述
    追加问题:如果同步函数被静态修饰之后,使用的锁是什么?静态方法中不能定义this!
      静态内存是:内存中没有本类对象,但是一定有该类对应的字节码文件对象。 类名.class 该对象类型是Class。
      所以静态的同步方法使用的锁是该方法所在类的字节码文件对象。 类名.class。代码如下:

    public static mySyn(String name){
        synchronized (Xxx.class) {
            Xxx.name = name;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    总结

    同步的前提:
      1、必须要有两个或者两个以上的线程。
      2、必须是多个线程使用同一个锁。
      3、必须保证同步中只能有一个线程在运行。
      4、只能同步方法,不能同步变量和类。
      5、不必同步类中所有方法,类可以拥有同步和非同步的方法。
      6、如果一个线程在对象上获得一个锁,就没有任何其他线程可以进入(该对象的)类中的任何一个同步方法。
      7、线程睡眠时,它所持的任何锁都不会释放。

    好处:解决了多线程的安全问题。
      弊端:多个线程需要判断,消耗资源,降低效率。

    死锁

    进程A中包含资源A,进程B中包含资源B,A的下一步需要资源B,B的下一步需要资源A,所以它们就互相等待对方占有的资源释放,所以也就产生了一个循环等待死锁。

  • 相关阅读:
    Unity主程如何做好游戏项目管理
    百川的大模型KnowHow
    一条Sql的执行过程
    linux--用户、组、权限
    Vue——方法与事件、 样式绑定、数据绑定、面试题、笔试题
    基于AOSP源码Android-10.0.0_r41分支编译,framework开发,修改系统默认字体大小
    springcloud 整合 openfeign
    使用 labelImg 制作YOLO系列目标检测数据集(.xml文件)
    1. CMake基本
    HStreamDB v0.9 发布:分区模型扩展,支持与外部系统集成
  • 原文地址:https://blog.csdn.net/qq_42487459/article/details/125507091