线程相关概念
线程
是依赖于进程的执行绪(执行路径/控制单元),是程序使用CPU的基本单位
进程
进程是指可执行程序并存放在计算机存储器的一个指令序列,它是一个动态执行的过程。
表示当前正在执行的程序,代表一个应用程序在内存中的执行区域。
单进程
一个进程中,只有一个线程执行
多进程
同一时间段内执行多个任务,同一时刻只能执行一个任务,如Windows为代表的操作系统。
多进程并不提高某个程序的执行速度,仅仅是提高了CPU的使用率。真正的多进程执行是指多核同时计算。
线程两种调度模型
分时调度模型:所有线程轮流使用CPU的使用权,平均分配每个线程占用CPU的时间片
抢占调度模型:优先让优先级高的线程使用CPU,如果线程的优先级相同,那么会随机选择一个。(线程随机性)
Java使用的为抢占调度模型
线程并行与并发
线程并行:正常的多线程执行就是线程并行。即逻辑上同一时间同时运行
线程并发(异常):由于线程抢占而不应出现的某一时刻的线程及相关数据状态。如并发修改异常的产生
JVM的多线程
JVM启动至少启动了垃圾回收线程和主线程,所以是多线程的。
Java命令会启动java虚拟机,启动JVM,等于启动了一个应用程序,也就是启动了一个进程。该进程会自动启动一个“主线程”, 然后主线程去调用某个类的main方法。所以main方法运行在主线程中
多个程序同时运行
多个程序都是通过cpu来运行的,cpu的执行时间被分为多个等大小的时间片,比如是1ms, 对于cpu来说程序实际是轮流运行的,多个程序是通过随机的抢占cpu时间片来运行,因为时间间隔非常短,所以感官上是多个程序同时运行的,这个称之为cpu时间片的轮转
线程创建
- 创建一个Thread类、或者一个Thread子类的对象
- 创建一个实现Runnable接口的类的对象
继承Thread类
实现:自定义线程类继承Thread类,重写run方法,run方法内为该线程执行代码。将其理解为其他线程的main方法,即该线程的执行入口。
使用:创建线程对象,开启线程,即调用start方法,该方法会自动调用这个线程的run方法
实现Runnable接口
实现:自定义Runnable的子类(非线程类),重写run方法,run方法内为该类对象所在线程的执行代码。同样可将其理解为其他线程的main方法,即该线程的执行入口。
使用:创建Runnable的子类对象,使用Runnable的子类对象创建线程对象,开启线程,即调用start方法,该方法会自动调用这个线程的run方法。
主要区别
- 继承Thread类
— 继承Thread类会导致每一个线程对象中都存储一份属性数据,无法在多个线程中共享该数据。如果加上静态,虽然实现了共享但是生命周期过长。
— 如果一个类明确了自己的父类,那么它就不可以再继承Thread。因为java不允许类的多继承。 - 实现Runnable接口
— 将线程与运行的业务逻辑分离,可以让多个线程共享业务逻辑中的数据。
— 可以让业务类不再继承Thread而专注于业务继承其他类,避免了单继承的局限性。
火车站卖票示例
1.总共5张火车票,通过Thread方式,3个窗口卖出15张,因为成员变量没有共享,票数加static修饰后正常
class MyThread extends Thread{
private static int ticketCount = 5;//一共5张火车票
private String name;//窗口,线程名称
public MyThread(String name) {
this.name = name;
}
@Override
public void run() {
while(ticketCount > 0){
ticketCount--;//如果还有票,就卖掉一张
System.out.println(name + "卖了一张票,剩余票数为:"+ticketCount);
}
}
}
public class TicketsThread {
public static void main(String[] args) {
//创建3个线程,模拟3个窗口卖票
MyThread mt1 = new MyThread("窗口1");
MyThread mt2 = new MyThread("窗口2");
MyThread mt3 = new MyThread("窗口3");
//启动线程,开始卖票
mt1.start();
mt2.start();
mt3.start();
}
}
2.总共5张火车票,通过实现Runnable接口方式,3个窗口卖出5张票,因为共享了同一个对象的成员变量
class MyRunnable implements Runnable{
private int ticketCount = 5;//一共5张火车票
@Override
public void run() {
while(ticketCount > 0){
ticketCount--;//如果还有票,就卖掉一张
System.out.println(Thread.currentThread().getName() + "卖了一张票,剩余票数为:"+ticketCount);
}
}
}
public class TicketsRunnable {
public static void main(String[] args) {
//创建3个线程,模拟3个窗口卖票
MyRunnable mt = new MyRunnable();
Thread th1 = new Thread(mt, "窗口1");
Thread th2 = new Thread(mt, "窗口2");
Thread th3 = new Thread(mt, "窗口3");
//启动线程,开始卖票
th1.start();
th2.start();
th3.start();
/*窗口3卖了一张票,剩余票数为:2
窗口1卖了一张票,剩余票数为:2
窗口2卖了一张票,剩余票数为:2
窗口1卖了一张票,剩余票数为:0
窗口3卖了一张票,剩余票数为:1
*/
}
}
以上执行都存在线程安全问题,票数的显示与预期效果可能不一致,后续我们通过线程同步来解决这个问题
总结
- Runnable方式可以避免Thread方式由于Java单继承特性带来的缺陷
- Runnable的代码可以被多个线程(Thread实例)共享,适合于多个线程处理同一资源的情况
使用匿名内部类创建线程
public class ThreadDemo {
public static void main(String[] args) {
method();
//method2();
}
//使用匿名内部类第一种方式
public static void method(){
//继承Thread类
Thread thread = new Thread(){
@Override
public void run() {
for (int i = 0; i < 20; i++) {
System.out.println(this.getName()+i);
}
}
};
thread.setName("糖糖");
thread.start();
//直接start
new Thread(){
@Override
public void run() {
for (int i = 0; i < 20; i++) {
System.out.println(this.getName()+i);
}
}
}.start();
}
//使用匿名内部类第二种方式
public static void method2(){
//实现Runnable接口
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 20; i++) {
System.out.println(Thread.currentThread().getName()+i);
}
}
}, "糖糖");
thread.start();
//直接start
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 20; i++) {
System.out.println(Thread.currentThread().getName()+i);
}
}
},"浅浅").start();
}
}
网友评论