一、 基础知识点回顾

  1. Java应用程序的main函数是一个线程,在JVM启动的时候调用,名字叫main
  2. 实现一个线程,必须创建Thread实例重写run方法。而且必须使用start方法启动,否则只是一个实现类
Thread t1 = new Thread(() -> {
    for (int i = 0; i < 1000; i++) {
        System.out.println("i:" + i);
    }
});
t1.start();

为什么要调用start方法而不是run方法?

  1. run方法是一个模版方法,需要子类去重写,可以提高其灵活性
  2. start方***去调用start0方法,这是个native方法,负责启动线程。还会调用run方法。
  3. 如果只调用run方法,相当于只是使用了run方法,但是没有启动线程
  1. 在JVM启动的时候,实际上有多个线程,但是至少有一个非守护线程(main)
  2. 当调用线程start方法时,实际上有两个线程,一个是新启动的线程,一个是调用start方法的线程
  3. 线程的生命周期分为:newrunnablerunningblockterminated

二、银行排队叫号系统

版本一

public class TicketWindow extends Thread {

    private final String name;

    private static final int MAX = 10;//通过staic,使每个线程访问的是同一个属性

    private static int index = 1;

    public TicketWindow(String name) {
        this.name = name;
    }

    @Override
    public void run() {

        while (index <= MAX) {

            System.out.println("柜台:" + name + "当前的号码是:" + (index++));
        }
    }
}
public class Bank {//银行
    public static void main(String[] args) {

        TicketWindow ticketWindow1 = new TicketWindow("一号柜台");
        ticketWindow1.start();

        TicketWindow ticketWindow2 = new TicketWindow("二号柜台");
        ticketWindow2.start();

        TicketWindow ticketWindow3 = new TicketWindow("三号柜台");
        ticketWindow3.start();

    }
}
  • 使用static定义index,使得实现了叫号的功能

版本二

  • 可以将业务逻辑抽取到Runnable接口中
public class TicketWindowRunnable implements Runnable {

    private int index = 1;

    private final static int MAX = 50;

