java 多线程看这一片就够了

122 阅读8分钟

@[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;
    }