Java 多线程看这一篇就够了

@[toc]

java 多线程

线程创建两种方式

  • 集成Thread 类
  • 实现Runable接口

两种方式都需要重写run方法

启动线程调用start()方法

创建线程

这里继承Thread 创建线程实例


public class ThreadStart {


    /**
     * java 应用程序的main函数是一个线程,是被jvm启动的时候调用,线程名字叫main
     *
     * 实现一个线程,必须创建Thread实例,重写 run方法,并且调用start方法
     * 在jvm启动后,实际上有多个线程,但是至少有一个非守护线程 main 入口启动线程
     * 当调用一个线程启动start方法的时候, 此时至少用两个线程,一个是调用你的线程 还是有一个是执行run的线程
     *
     * 线程的生命周期 分为 new Thread,runnbale,running,bloic,terminated
     *
     * 注意:
     * start 之后不可能立即进入 running 先处于 runnbale
     * bloic 之后 必须先回到 runnbale  再回到 running,过程中可能挂掉 处于 terminated
     *
     */
    public static void main(String[] args) {

        //main方法线程名称
        System.out.println(Thread.currentThread().getName());

        Thread t1 = new Thread(){

            @Override
            public void run() {
                //t1线程名称
                System.out.println(Thread.currentThread().getName());
            }
        };

        //启动线程 不是run方法 在源码中 将逻辑方法都抽象到run方法中,在start方法中 启动run方法,这样子可以通过重写来定义自己的业务逻辑
        t1.start();
    }
}

模拟排队机叫号排队

创建线程

public class ThreadBank1 {


    public static void main(String[] args) {
        BnakThread t1 = new BnakThread();
        BnakThread t2 = new BnakThread();
        BnakThread t3 = new BnakThread();

        //启动
        t1.start();
        t2.start();
        t3.start();


    }
}


class BnakThread extends Thread {


    int num = 1;
    int max = 50;

    @Override
    public void run() {
        while (num<=max){
            System.out.println(Thread.currentThread() + " 的号码是 " + num );
            num++;
        }
    }
}

结果如下

仅一部分数据


仅一部分

这样虽然启动线程打印出结果,但是存在一个问题,这样三个排队机 互相各玩各的,三个排队机都将50个号码打印一个遍。

数据没有做到共享

解决办法1:

  • 可以设置静态变量
   static int num = 1;
   static int max = 50;
  • 可以使用runnable接口将逻辑从线程中分离出来

也就是创建现成的第二种方式实现Runable接口

定义一个类实现runnable接口

package com.company;

/**
 * @description: 模拟叫号排队
 * @author: Administrator
 * @create: 2019-12-08 13:32
 **/
public class ThreadRunnableBank2 {


    public static void main(String[] args) {

        BnakRunable runable = new BnakRunable();

        Thread t1 = new Thread(runable,"1号");
        Thread t2 = new Thread(runable,"2号");
        Thread t3 = new Thread(runable,"3号");

        //start

        t1.start();
        t2.start();
        t3.start();


    }
}


class BnakRunable implements Runnable {


   static int num = 1;
    static int max = 50;

    @Override
    public void run() {
        while (num<=max){
            System.out.println(Thread.currentThread() + " 的号码是 " +  num++ );
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

        }
    }
}

可以看到结果如下