    @Override
    public void run() {

        while (index <= MAX) {
            System.out.println(Thread.currentThread() + " 的号码是:" + (index++));
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}
public class BankVersion2 {

    public static void main(String[] args) {

        final TicketWindowRunnable ticketWindow = new TicketWindowRunnable();

        Thread windowThread1 = new Thread(ticketWindow, "一号窗口");
        Thread windowThread2 = new Thread(ticketWindow, "二号窗口");
        Thread windowThread3 = new Thread(ticketWindow, "三号窗口");
        windowThread1.start();
        windowThread2.start();
        windowThread3.start();
    }
}
  • Runnable的作用是将业务的逻辑单元与线程分离出来

注: 上述两个版本都没有解决线程安全的问题

三、策略者模式在Thread和Runnable中的应用分析

定义一个税率计算器,处理的策略由用户自定义

public class TaxCalculator {

    private final double salary;//工资
    private final double bonus;//奖金
    private CalculatorStrategy strategy;//计算策略

    public TaxCalculator(double salary, double bonus) {//构造函数
        this.salary = salary;
        this.bonus = bonus;
    }

    protected double calcTax(){//计算税率
        return strategy.calculate(salary,bonus);
    }

    public double calclate(){//

        return this.calcTax();
    }

    public double getSalary() {
        return salary;
    }

    public double getBonus() {
        return bonus;
    }

    public void setStrategy(CalculatorStrategy strategy) {
        this.strategy = strategy;
    }
}

用户定义策略,实现计算税率

public class TaxCalcMain {

    public static void main(String[] args) {
        //1.第一种实现
// TaxCalculator calculator = new TaxCalculator(10000,2000){
// @Override
// public double calcTax() {//由使用者定义处理逻辑,类似于定于run方法的逻辑
// return getSalary()*0.1+getBonus()*0.15;
// }
// };
// double tax = calculator.calclate();
// System.out.println(tax);

        //2.使用接口抽离出处理逻辑
        TaxCalculator calculator = new TaxCalculator(10000,2000);
        CalculatorStrategy strategy = new CalculatorStrategyImpl();//相当于一个Runnable接口
        calculator.setStrategy(strategy);//将处理逻辑设置到计算器中
        //调用
        System.out.println(calculator.calclate());

        //3.使用函数式编程实现
        TaxCalculator calculator2 = new TaxCalculator(10000,2000);
        calculator.setStrategy(((salary, bonus) -> salary*0.1+bonus*0.15));
        //调用
        System.out.println(calculator2.calclate());
    }
}
/** * 定义计算的策略 */

public interface CalculatorStrategy {
    public double calculate(double salary,double bonus);
}
/** * 策略的实现类 */
public class CalculatorStrategyImpl implements CalculatorStrategy {


    //汇率设置为常量
    private final static double SALARY_RATE  = 0.1;
    private final static double BONUS_RATE  = 0.15;

    @Override
    public double calculate(double salary, double bonus) {
        return salary*SALARY_RATE+bonus*BONUS_RATE;
    }
}

<mark>总结</mark>

  • 根据上述案例,可以得出Runnable接口最重要的方法-----run方法,使用了策略者模式将执行的逻辑(run方法)和程序的执行单元(start0方法)分离出来,使用户可以定义自己的程序处理逻辑,更符合面向对象的思想

四、Thread的构造函数

主要是对于其中几个参数的源码分析

  • 线程组ThreadGroup group
  • Runnable实例Runnable target
  • 线程名称String name
  • 栈空间大小long stackSize

默认构造Thread()源码分析

  • 默认构造会去调用init方法,完成对线程的构造
  • 默认命名规则"Thread-"加上nextThreadNum()方法获取一个数字
public Thread() {
    init(null, null, "Thread-" + nextThreadNum(), 0);
}
private static int threadInitNumber;//初始化0
private static synchronized int nextThreadNum() {
    return threadInitNumber++;//从0开始依次增大
}
  • ThreadGroup是线程组,stackSize是栈的大小
/** * Initializes a Thread with the current AccessControlContext. * @see #init(ThreadGroup,Runnable,String,long,AccessControlContext,boolean) */
private void init(ThreadGroup g, Runnable target, String name,
                  long stackSize) {
    init(g, target, name, stackSize, null, true);
}
  • 继续向下,可以发现此时会赋值一个空的Runnable对象,此时需要查看Start0()方法
private void init(ThreadGroup g, Runnable target, String name,
                  long stackSize, AccessControlContext acc,
                  boolean inheritThreadLocals) {
    if (name == null) {
        throw new NullPointerException("name cannot be null");
    }
    this.name = name;
    this.target = target;//将Runnable赋值在静态变量里,此时为空
	//....省略部分接下来分析
}
  • 可以发现如果当Start0方法调用run方法时,如果没有Runnable对象,则不会执行
@Override
public void run() {
    if (target != null) {
        target.run();
    }
}

接着我们继续普及线程组的概念

  • 线程组就是将许多个线程,汇聚到组中,通过线程组的一些API对这些线程进行一些操作
  • 当传递的线程组为空时,可以发现,会从currentThread()方法中获取线程组,而currentThread就是创建该线程的线程,如主线程

private void init(ThreadGroup g, Runnable target, String name,
                  long stackSize, AccessControlContext acc,
                  boolean inheritThreadLocals) {

    Thread parent = currentThread();
    SecurityManager security = System.getSecurityManager();
    if (g == null) {
        if (g == null) {
            g = parent.getThreadGroup();//从currentThread获取
        }
    }

    this.group = g;
}

观察线程组的API

  • 获取线程组的名称public final String getName()
  • 获取当前存活的线程数public int activeCount()
  • 获取当前线程组的线程的集合public int enumerate(Thread[] list)
public class CreatThread2 {
    public static void main(String[] args) {
        Thread t = new Thread();
        ThreadGroup threadGroupT = t.getThreadGroup();//子线程线程组
        ThreadGroup threadGroupMain = Thread.currentThread().getThreadGroup();//主线程线程组
        t.start();
        System.out.println("我是子线程线程组:" + threadGroupT.getName());
        System.out.println("我是主线程线程组:" + threadGroupMain.getName());


        System.out.println(threadGroupT.activeCount());//获取存活的线程数

        Thread[] threads = new Thread[threadGroupT.activeCount()];
        threadGroupT.enumerate(threads);//获取当前线程组的全部集合
        for (Thread thread : threads) {
            System.out.println(thread);
        }
    }
}

结果

我是子线程线程组:main
我是主线程线程组:main
3
Thread[main,5,main]
Thread[Monitor Ctrl-Break,5,main]
Thread[Thread-0,5,main]

  • Monitor 线程是一个守护线程,负责监听一些操作,也在main线程组中

构造函数Thread(Runnable target)

  • 将这个Runnable实例传递到该线程的target属性中,在start0方法调用时,执行其逻辑单元

构造函数Thread(String name)

  • 会覆盖默认的线程名,同时不会使threadInitNumber 增加

构造函数Thread(Runnable target , String name)

  • 同上两个

构造函数Thread(ThreadGroup group, Runnable target, String name, long stackSize)

API对于stackSize解释

On some platforms, specifying a higher value for the stackSize parameter may allow a thread to achieve greater recursion depth before throwing a StackOverflowError.

大致意思是,可以给线程一个较高的值来提高栈的深度可以放更多的栈帧,防止栈溢出
但是注意:某些平台可能失效,但是官方文档没有列举

总结

  1. 创建线程对象Thread,默认有一个线程名,以Thread-开头,从0开始计数,如“Thread-0、Thread-1、Thread-2 …”
  2. 如果没有传递Runnable或者没有覆写Thread的run方法,该Thread不会调用任何方法
  3. 如果传递Runnable接口的实例或者覆写run方法,则会执行该方法的逻辑单元(逻辑代码)
  4. 如果构造线程对象时,未传入ThreadGroup,Thread会默认获取父线程的ThreadGroup作为该线程的ThreadGroup,此时子线程和父线程会在同一个ThreadGroup中
  5. stackSize可以提高线程栈的深度,放更多栈帧,但是会减少能创建的线程数目
  6. stackSize默认是0,如果是0,代表着被忽略,该参数会被JNI函数调用,但是注意某些平台可能会失效,可以通过“-Xss10m”设置