06- 线程基础

多线程基础

线程进程

进程

每一个正在运行的程序 

通过windows 任务管理器 查看正在运行的程序

  • 特点
1- 进程是系统运行程序的基本单位
2- 每一个进程都有自己独立的一块内存空间,一组系统资源
3- 每一个进程内部的数据和状态都是完全独立的

线程

概念

线程是进程执行运算的最小单位, 一个进程在其执行过程中可以产生多个线程,线程必须在某个进程中执行.
线程是进程内部的一个执行单元, 是可完成一个独立任务的顺序控制流程,如果在一个进程中同时运行了多个线程,用来完成不同工作, 称之为多线程.

多线程的优势

迅雷多线程下载

  • 最大限度的提高计算机系统的利用效率
  • 可以带来更好的用户体验

线程和进程对比

1- 一个进程至少包含一个线程
2- 系统资源分配给进程, 同一进程中的所有线程共享该进程的所有资源
3- cpu 分配给线程执行机会,而不是分配给进程, 真正运行在cpu上的是线程

CPU 采用时间片段

一个CPU 同一时间只能做一件事

时间片段 : 每隔一段时间去执行一个程序, 时间片段使用完毕之后,再切换到另外一个程序, 因为这个切换速度很快, 用户感觉不到CPU 正在切换执行程序.

就好比说播放视频
视频 帧率 (本质就是一张一张的图片)  一秒内切换20 张图片.
连贯的执行图片切换,让用户感觉是在看视频.
用户感受不到时间粒度太细的操作

CPU 随机选择 一个线程去执行, 感觉上是多个线程是在争抢CPU 的执行权

线程的使用

在学习本章节之前, 所有的代码程序都是单线程的

无论代码有多复杂, 都是按照顺序依次执行的

main 函数线程

书写代码, 抛出异常

Exception in thread "main" java.lang.NullPointerException
  • 主线程入口
  • 开启其它子线程
  • 最后执行各种关闭资源的操作

Thread

第一种创建线程的方法

1- 将一个类声明为Thread的子类。 
2- 这个子类应该重写Thread类的run方法 。 
3- 然后可以分配并启动子类的实例。

创建线程的步骤

1- 书写子类继承Thread 类
2- 重写 run 方法
3- 创建自定义的线程子类对象
4- 调用start() 方法

案例测试

public class MyThread extends Thread{
    @Override
    public void run() {
        for(int i = 0 ; i < 100 ; i++){
            System.out.println("Mythread--"+i);
        }
    }
}

public class Demo2_Thread {
    public static void main(String[] args) {
        MyThread mt = new MyThread();
        mt.start();

        for(int i = 0 ; i < 100 ; i++){
            System.out.println("main--"+i);
        }
    }
}

start()

1- 启动一条线程

2- 执行该线程的run()方法

run() 和 start() 区别

run 仅仅是 线程类中的一个普通方法

如果使用线程对象调用run方法, 仅仅是在main 线程中 执行run() 方法中的代码

start() 才可以真正的启动线程

start() 可以自动去执行run() 方法

细节

多个线程执行, 不是真正的并行

线程每次执行的时长是由CPU 随机分配的时间片决定的

Thread常用方法

优先级

优先级高 有更高的机率可以获取CPU的更多时间片段, 不能保证一定优先执行

类似于掷骰子 1-4 张三 赢 56 是 李四赢, 真正玩游戏, 不能保证是张三

int getPriority() 
返回此线程的优先级。 
setPriority(int newPriority) 
更改此线程的优先级。

设置线程名称

1- 有参构造 Thread(String name) 
分配一个新的 Thread对象。 
2- void setName(String name) 
将此线程的名称更改为等于参数 name 。
String getName() 
返回此线程的名称。  

案例

public class MyThread extends Thread{
    public MyThread() {
    }
    public MyThread(String name) {
     	// 子类如何调用执行 父类的构造方法
        super(name);
    }
}
new MyThread("线程1");

获取当前正在执行的线程

static Thread currentThread() 
返回对当前正在执行的线程对象的引用。  

获取线程状态

Thread.State getState() 
返回此线程的状态。  

判断线程是否处于活跃状态

boolean isAlive() 
测试这个线程是否活着。  

线程休眠

