多线程_基础和方法应用

本文介绍了多线程的基础知识,包括PCB、进程状态、线程创建和线程间通信。强调了进程和线程的区别,以及为何需要线程的原因,如并发编程和资源消耗。文章通过Java的Thread和Runnable接口展示了线程的创建方式,并讨论了线程的中断、守护线程和线程同步问题。
摘要由CSDN通过智能技术生成

多线程必备知识:

(为了更好的理解多线程)

IO:磁盘和网络一起称为IO

PCB:为了使参与并发执行的每个程序,包含数据都能独立地运行,在操作系统中必须为之配置一个专门的数据结构,称为进程控制块(PCB,Process Control Block)。进程与PCB是一一对应的,用户进程不能修改。

组织形式是通过双向链表来把多喝PCB给串到一起,创建一个进程,本质上就是创建一个PCB这样的结构体对象,把它插入到链表中,任务管理器查看到进程列表,本质就是遍历这个PCB链表。

PID:进程的身份标识(唯一)

内存指针:指向自己的内存是哪些

文件描述符表:硬盘上的文件和其他资源

进程:操作系统资源分配的基本单位,一个跑起来的程序,也叫任务,每个进程都对应了一些资源,对线程的操作(创建、销毁、调度)开销是比较大的。一线程只能在一个进程里。

进程调度相关属性:

1)进程状态

就绪状态:随叫随到,进程随时准备好了去CPU上执行

运行状态:正在工作

阻塞状态:短时间内无法到CPU上执行,比如进程在进行密集的IO操作,读写数据

2)优先级        先执行谁,后执行谁

3)上下文        

操作系统在进行进程切换的时候需要把进程执行的“中间状态”记录下来,保存好,下次在这个进程上CPU上运行的时候,可以恢复上次的状态,继续执行

保存上下文,就是把这些CPU寄存器上的值,记录保存到内存中

恢复上下文就是把内存中的这些寄存器值恢复回去

并行:微观上同一时刻,两个核心上的进程,就是同时执行的

并发:微观上,同一时刻,一个核心上只能运行一个进程,但是它能够对进程快速的进行切换,人不会察觉

句柄:系统通过调用句柄就可以操作软件资源了

为啥要有线程?

1)并发编程,对着多核CPU的发展称为刚需

2)多进程虽然也能实现并发编程的效果,但是进程的创建和销毁,太消耗资源(成本太高)

理解多线程:

单线程好比一个人在做一件事,多线程,好比叫几个人共同来完成这件事,就像工厂的流水线。增加工人的数量(线程数)是否能够一直提高工作速度呢?当一件事叫太多的人,你给他们的成本,因为工资是一定的,他们分的钱就越少,而且大家可能会因为做不到事情,而开始发生冲突(线程安全问题),这不仅造成了人力资源的浪费,更让工作因为不合理竞争,造成工作出现问题。还有一种情况,有个核心工作的人工做错了(线程抛异常了),如果他没处理好,很可能整个工作就挂了,所有人的努力都白费了。(很可能把整个进程带走)

从组件来看,CPU得核心数量是有限的(工资),线程太多,核心数目有限,不少的开销反而浪费在线程调度上了。到多到一定数量,会导致资源的耗尽,导致别的进程用不了,cpu耗尽。

线程、进程的特点:

线程模型,天然就是资源共享的。多线程争抢同一个资源(同一个变量)。

进程模型,天然是资源隔离的。每个进程至少有一个线程存在,即:主线程,进行进程通信的时候,多进程访问同一个资源,肯能会出现问题。

进程是系统分配资源的最小单位,线程是系统调度的最小单位。

线程的使用:Thread

线程的创建

通过Thread各种的方法写 线程,在主线main用Threa.start()创建子线程

a)继承Thread 重写run

class myThread extends Thread{
    @Override
    public void run() {
        System.out.println("hello Thread");
    }
}
public class Test01 {
        public static void main(String[] args) {
            myThread t1 = new myThread();
            myThread t2 = new myThread();
            myThread t3 = new myThread();
            t1.start();
            t2.start();
            t3.start();
    }
}