Thread[1号,5,main] 的号码是 1
Thread[3号,5,main] 的号码是 2
Thread[2号,5,main] 的号码是 3
Thread[1号,5,main] 的号码是 4
Thread[3号,5,main] 的号码是 4
Thread[2号,5,main] 的号码是 5
Thread[1号,5,main] 的号码是 6
Thread[3号,5,main] 的号码是 7
Thread[2号,5,main] 的号码是 8
Thread[3号,5,main] 的号码是 9
Thread[1号,5,main] 的号码是 9
Thread[2号,5,main] 的号码是 10
Thread[1号,5,main] 的号码是 11
Thread[3号,5,main] 的号码是 12
Thread[2号,5,main] 的号码是 13
Thread[3号,5,main] 的号码是 14
Thread[1号,5,main] 的号码是 15
Thread[2号,5,main] 的号码是 16
Thread[3号,5,main] 的号码是 17
Thread[1号,5,main] 的号码是 17
Thread[2号,5,main] 的号码是 18
Thread[1号,5,main] 的号码是 19
Thread[3号,5,main] 的号码是 20
Thread[2号,5,main] 的号码是 21
Thread[3号,5,main] 的号码是 22
Thread[1号,5,main] 的号码是 23
Thread[2号,5,main] 的号码是 24
Thread[3号,5,main] 的号码是 25
Thread[1号,5,main] 的号码是 25
Thread[2号,5,main] 的号码是 26
Thread[3号,5,main] 的号码是 27
Thread[1号,5,main] 的号码是 27
Thread[2号,5,main] 的号码是 28
Thread[3号,5,main] 的号码是 29
Thread[1号,5,main] 的号码是 29
Thread[2号,5,main] 的号码是 30
Thread[1号,5,main] 的号码是 31
Thread[3号,5,main] 的号码是 31
Thread[2号,5,main] 的号码是 32
Thread[1号,5,main] 的号码是 33
Thread[3号,5,main] 的号码是 33
Thread[2号,5,main] 的号码是 34
Thread[3号,5,main] 的号码是 35
Thread[1号,5,main] 的号码是 36
Thread[2号,5,main] 的号码是 37
Thread[3号,5,main] 的号码是 38
Thread[1号,5,main] 的号码是 38
Thread[2号,5,main] 的号码是 39
Thread[3号,5,main] 的号码是 41
Thread[1号,5,main] 的号码是 40
Thread[2号,5,main] 的号码是 42
Thread[3号,5,main] 的号码是 44
Thread[1号,5,main] 的号码是 43
Thread[2号,5,main] 的号码是 45
Thread[3号,5,main] 的号码是 46
Thread[1号,5,main] 的号码是 46
Thread[2号,5,main] 的号码是 47
Thread[1号,5,main] 的号码是 48
Thread[3号,5,main] 的号码是 48
Thread[2号,5,main] 的号码是 49
Thread[1号,5,main] 的号码是 50
Thread[3号,5,main] 的号码是 50

Runable的作用是将可执行的代码逻辑从线程中分离出来,这比较符合我们的面向对象思想。

线程生命周期

线程生命周期图例如下图


图片来自网络

大致分为以下及格过程

  • 1 new Thread() 创建线程

  • 2 然后调用start()方法进入 runnable可执行状态,runbale也可能出现异常线程进入terminated 状态

  • 3 可执行状态阶段争抢cpu ,抢到了cpu执行调度权进入running状态,如果没有抢到执行cpu 线程继续处于runbale状态

  • 4 running过程中 可能存在 wait , sleep 等 让线程处于bolck状态

  • bolck 状态结束后 ,线程进入可执行 runnable 状态,然后 runnable 继续执行第3阶段步骤;
    也可能出现异常,进入 terminated 死亡结束状态;

线程中一些常用方法

创建对象Thread的时候,默认会有一个线程名,以Thread-开头,从0开始计数

构造方法

new Thread();

Thread-0
Thread-1
Thread-2


  public static void main(String[] args) {

        Thread t0 = new Thread();
        System.out.println(t0.getName());


        Thread t1 = new Thread(){

            @Override
            public void run() {
                //t1线程名称
                Thread.currentThread().setName("线程2");
                System.out.println(Thread.currentThread().getName());
            }
        };

        t1.start();
        t0.start();
    }

可以看到默认是以0开头的


在这里插入图片描述

通过源码可以看到具体缘由

"Thread-" + nextThreadNum() Thread开头 然后+ nextThreadNum()方法

  public Thread() {
        init(null, null, "Thread-" + nextThreadNum(), 0);
    }

nextThreadNum() 方法 静态方法提供递增操作

 private static synchronized int nextThreadNum() {
        return threadInitNumber++;
    }

也可以看到当构造方法里什么都不传值的话 调用默认的init初始化方法

Runable 参数传值null

init(null, null, "Thread-" + nextThreadNum(), 0);

Runable 赋值成员变量


在这里插入图片描述

然后start开启线程执行run方法
调用本地方法


在这里插入图片描述

本地run执行方法内容,可以看到 上面穿的值默认为null 所以没有任何线程内容输出。


在这里插入图片描述

所以定义线程时候,要重写run方法实现自己业务逻辑

ThreadGroup 值,默认传值为null

如果没有传入 ThreadGroup 值,默认传值为null

在这里插入图片描述

会获取父线程的ThreadGroup 作为该线程的ThreadGroup ,此时子线程和父线程将会在同一个ThreadGroup 里。
可以通过ThreadGroup 查看到 线程的运行数量等

