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