线程三连鞭之“线程基础”

一:什么是线程?🎈

1.要了解线程,首先就要知道什么是进程,进程和线程之间又有什么区别?

  • 1.什么是进程?
    是具备一定独立功能的程序,他是系统进行资源(内存,CPU)分配和调度的基本单位,他是程序执行时的一个实例,也就是说,进程可以独立运行一段程序。程序运行时系统就会创建一个进程,并为它分配资源,然后把该进程放入进程就绪队列,进程调度器选中它的时候就会为它分配CPU时间,程序开始真正运行。
  • 2.线程又是什么?
    线程是进程的一个实体,是程序执行时的最小单位,是CPU调度和分派的基本单位
    一个进程可以有多个线程组成,线程间共享进程的所有资源,每个线程有自己的堆栈和局部变量。
    线程由CPU独立调度执行,在多CPU环境下就允许多个线程同时运行。同样,多线程也可以实现并发操作,每个请求分配一个线程来处理。

2.线程和进程之间有什么联系?

  • 1.一个线程只能属于一个进程,而一个进程可以有多个线程(至少有一个线程,即主线程),线程是操作系统可识别的最小执行和调度单位。
  • 2.资源分配给进程,同一进程的所有线程共享该进程的所有资源。
  • 3.线程在执行过程中,需要协作同步(例如:生产者消费者)。不同进程的线程间要利用消息通信的办法实现同步。
  • 4.CPU是分给线程,即真正在CPU上运行的是线程。

小结:
进程作为拥有资源的基本单位,线程是作为CPU调度和分配的基本单位。
进程是拥有资源的独立单位,线程是不拥有系统资源,但是可以访问隶属于进程的资源。

在这里插入图片描述

二:并行与并发🏀

  • 并发是在同一个cpu上同时(不是真正的同时,而是看来是同时,因为CPU要在多个程序之间切换)运行多个程序。
  • 并行指两个或两个以上事件或活动在同一时刻发生,在多道程序环境下,并行使多个程序同一时刻可在不同CPU上同时执行。

单核CPU下,线程实际还是串行执行。操作系统中的任务调度器将CPU的时间片分给不同的线程使用,由于CPU在线程间的切换非常快,人类感觉是同时运行的。
硬核举例:
并发:一个人的送餐速度特别快,快到刚点完外卖就送到用户家楼下,那么用户就会以为这是一个团队在办事,实际上只有一个人。
并行:一个团队送餐。
=ps:大部分时候是既有并行又有并发,虽然有多个核,但是来的线程数多。=

二:进程的四种创建方式🏸

1.继承Thread类,重写run()方法;
2.实现Runnable接口,重写run()方法;
3.实现Callable接口,重写call()方法;
4.使用线程池创建线程;

一:继承Thread类,重写run方法:

在这里插入图片描述


二:实现Runnable接口,重写run()方法(实现类的实例对象作为参数传给Thread)
在这里插入图片描述


那么就会有同学问了,为什么要这么写?
因为开发者要求我们这么写呀,我们不妨看看源码:
在这里插入图片描述

第一种和第二种方式在本质上没有明显的区别,在外观上有很大的区别,第一种方式是继承Thread类,因Java是单继承,如果一个类继承了Thread类,那么就没办法继承其它的类了,在继承上有一点受制,有一点不灵活,第二种方式就是为了解决第一种方式的单继承不灵活的问题,所以平常使用就使用第二种方式


三.实现Callable接口,重写call()方法;
实现接口,重写call( )方法:
在这里插入图片描述
具体过程:
在这里插入图片描述


Runnable和Callable的区别:

  • 1)Runnable提供run方法,无法通过throws抛出异常,所有CheckedException必须在run方法内部处理。Callable提供call方法,直接抛出Exception异常。
  • 2)Runnable的run方法无返回值,Callable的call方法提供返回值用来表示任务运行的结果
  • 3)Runnable可以作为Thread构造器的参数,通过开启新的线程来执行,也可以通过线程池来执
    行。而Callable只能通过线程池执行。

ps:关于Callable,FutureTest只是接受Callable的结果,FutureTest继承了Future类,同时也实现Runnable接口,所以最后是FutureTest是作为Runnable的任务submit( )给线程池,所以只能通过线程池来执行。


四:线程池(重点)

因为是重点,所以:传送门!!!

三:同步和异步⚽

同步 :synchronized;需要等待结果返回,才能继续运行就是同步
异步 :asynchronized;不需要等待结果返回,就能继续运行就是异步
在这里插入图片描述
那么为什么要有同步和异步?我们来看一个案例:

在这里插入图片描述
那么synchronized(同步(锁))的意义就在于这里:Synchronized加了锁之后其他线程就不能访问了,除非释放这个锁。

接下来再看一个典型案例(生产者消费者问题):
在这里插入图片描述
在这里插入图片描述
一:创建蛋糕类

public class Cake {
    private String number;//给每个蛋糕一个编号

    public Cake() {
    }

    public Cake(String number) {
        this.number = number;
    }

    public String getNumber() {
        return number;
    }

    public void setNumber(String number) {
        this.number = number;
    }

    @Override
    public String toString() {
        return "Cake{" +
                "number='" + number + '\'' +
                '}';
    }
}

二:创建盘子类

import java.util.Deque;
import java.util.LinkedList;

public class Panzi {
    //用双向队列来模拟工作流程
    LinkedList<Cake> queue = new LinkedList<>();