//t1线程ThreadGroup名
System.out.println(t1.getThreadGroup().getName()); //main
//main 的ThreadGroup名
System.out.println(Thread.currentThread().getThreadGroup().getName());//main

获取运行线程数量

   //获取到 ThreadGroup 中有多少个线程在运行
        System.out.println(Thread.currentThread().getThreadGroup().activeCount()); //理想情况下是2个

参数 stackSize

stackSize 默认是0 表示跟平台有关,个人电脑配置内存大小和jvm内存大小有关

stackSize 参数表示意思是当前线程虚拟机栈帧大小

方法在调用的时候当前栈的大小决定方法可以进行压栈进栈多少次,如果超过这个次数会抛出 StackOverflowError 错误信息

通常默认情况下main方法栈帧,也就是jvm启动时候创建的栈帧大小即虚拟机栈


    //计数器
    private static int count;
    /**
     * 线程名
     * @param args
     */
    public static void main(String[] args) {

        //main方法是jvm启动时候创建的 虚拟机栈帧大小是 49799 
        //main方法 中调用add方法压栈进栈 次数为 49799 次

       try {
            add(1);
        }catch (Error e){
            e.getMessage();
            System.out.println(count);
        }
          
      
    }


    /**
     * 递归调用
     * @param i
     */
    static void add(int i){
        ++count;
        add(i+1);
    }
    

结果

java.lang.StackOverflowError
49799

线程设置栈帧大小

根据构造函数传参

>

线程设置栈帧大小

   public static void main(String[] args) {

        Thread thread = new Thread(null, new Runnable() {
            @Override
            public void run() {
                try {
                    add(1);
                }catch (Error e){
                    System.out.println(e.getClass().getName());
                    System.out.println(count);
                }
            }
        },"test",1<<24); // 栈帧 1<<24 次

        thread.start();
    }


    /**
     * 递归调用
     * @param i
     */
    static void add(int i){
        ++count;
        add(i+1);
    }
}


结果

java.lang.StackOverflowError
418012

设置守护线程


    /**
     * 守护线程 随着父线程结束而结束
     * @param args
     */
    public static void main(String[] args) {


        Thread thread = new Thread(null, new Runnable() {
            @Override
            public void run() {

                System.out.println("test 线程 ");
            }
        },"test");


        /**
         * 设置为true时候 控制台只打印main  test 线程随着main线程结束而退出
         * 设置为true时候   "test 线程 " 会打印
         */
        thread.setDaemon(false); //默认是false
        thread.start();


        System.out.println("mian ");
    }

结果


在这里插入图片描述

设置守护线程时候 必须在启动线程之前设置,不然会抛出异常 java.lang.IllegalThreadStateException

线程优先级

void setPriority(int newPriority)

newPriority 默认是5 范围 1- 10最高级10和最低级1 ; 一般情况下 数字越大优先级越高

线程id

   System.out.println(Thread.currentThread().getId()); //1   main线程由jvm 优先启动
        System.out.println(thread2.getId()); //11
        System.out.println(thread.getId()); //12

源码

 public long getId() {
        return tid;
    }
在这里插入图片描述

获取下一个线程id 值递增

  private static synchronized long nextThreadID() {
        return ++threadSeqNumber;
    }
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

  • Thread 和 Runnable 区别 多线程是并行计算实现的方式, 但是在单cpu中实际上没有真正的并行,只不...
    Tim在路上阅读 3,698评论 0 1
  • 1.1 并发与并行 并行:指两个或多个时间在同一时刻发生(同时发生)并发:值两个或多个时间在同一时间段内发生在操作...
    阿麽阅读 3,480评论 0 0
  • 导读目录 线程组(ThreadGroup) 线程池(Thread Pool) Fork/Join框架和Execut...
    ql2012jz阅读 5,360评论 0 0
  • 1 线程的生命周期 每个线程都有自己的局部变量表、程序计数器以及生命周期。 上图就时一个线程的生命周期图,答题可以...
    WangGavin阅读 4,898评论 0 2
  • 尊敬的校领导您好,我是一八届栋一四班的学生,非常感谢你们抽出时间来看,我们社团申请书,我们成立社团的初衷以及缘由有...
    林展锋阅读 1,189评论 0 0

友情链接更多精彩内容