b)实现Runnble接口重写run,实现interface接口

这里复习一下。抽象类和普通类的区别:

抽象类不能实例化,不能直接new

必须要搞个子类来继承抽象类

抽象类里还有抽象方法,抽象方法里没有方法体,需要让子类来重写

 而接口比抽象类更抽象,抽象类除了抽象方法,还有普通方法和普通属性,接口则不行,则要求方法都得是抽象方法。

//Runnable 的作用,是描述一个"要执行的任务"
class MyThread implements Runnable{
    @Override
    public void run() {
        System.out.println("hello Thread");
    }
}
public class Test01 {
    public static void main(String[] args) {
        Thread t1 = new Thread(new  MyThread());
        Thread t2 = new Thread(new  MyThread());
        Thread t3 = new Thread(new  MyThread());
        t1.start();
        t2.start();
        t3.start();
    }

c)继承Thread,重写使用匿名类

public static void main(String[] args) {
        Thread t1 = new Thread(){
            @Override
            public void run() {
                System.out.println("这是内部类");
            }
        };
        t1.start();
    }

d)实现Runnable内部类

public static void main(String[] args) {
        Thread t2 = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("这时runnable内部类");
            }
        });
        t2.start();
    }

e)lambda表达式创建

public static void main(String[] args) {
        Thread t1 = new Thread(() -> {
            System.out.println("这是线程1");
        });
        Thread t2 = new Thread(() ->{
            System.out.println("这是线程2");
        });
        t1.start();
        t2.start();
    }

 当线程的run()方法执行完了,线程也没了;

当main方法执行完了,主线程也没了

 常见问题:

1、main比Thread先出来?

不是,线程执行时抢占式执行,有随机性

2、start和run的区别“

start:是创建线程        run:是描述线程要干的工作

如果从main里直接调用run方法,相当于是main主线程直接干run()里的工作

3、可以使用jdk自带的jconsole查看正在运行的线程的情况

 

 

Thread常见方法:

 获取属性                                方法

ID                                         getId()         

名称                                     getName()

状态                                     getState()

优先级                                  getPriority()

是否后台线程                        isDaemon()

是否存活                               isAlive()

是否被中断                            isInterrupted()

public static void main(String[] args) {
        Thread thread = new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                System.out.println(Thread.currentThread().getName() + "正在运行");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println(Thread.currentThread().getName() + ":我快运行完了");
        });
        System.out.println(Thread.currentThread().getName()
                + ": ID: " + thread.getId());
        System.out.println(Thread.currentThread().getName()
                + ": 名称: " + thread.getName());
        System.out.println(Thread.currentThread().getName()
                + ": 状态: " + thread.getState());
        System.out.println(Thread.currentThread().getName()
                + ": 优先级: " + thread.getPriority());
        System.out.println(Thread.currentThread().getName()
                + ": 后台线程: " + thread.isDaemon());
        System.out.println(Thread.currentThread().getName()
                + ": 活着: " + thread.isAlive());
        System.out.println(Thread.currentThread().getName()
                + ": 被中断: " + thread.isInterrupted());
        thread.start();
        while (thread.isAlive()) {}
        System.out.println(Thread.currentThread().getName()
                + ": 状态: " + thread.getState());
    }

守护线程:

后台线程即是守护线程。

线程可分为前台线程和后台线程:

前台线程:会阻止进程结束,前台线程的工作没做完,进程是结束不了的,main以及mani创建的函数,都是前台的

后台线程:不会组织进程结束,后台线程工作没做完,进程也是可以结束的,其他JVM自带的线程都是后台的

可以通过setDeamon实质后台线程

public static void main(String[] args) {
        Thread t1 = new Thread(() -> {
            while (true){
                System.out.println("hello");
            }
        },"mythread");
        t1.setDaemon(true);
        t1.start();
    }

中断线程:

中断的意思是,不是让线程立即停止,而是通知线程,你应该要停止了,是否真的要停止,取决于线程这里具体的代码写法。

1、使用标志位来控制线程的写法