static void sleep(long millis) 
使当前正在执行的线程以指定的毫秒数暂停(暂时停止执行),具体取决于系统定时器和调度程序的精度和准确性。  
public void run() {
    for(int i = 0 ; i < 100 ; i++){
        // 获取当前线程名称
        String sname = Thread.currentThread().getName();
        if(i == 61){
            // run 方法 是 子类重写的父类的方法, 子类抛出的异常不能超过父类的异常
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println(sname+"--"+i);
    }

线程加入

void join() 
等待这个线程死亡。  
ThreadTest tt1 = new ThreadTest();
ThreadTest tt2 = new ThreadTest();
ThreadTest tt3 = new ThreadTest();
tt1.setName("西施");
tt2.setName("貂蝉");
tt3.setName("王昭君");
tt1.start();
/**join的意思是使得放弃当前线程的执行,并返回对应的线程,例如下面代码的意思就是:
 程序在main线程中调用tt1线程的join方法,则main线程放弃cpu控制权,并返回tt1线程继续执行直到线程tt1执行完毕
 所以结果是tt1线程执行完后,才到主线程执行,相当于在main线程中同步tt1线程,tt1执行完了,main线程才有执行的机会
 */
tt1.join();
tt2.start();
// 我要插队, 等我运行结束之后, 之后的代码才可以开始执行
tt3.start();

线程礼让

static void yield() 
对调度程序的一个暗示,即当前线程放弃当前使用的处理器。 
public void run() {
    for(int i = 0 ; i < 100 ; i++){
        // 获取当前线程名称
        String sname = Thread.currentThread().getName();
        System.out.println(sname+"--"+i);
        // 当线程运行到该位置 , 放弃当前cpu的执行权
        // 但是CPU 依然有可能还选择你 不能保证均匀
        Thread.yield();
    }
}

线程终止

void interrupt() 
中断这个线程。 
// 详解
interrupt() 它基于「一个线程不应该由其他线程来强制中断或停止,而是应该由线程自己自行停止。」思想,是一个比较温柔的做法,它更类似一个标志位。其实作用不是中断线程,而是「通知线程应该中断了」,具体到底中断还是继续运行,应该由被通知的线程自己处理。

interrupt() 并不能真正的中断线程,这点要谨记。需要被调用的线程自己进行配合才行。也就是说,一个线程如果有被中断的需求,那么就需要这样做:

在正常运行任务时,经常检查本线程的中断标志位,如果被设置了中断标志就自行停止线程。
在调用阻塞方法时正确处理InterruptedException异常。(例如:catch异常后就结束线程。)
1- interrupt 中断操作时,非自身打断需要先检测是否有中断权限,这由jvm的安全机制配置;
如果线程处于sleep, wait, join 等状态,那么线程将立即退出被阻塞状态,并抛出一个InterruptedException异常;
2- 如果线程处于I/O阻塞状态,将会抛出ClosedByInterruptException(IOException的子类)异常;
3- 如果线程在Selector上被阻塞,select方法将立即返回;
4- 如果非以上情况,将直接标记 interrupt 状态;
5- 注意:interrupt 操作不会打断所有阻塞,只有上述阻塞情况才在jvm的打断范围内,如处于锁阻塞的线程,不会受 interrupt 中断;

守护线程

public final void setDaemon(boolean on)
将此线程标记为daemon线程或用户线程。
当运行的唯一线程都是守护进程线程时,Java虚拟机将退出。 
线程启动前必须调用此方法。 
参数 如果 true ,将此线程标记为守护线程 
public static void main(String[] args) {
    MyThread t1 = new MyThread("皇帝");
    MyThread t2 = new MyThread("总管1");
    MyThread t3 = new MyThread("总管2");
    // 线程启动之前设置 守护线程
    t2.setDaemon(true);
    t3.setDaemon(true);
    // 只要主线程运行完毕, 守护线程 不是立即停止, 等待虚拟机退出
    t1.start();
    t2.start();
    t3.start();
}

Runnable 接口 创建线程

第二种创建线程的方法

查看API Thread 的构造方法
Thread(Runnable target) 
分配一个新的 Thread对象。  
Thread(Runnable target, String name) 
分配一个新的 Thread对象。  
// 如何启动一个 Runnable 接口的线程
MyRunnable mr = new MyRunnable();
// 创建线程离不开 Thread
Thread t1 = new Thread(mr,"线程名称1");
Thread t2 = new Thread(mr,"线程名称2");
t1.start();
t2.start();

使用类创建线程 使用接口创建线程的区别

回忆 类和接口的区别
1- 类只能单继承  接口可以多实现
2- 类是什么是什么的概念  接口 什么有什么功能

自己实现一个线程 如果采用继承的方式 , 只能单继承, 不能够再继承其他类

多线程也可以看做是一种功能, 可以实现 业务和逻辑的分离

推荐使用接口来创建线程

线程状态

线程的生命周期

// 如何启动一个 runnable 接口的线程
MyRunnable mr = new MyRunnable();
// 创建线程离不开 Thread
Thread t1 = new Thread(mr,"线程名称1");
Thread t2 = new Thread(mr,"线程名称2");
t1.start();
t2.start();

线程状态图例

在这里插入图片描述

在这里插入图片描述

线程的5种状态

1- 新建  new 对象
2- 就绪 可运行状态 (对象调用了 start() 方法之后)
3- 运行状态 (CPU 选择了 线程)
4- 死亡 run方法运行完毕
5- 阻塞 由运行状态==> 阻塞状态
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章