目录
一、 基础知识点回顾
- Java应用程序的main函数是一个线程,在JVM启动的时候调用,名字叫main
- 实现一个线程,必须
创建Thread实例
,重写run方法
。而且必须使用start方法启动
,否则只是一个实现类
Thread t1 = new Thread(() -> {
for (int i = 0; i < 1000; i++) {
System.out.println("i:" + i);
}
});
t1.start();
为什么要调用start方法而不是run方法?
- run方法是一个模版方法,需要子类去重写,可以提高其灵活性
- start方***去调用start0方法,这是个native方法,负责启动线程。还会调用run方法。
- 如果只调用run方法,相当于只是使用了run方法,但是没有启动线程
- 在JVM启动的时候,实际上有多个线程,但是至少有一个非守护线程(main)
- 当调用线程start方法时,实际上有两个线程,一个是新启动的线程,一个是调用start方法的线程
- 线程的生命周期分为:
new
、runnable
、running
、block
、terminated
二、银行排队叫号系统
版本一
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.
大致意思是,可以给线程一个较高的值来提高栈的深度可以放更多的栈帧,防止栈溢出
但是注意:某些平台可能失效,但是官方文档没有列举
总结
- 创建线程对象Thread,
默认有一个线程名,以Thread-开头,从0开始计数
,如“Thread-0、Thread-1、Thread-2 …” - 如果没有传递Runnable或者没有覆写Thread的run方法,
该Thread不会调用任何方法
- 如果传递Runnable接口的实例或者覆写run方法,则
会执行该方法的逻辑单元
(逻辑代码) - 如果构造线程对象时,未传入ThreadGroup,
Thread会默认获取父线程的ThreadGroup作为该线程的ThreadGroup
,此时子线程和父线程会在同一个ThreadGroup中 - stackSize可以
提高线程栈的深度
,放更多栈帧,但是会减少能创建的线程数目
- stackSize默认是0,
如果是0,代表着被忽略,该参数会被JNI函数调用
,但是注意某些平台可能会失效,可以通过“-Xss10m”设置