public class Test04 {
    private static boolean flg = true;
    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(() ->{
            while (flg){
                System.out.println("hello Thread");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
                }
        }
        });
        t1.start();
        Thread.sleep(3000);
        flg = false;
        //通过外部的flg来控制线程
    }
}

自定义变量这种方式,不能及时响应,尤其是在sleep休眠时间较久的情况下

这个代码之所以能起到修改flg  ->  线程就结束

完全取决于线程内部的代码

2、使用Thread自带的标注位,来进行判定

这个东西是可以唤醒上面的这个sleep这样的方法的

isInterrupted():返回Boolean,判定线程是否被终止。true终止;false未被终止(应该要继续走)

 public static void main(String[] args) throws InterruptedException {
    Thread t = new Thread(()->{
     //Thread.currentThread(): 相当于this,当前的这个线程
        while (!Thread.currentThread().isInterrupted()){
            System.out.println("hello thread");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    });
    t.start();
    Thread.sleep(3000);

    t.interrupt();

线程t忽略了你的终止请求

如果线程在sleep中休眠,此时调用的interrupt会把t线程唤醒,从sleep中提前返回(interrupt 会触发sleep内部的异常,导致sleep提前返回)

 interrupt会做两件事:

1、把线程内部的标志位(boolean)设置成true

2、如果线程在进行sleep,就会触发异常,把sleep唤醒

3、但是sleep在唤醒的时候,还会做一件事,把刚才设置的这个标志位,再设置成false(清空了标志位)这就导致sleep的异常被catch完了之后,循环还要继续进行

 线程t立即响应你的终止请求

public static void main(String[] args) throws InterruptedException {
    Thread t = new Thread(()->{
        while (!Thread.currentThread().isInterrupted()){
            System.out.println("hello thread");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            //上述代码加一个break
                break;
            }
        }
    });
    t.start();
    Thread.sleep(3000);

    t.interrupt();
}

 线程t稍后终止:

public static void main(String[] args) throws InterruptedException {
    Thread t = new Thread(()->{
        while (!Thread.currentThread().isInterrupted()){
            System.out.println("hello thread");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
                try {
                    Thread.sleep(3000);
                } catch (InterruptedException ex) {
                   ex.printStackTrace();
                }
                break;
            }
        }
    });
    t.start();
    Thread.sleep(3000);

    t.interrupt();
}

到底是立即终止还是稍后,就把选择权交给程序猿自己用break去终止;调用interrupt知识告诉线程,你应该终止了。如果不用sleep就不会出现上述情况。

 等待一个线程-join()

线程是一个随机调度的过程

等待线程,做的事情就是再控制两个线程的结束顺序

public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(() -> {
            for (int i = 0; i < 3; i++) {
                System.out.println("hello Thread");
                try {
                    Thread.sleep(300);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        t1.start();
        System.out.println("join之前");
        //让join让main去等待t1线程结束
        t1.join();
        System.out.println("join之后");
    }

如果开始执行join时,t线程已经结束,就会直接返回,不会阻塞main线程

join方法

方法                                                             说明

public void join()                                         等待线程结束

public void join(long millis)                         等待线程结束,最多等 millis 毫秒

public void join(long millis, int nanos)         同理,但可以更高精度

获取当前线程引用currentThread()

方法                                                                 说明

public static Thread currentThread()        返回当前线程对象的引用

static:是静态方法,也就是类方法,不需要实例化对象,可以直接通过列名来调用

 public static void main(String[] args) throws InterruptedException {
        //t1只是引用,并不是Thread的id
        Thread t1 = new Thread(() -> {
            for (int i = 0; i < 3; i++) {
                System.out.println(Thread.currentThread().getName());
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        t1.start();
        t1.join();
        Thread t2 = new Thread();
        System.out.println(t2.currentThread().getName());
    }

让线程休眠-sleep()

本质上就是让这个线程不参与调度.

方法                                                                                                                 说明

public static void sleep(long millis) throws InterruptedException        休眠当前线程 millis毫秒

public static void sleep(long millis, int nanos) throws InterruptedException 可以更高精度的

                                                                                                                        休眠

 上述代码已多次使用,这里就不再演示。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值