线程
引入
目前我们的代码在等待用户输入的时候,不能生成随机数
作用
使程序可以同时执行多个事物
名词
进程
简介: 一个正在进行的程序
从内存的角度来看,程序要运行时系统才会为其开辟一片运行内存,程序在运行完毕后系统将回收这片内存
线程
简介: 一个代码的执行路径就是一个线程
多线程
简介: 一个进程中有多个线程
注意:
多个线程在宏观上是同时进行的,在微观上多个线程是在不断抢夺CPU执行权,交替执行的
主线程
简介: 一个进程自带一个线程,该线程被称为主线程
子线程
简介: 一个进程除了主线程,都是子线程
同步
多个线程在执行时,必须先执行一个,再执行另外一个,不能同时操作同一个数据
异步
多个线程同时执行,互不干扰
线程与进程的关系
1.一个进程中可以有多个线程
2.一个进程至少要保证有一个前台线程存活,否则该进程也将被销毁
3.进程间数据不能共享,多个线程间数据是可以共享的
4.进程是一片运行内存,线程是一个执行路径
线程的组成(了解)
1.运行数据
1.栈,每个线程都有其各自的栈内存
2.堆,多个线程共享一个堆内存
2.CPU时间片
当线程抢夺到CPU执行权后可执行的时间
3.线程的逻辑
线程的创建(*)
方案1
方式1
思想: 使用一个类继承于Thread,在该类中重写run方法
步骤:
1.创建一个类
2.使该类继承于Thread
3.在该类中重写run方法
4.在使用该线程的地方创建该类对象
方式2
思想: 使用匿名内部类的形式创建线程对象
方案2
将线程任务(Runnable)与线程(Thread)分开
方式1
思想: 创建线程任务对象,在创建线程对象时传入线程任务对象
步骤:
1.创建一个类,使其实现Runnable接口
2.重写run方法
3.创建该类对象,该类对象就是线程任务对象
4.创建线程对象,传入线程任务对象
方式2
思想: 使用匿名内部类的形式创建线程任务对象
线程的方法
启动(*)
语法:
线程对象.start();
注意:
使用线程对象调用start()方法才会开启新的执行路径
如果使用线程对象调用run()方法相当于对象调用方法,并没有开启新的执行路径
//简单售票案例
public class Test05 {
public static void main(String[] args) {
new Thread(new MyRunnable0("小红")).start();
new Thread(new MyRunnable0("小明")).start();
new Thread(new MyRunnable0("小白")).start();
new Thread(new MyRunnable0("小黑")).start();
}
}
class MyRunnable0 implements Runnable{
private int count = 100;
private String name;
public MyRunnable0(String name) {
this.name = name;
}
@Override
public void run() {
while(count > 0) {
count--;
System.out.println(name + "销售了1张票, 还剩" + count +"张票");
}
}
}
运行结果
消亡(*)
语法:
线程对象.stop(): 可以让线程停止,但是方法已经过时
线程对象.destroy(): 是让程序崩溃,该方法也过时了
线程在什么情况下消亡?
线程执行完run()方法中的代码就会等待系统回收
注意:
一般来说开启线程我们将不再管理线程,等线程中run()方法执行完毕后会自动销毁
线程一经启动不受控制
package demo01;
//手动调用方法销毁线程
public class Test01 {
public static void main(String[] args) {
MyThread thread = new MyThread();
thread.start();
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//销毁线程
thread.myStop();
}
}
class MyThread extends Thread{
private boolean isRun = true;
public void myStop() {
isRun = false;
}
@Override
public void run() {
for (int i = 0; i < 1000 && isRun; i++) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(i);
}
}
}
运行结果
获取当前线程(*)
语法:
Thread 当前线程对象 = Thread.currentThread();
注意:
在哪个线程中使用该方法,就是获取哪个线程的对象
名称
设置名称
方法1:
在创建线程对象时传入线程名称
方法2:
在线程启动前通过setName()设置线程名称
线程对象.setName(线程名称);
因为线程是有默认名称的,如Thread-0,Thread-1...
获取名称
String 线程名称 = 线程对象.getName();
休眠(*)
作用: 线程等待一会
语法:
Thread.sleep(休眠时间);
注意:
1.休眠时间单位为毫秒 1000毫秒 == 1秒
2.在哪个线程中用就是让哪个线程休眠
3.不会释放锁对象
合并
作用: 将多个线程合并为一个线程
语法:
线程对象.join();
注意:
对哪个线程使用该方法就是将哪个线程合并到当前线程中
礼让
作用: 放弃本次抢夺到的CPU执行权,重新参与抢夺
语法:
Thread.yield();
注意:
在哪个线程中使用就是让哪个线程放弃本次抢夺到的CPU执行权
优先级
作用: 增大或减小线程抢夺到CPU执行权的概率
语法:
设置优先级
线程对象.setPriority(优先级);
获取优先级
int 线程优先级 = 线程对象.getPriority();
注意:
1.优先级取值范围为1~10,超过范围会报错
2.在线程启动前设置
3.线程的优先级默认是5
守护线程(*)
子线程分为两种:
前台线程
守护线程(后台线程)
如果创建一个子线程,默认是前台线程
可以通过
线程对象.setDaemon(true); 将子线程设置为守护线程
前台线程与守护线程的区别
当一个进程有前台线程存活,那么该进程就不会被系统回收,直到当前进程中所有前台线程都执行完毕后,进程才会被系统回收
如果一个进程所有前台线程执行完毕,还有守护线程正在执行,那么此时不管守护线程是否执行完毕,进程都将被销毁
package demo01;
public class Test02 {
public static void main(String[] args) {
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(i);
}
System.out.println("子线程结束");
}
});
thread.start();
System.out.println("主线程结束");
}
}
运行结果
修改下代码,设置thread线程为守护线程
package demo01;
public class Test02 {
public static void main(String[] args) {
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(i);
}
System.out.println("子线程结束");
}
});
//设置thread线程为守护线程
thread.setDaemon(true);
thread.start();
System.out.println("主线程结束");
}
}
运行结果
线程的生命周期(*)
初始状态: New
线程创建后,到启动前
就绪状态: Ready
线程启动后,但是没有抢夺到CPU执行权,准备抢夺CPU执行权
运行状态: Running
线程抢夺到CPU执行权,正在执行run方法中的代码
当CPU执行权时间到了后,
如果run方法中的代码执行完毕进入终止状态
如果run方法中的代码没有执行完成,会回到就绪状态
终止状态: Terminated
当线程执行完run方法中的代码后会进入到该状态,等待被系统回收
等待状态: Timed Waiting
有限期等待
如使用线程类调用sleep方法,线程会进入到有限期等待
无限期等待
如A线程合并B线程,B线程就会进入无限期等待
练习
1.火车站三个窗口同时销售1000张票
分析:
需要3个线程
线程任务使销售1000张票
因为是多个窗口同时销售1000张票,所以剩余票的数量应该属于所有售票任务共有的属性
注意: 此时代码存在线程安全问题,以后修改补充...
package demo02;
public class MyRunnable implements Runnable{
private static int count = 1000;
@Override
public void run() {
while(count > 0) {
//放慢线程速度
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
//获取当前线程
Thread thread = Thread.currentThread();
//获取线程名称
String name = thread.getName();
count--;
System.out.println(name + "销售了1张票, 还剩" + count +"张票");
}
}
}
package demo02;
public class Test {
public static void main(String[] args) {
MyRunnable runnable = new MyRunnable();
new Thread(runnable, "窗口一").start();
new Thread(runnable, "窗口二").start();
new Thread(runnable, "窗口三").start();
}
}
运行结果
此时可以看到,会有线程重复数据,涉及到线程安全问题,明天补充~