    //放蛋糕
    //防止生产同一块蛋糕(硬核)
    public synchronized void putCake(Cake cake){
        if(queue.size() >= 5){//定义最多5个盘子
            System.out.println("ProducerThread wait");//等待客户吃完
            try {
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        queue.addLast(cake);
        notifyAll();//提醒客户蛋糕来喽
    }

    //取蛋糕
    //同时加锁,防止客户抢蛋糕哦
    public synchronized Cake getCake(){
        if(queue.size() <= 0){//无餐了
            System.out.println("ConsumerThread wait");//等待店家生产
            try {
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        Cake cake = queue.removeFirst();
        notifyAll();
        return cake;
    }
}

三:创建生产者类

import java.util.Random;

public class ProducerThread extends Thread {
    private Panzi panzi;

    public ProducerThread(String name, Panzi panzi) {
        super(name);
        this.panzi = panzi;
    }

    @Override
    public void run() {
        for (int i = 1; i < 100; i++) {
            Cake cake = new Cake("no:" + i);
            panzi.putCake(cake);
            System.out.println(Thread.currentThread().getName() + ":" + cake);

            //模拟真实时间场景
            try {
                Thread.sleep(new Random().nextInt(5000));
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

四:创建消费者类

import java.util.Random;

public class ConsumerThread extends Thread{
    private Panzi panzi;
    public ConsumerThread(String name, Panzi panzi) {
        super(name);
        this.panzi = panzi;
    }

    @Override
    public void run() {
        for (int i = 1; i < 100; i++) {
            Cake cake = panzi.getCake();
            System.out.println(Thread.currentThread().getName()+":"+cake);
            try {
                Thread.sleep(new Random().nextInt(5000));
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

Main

public class Main {
    public static void main(String[] args) {
        Panzi panzi = new Panzi();
        ProducerThread producerThread = new ProducerThread("生产者线程",panzi);
        producerThread.start();
        ConsumerThread consumerThread = new ConsumerThread("消费者线程",panzi);
        consumerThread.start();
    }
}

结果部分截图:

在这里插入图片描述

多线程的编程步骤:
1、第一步:创建资源类,在资源类创建属性和操作方法(在这类Panzi就是资源类)
2、第二步:在资源类中操作方法 (Panzi里面有getCake()方法)
1、判断
2、业务代码(干活)
3、通知
3、第三步:创建多个线程,调用资源类的操作方法。 (生产者线程和消费者线程生产了蛋糕放到盘子panzi.putCake(),吃蛋糕panzi.getCake())

四:Thread常见API🏓

在这里插入图片描述

注意点

  • sleep():
    让线程暂时停止可以选择sleep方法。比如Thread.sleep(1000),当前线程睡眠1秒。需要知道的是,1秒后,线程是回到可执行状态,并不是执行状态,什么时候执行那是由虚拟机来决定的。所以sleep(1000)并不是在睡眠1秒后立即执行。

  • yield():
    解释它之前,先简述下,多线程的执行流程:多个线程并发请求执行时,由cpu决定优先执行哪一个,即使通过thread.setPriority(),设置了线程的优先级,也不一定就是每次都先执行它
    Thread.yield();,表示暂停当前线程,执行其他线程(包括执行yield这个线程), 执行谁由cpu决定
    yield这个方法是让当前线程回到可执行状态,以便让具有相同优先级的线程进入执行状态(包括这个执行yield的线程,因为其也在可执行状态)
    public static native void yield();
    ①Yield是一个静态的原生(native)方法
    ②Yield告诉当前正在执行的线程把运行机会交给线程池中拥有相同优先级的线程。
    ③Yield不能保证使得当前正在运行的线程迅速转换到可运行的状态
    ④它仅能使一个线程从运行状态转到可运行状态,而不是等待或阻塞状态

  • join()
    在某些情况下,如果子线程里要进行大量的耗时的运算,主线程可能会在子线程执行完之
    前结束,但是如果主线程又需要用到子线程的处理结果,也就是主线程需要等待子线程执
    行完成之后再结束,这个时候就要用到join()。
    阻塞所在线程,等调用它的线程执行完毕,再向下执行
    a.join,在API中的解释是,在B线程中调用a.join(),堵塞当前线程B,直到A执行完毕并死掉,再执行B。
    线程实例的方法join()方法可以使得一个线程在另一个线程结束后再执行。如果join()方法在一个线程实例上调用,当前运行着的线程将阻塞直到这个线程实例完成了执行

ps:有些题目中常问下面哪些可以开启Thread线程,其中有个选项是run();这个run()方法并不能开启一个线程,只有start()才能开启一个线程;不妨进行尝试:
在这里插入图片描述
在这里插入图片描述
由此可见,这两个案例都只有一个主线程在运行,因此run()并不能打开一个线程。

五:如何正确停止线程?🎱

java中有三种停止线程方法

  • 1)使用退出标志,使线程正常退出,也就是当run方法完成后线程终止。
  • 2)使用stop方法方法强行终止线程,但是不推荐使用这个方法,应为stop不安全而且已经被废弃的方法,还有suspend和resume都是废弃的方法。
  • 3)使用interrupt方法中断线程。 interrupt()方法 仅仅使线程中打了一个停止的标记,并不是真的停止线程。
    this.interrupted() 测试当前线程是否已经中断。
    this.isInterrupted()测试线程是否已经中断。

中断线程

线程的thread.interrupt()方法是中断线程,将会设置该线程的中断状态位,即设置为true,之后的结果:线程是死亡、还是等待新的任务或是继续运行至下一步,就取决于这个程序本身,并不是一定中断这个线程。线程会不时地检测这个中断标示位,以判断线程是否应该被中断(中断标示值是否为true)。

  • 4
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值