• java多线程(二)实现多线程的几种方式


    1.概述

    java多线程创建方式包括。
    常说的方式有以下四种:

    • 继承Thread:重写该类run方法
    • 实现Runable接口:重写接口的run方法
    • 实现Callable接口(可以获取线程执行之后的返回值)
    • 通过线程池的方式

    但实际后两种,更准确的理解是创建了一个可执行的任务,要采用多线程的方式执行,
    还需要通过创建Thread对象来执行,比如 new Thread(new Runnable(){}).start();这样的方式来执行。
    在实际开发中,我们通常采用实现runnable接口以及线程池的方式来完成Thread的创建,更好管理线程资源。

    继承Thread类本质上还是内部还是实现的Runable接口,这里主要介绍下实现Runable接口:重写接口的run方法。

    2.实现Runable接口方法实现多线程案例

    • 常规写法
    package com.demo;
    
    //通过实现Runnable接口创建多线程
    public class MyThread1 implements Runnable{
        
        private int ticket = 10;
        
        public void run(){
            for(int i=0;i<20;i++){
                if(this.ticket>0){
                    System.out.println(Thread.currentThread().getName()+" 卖票:ticket"+this.ticket--);
                }
            }
        }
        
        public static void main(String[] args) {  
            MyThread1 mt=new MyThread1();
    
            // 启动3个线程t1,t2,t3(它们共用一个Runnable对象),这3个线程一共卖10张票!
            Thread t1=new Thread(mt);
            Thread t2=new Thread(mt);
            Thread t3=new Thread(mt);
            t1.start();
            t2.start();
            t3.start();
        }  
    
    }
    
    • 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
    • Lambda表达式写法
        public static void main(String[] args){
         final int[] ticket = {10};
         for(int i=0;i<3;i++){
           new Thread( () -> {
     //        System.out.println(Thread.currentThread().getName());
             for (int j=0;j<10;j++){
               if (ticket[0] > 0){
                 System.out.println(Thread.currentThread().getName()+" 卖票:ticket"+ ticket[0]--);
               }
             }
    
           }).start();
       }
    
     }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    运行结果:三个线程共享局部变量,一共卖出10张票

    Thread-0 卖票:ticket10
    Thread-0 卖票:ticket8
    Thread-0 卖票:ticket7
    Thread-0 卖票:ticket6
    Thread-1 卖票:ticket9
    Thread-1 卖票:ticket4
    Thread-0 卖票:ticket5
    Thread-0 卖票:ticket1
    Thread-1 卖票:ticket2
    Thread-2 卖票:ticket3
    
    Process finished with exit code 0
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    额外知识点

    Lambda 表达式或者匿名内部类不能访问非 final 的局部变量,这是为什么呢?

    首先思考外部的局部变量 finalI 和匿名内部类里面的 finalI 是否是同一个变量?
    我们知道,每个方法在执行的同时都会创建一个栈帧用于存储局部变量表、操作数栈、动态链接,方法出口等信息,每个方法从调用直至执行完成的过程,就对应着一个栈帧在虚拟机栈中入栈到出栈的过程(《深入理解Java虚拟机》第2.2.2节 Java虚拟机栈)。

    就是说在执行方法的时候,局部变量会保存在栈中,方法结束局部变量也会出栈,随后会被垃圾回收掉,而此时,内部类对象可能还存在,如果内部类对象这时直接去访问局部变量的话就会出问题,因为外部局部变量已经被回收了,解决办法就是把匿名内部类要访问的局部变量复制一份作为内部类对象的成员变量,查阅资料或者通过反编译工具对代码进行反编译会发现,底层确实定义了一个新的变量,通过内部类构造函数将外部变量复制给内部类变量。

    为何还需要用final修饰?
    其实复制变量的方式会造成一个数据不一致的问题,在执行方法的时候局部变量的值改变了却无法通知匿名内部类的变量,随着程序的运行,就会导致程序运行的结果与预期不同,于是使用final修饰这个变量,使它成为一个常量,这样就保证了数据的一致性。
    这里用了final int[] ticket = {10},主要是因为final修饰的局部基本类型变量无法改变变量值。当 final 修饰引用数据类型变量时,它仅仅保存的是一个引用,final 只保证这个引用类型变量所引用的地址不会发生改变,即一直引用这个对象,但这个对象的内容是可以改变的。

  • 相关阅读:
    揭秘!2024年热门的图标设计工具
    tasklet的实现(原理篇)
    刨析String对象常用的创建方式-日记篇
    报错01__GAN网络
    java毕业设计大学生学业互助与交流平台Mybatis+系统+数据库+调试部署
    Redis(事务和持久化)(很重要!)
    js中0.1+0.2!=0.3
    光猫桥接模式详细步骤
    C语言:空指针野指针
    go-zero jwt 鉴权快速实战
  • 原文地址:https://blog.csdn.net/maligebilaowang/article/details/127904911