线程基础复习

本文详细介绍了线程的概念,线程与进程的关系,以及Java中线程的创建方法,包括继承Thread、实现Runnable和Callable接口。同时,文章讨论了并发与并行、同步与异步的区别,线程间的同步方式如互斥锁、读写锁等,并解释了PCB(进程控制块)的作用。此外,还讲解了线程的优先级、状态、死锁的概念及其预防策略,以及线程通信的经典案例——生产者-消费者问题。
摘要由CSDN通过智能技术生成

目录

线程

线程与进程的关系

程序计数器、虚拟机栈和本地方法栈为什么是私有的?

并发与并行的区别

同步和异步的区别

线程的创建

线程间的同步方式

PCB是什么?

线程的优先级

线程状态

线程的分类

什么是线程的上下文切换?

什么是线程的死锁?

产生死锁的四个必要条件

如何预防和避免死锁?

线程中一些常用的方法

sleep()方法和wait()方法对比

为什么wait()方法不定义在Thread中?

为什么 sleep() 方法定义在 Thread 中?

可以直接调用Thread的run方法吗?

线程通信

经典案例


线程

程序:一段静态的代码

进程:正在执行的程序,是操作系统资源分配的最小单位

线程:进程可进一步细分为线程,是进程内部最小的执行单元,是操作系统进行任务调度的最小单元,属于进程

现在Java线程的本质就是操作系统的线程

线程与进程的关系

  • 一个进程可以包含多个线程,线程隶属于进程

  • 一个进程中至少包含一个线程(主线程),在主线程中可以创建其他线程

  • 一个进程内的所有线程共享该进程的内存资源

总结:线程是进程可以划分的最小执行单位,线程和进程最大的区别是进程基本上都是独立的,而各线程不一定,因为同一进程的线程之间可能互相影响。线程执行开销小,但不利于资源的管理和保护,进程则相反。

程序计数器、虚拟机栈和本地方法栈为什么是私有的?

  • 程序计数器主要是为了保证切换线程后能恢复到正确的位置

  • 虚拟机栈和本地方法栈是为了保证局部变量不被其他线程访问到

并发与并行的区别

  • 并发:两个及两个以上的作业在同一时间段进行,也就是依次进行

  • 并行:两个及两个以上的作业在同一时刻进行

同步和异步的区别

  • 同步:发出一个调用后,在没有得到结果前,该调用就不可以返回,一直等

  • 异步:调用在发出后,不用等待返回结果,该调用可以直接返回

线程的创建

继承Thread类

类就不能继承其他类

public class MyThread extends Thread{
    @Override
    public void run() {// 线程执行代码 执行完毕 线程死亡 
        for (int i = 0; i < 1000; i++) {
            System.out.println("MyThread:"+i);
        }
    }
}
public class Test {
    /*
       main启动java主线程
     */
    public static void main(String[] args) {
          //创建线程
          MyThread myThread = new MyThread();
                   myThread.start();//启动线程
​
        for (int i = 0; i < 1000; i++) {
            System.out.println("main:"+i);
        }
    }
}

实现Runnable

类还可以继承其他类

public class ThreadeDemo implements Runnable{
​
    /*
    currentThread() 获得当前正在执行的线程对象
    getName()获取线程名称
     */
    @Override
    public void run() {
        /*for (int i = 0; i < 1000; i++) {
            System.out.println(Thread.currentThread().getName()+":"+i);
        }*/
        System.out.println(Thread.currentThread().getName());
    }
}
public class Test {
​
    public static void main(String[] args) {
        //创建线程执行任务
        ThreadeDemo threadeDemo = new ThreadeDemo();
        //Thread t = new Thread(threadeDemo);//这是创建线程
        Thread t = new Thread(threadeDemo,"自定义线程");//这是创建线程,并为线程命名
               //t.setName("");// 为线程命名的方法
               t.start();//启动线程,在操作系统中注册,加入到就绪队列,并不是立即执行
               t.setPriority(10);//设置优先级
               Thread.currentThread().setPriority(1);
        System.out.println(t.getPriority());//获得优先级
        System.out.println(Thread.currentThread().getPriority());
​
        System.out.println("main");
    }
}

实现Callable接口

与使用Runnable相比,Callable功能更强大一些

相比run()方法,可以有返回值

方法可以抛出异常

支持泛型的返回值

需要借助FutureTask类,获取返回结果

public class SumThread implements Callable<Integer>{
​
​
    @Override
    public Integer call() throws Exception {
        int num = 0;
        for (int i = 0; i < 100; i++) {
            num+=i;
        }
        return num;
    }
}  
public class Test {
​
    public static void main(String[] args) throws ExecutionException, InterruptedException {
​
        SumThread sumThread = new SumThread();
​
        // 接收任务
        FutureTask<Integer> futureTask = new FutureTask(sumThread);
​
        // 创建线程
        Thread t = new Thread(futureTask);
​
        t.start();
​
        Integer sum = futureTask.get();// 获得call方法返回值
        System.out.println(sum);
​
    }
}

线程间的同步方式

  • 互斥锁:采用互斥对象机制,只有拥有互斥对象的线程才可以访问共同资源,例如synchronized和Lock

  • 读写锁:允许多个线程同时读取共享资源,但是只有一个线程可以对资源进行写操作

  • 信号量:允许同一时刻多个线程访问同一资源,但是要控制访问的最大线程数

  • 屏障:屏障是一种同步原语,用于等待多个线程到达某个点再一起继续执行。

  • 事件:Wait/Notify:通过通知操作的方式来保持多线程同步,还可以方便的实现多线程优先级的比较操作。

PCB是什么?

PCB(Process Control Block) 即进程控制块,是操作系统中用来管理和跟踪进程的数据结构,每个进程都对应着一个独立的 PCB。可以将 PCB 视为进程的大脑。

PCB主要包含以下内容:

  • 进程的描述信息:包括进程的名称、标识符等

  • 进程的调度信息:包括阻塞原因、进程状态、优先级等

  • 进程对资源的需求情况:包括CPU时间、内存空间等

  • 进程打开的文件信息:包括文件类型、打开模式等

线程的优先级

实际上,计算机上只有一个CPU,各个线程轮流获取到CPU的执行权,才能使用任务

优先级较高的线程有更多的机会获取到CPU,反之亦然

优先级用整数表示,取值范围在1~10,一般情况下线程默认优先级为5,但是可以通过setPriority和getPriority方法来设置或返回优先级;

调度策略

  • 时间片

  • 抢占式:高优先级的线程抢占CPU

Java的调度方法:

同优先级线程组成先进先出的队列,使用时间片的策略

高优先级,使用抢占式策略

线程状态

新建:当一个线程被声明并创建时,那么该线程就是新建状态

就绪:新建的线程被start()后,将进入等待线程的队列等待CPU的时间片

运行:当就绪的线程获取到CPU资源时,便进入到了运行状态

阻塞:在某种情况下,被认为挂起或者执行输入输出操作时,让CPU临时终止自己的执行,进入阻塞状态

死亡:线程完成了它的全部工作或者线程提前被强制终止或者出现了异常

线程的分类

java中的线程分为两类:用户线程和守护线程

通俗来讲,任何一个守护线程都是非守护线程的保姆,只要当前JVM中还存在任何一个非守护线程没有结束,那么守护线程就会一直工作,当最后一个非守护线程结束时,守护线程也会随之结束

ThreadDemo1 t1 = new ThreadDemo1();
            t1.setDaemon(true);//设置线程为守护线程,必须在启动前设置,其他的用户线程结束,守护线程自动结束
            t1.start();

注意:设置守护线程一定要在启动线程之前,否则会抛出一个

IllegalThreadStateException异常

什么是线程的上下文切换?

也就是线程的切换,当线程进行切换时,要保存当前线程的上下文,等待下次线程获取到CPU执行的时候恢复现场,并加载下一个占用CPU的线程的上下文

以下几种情况,线程会从占用的CPU中退出:

  • 主动让出CPU,例如调用sleep()、wait()等方法

  • 时间片用完,因为操作系统要防止一个线程占用CPU资源太长时间导致其他线程死亡

  • 被用了阻塞类型的操作,例如IO,线程被阻塞

什么是线程的死锁?

多个线程同时被阻塞,它们中的一个或者全部都在等待某个资源被释放,由于线程无限期的被阻塞,因此程序不能终止,被称为线程死锁

线程A持有资源2,线程B持有资源1,它们同时都想申请对方的资源,所以这两个线程会因为互相等待进入到死锁状态

public class DemoThread extends Thread {
    static Object obja = new Object();
    static Object objb = new Object();
    boolean flag = true;
​
    public DemoThread(boolean flag) {
        this.flag = flag;
    }
​
    @Override
    public void run() {
​
        if (flag) {
            synchronized (obja) {
                System.out.println("if obja");
                synchronized (objb) {
                    System.out.println("if objb");
                }
            }
        } else {
            synchronized (objb) {
                System.out.println("else objb");
                synchronized (obja) {
                    System.out.println("else obja");
                }
            }
        }
    }
}
/*
     该测试实现的可能结果:
                   1. if obja  else objb 这是因为当我们第一线程开始执行后,在其执行完System.out.println("if obja");我们的第二个线程可能这个时候拿到了objb锁,也开始执行,这个时候不管是第一个线程还是第二个线程,都在等待对方释放自己需要的同步资源,这就形成了线程死锁
                   2. else objb if obja 和上面的原因同理 也是形成了死锁
                   3.if obja if objb else objb else obja 这种情况是最可观的情况,当第一个线程执行完,第二个线程再获得锁,再去执行
  */
public class Test {

    public static void main(String[] args) {

        DemoThread d1 = new DemoThread(true);
        d1.start();

        DemoThread d2 = new DemoThread(false);
        d2.start();
    }
}

产生死锁的四个必要条件

  • 互斥条件:该资源任意时刻只能有一个线程占用

  • 请求与保持条件:一个线程因请求资源而被阻塞,对已获得的资源保持不放

  • 不剥夺条件:线程已获得的资源不能被其他线程强制剥夺,必须自己使用完毕才可以释放

  • 循环等待条件:若干线程形成一种循环等待的关系

如何预防和避免死锁?

破坏掉四个必要条件:

  • 破坏申请与保持条件:一次性申请全部资源

  • 破坏不剥夺条件:占用部分资源的线程申请其他资源的时候,如果申请不到,主动释放自己的资源

  • 破坏循环等待条件:按某一顺序申请资源,释放资源则反着释放

线程中一些常用的方法

run()

start():启动线程

构造方法

获取当前线程

获取名字

sleep():让线程休眠指定时间

join():等待该线程结束(把其他线程加入到此线程,让其他线程阻塞)

yield():线程让步

wait():当前线程进入阻塞状态,并释放同步监视器

notify():唤醒被wait的一个线程,如果多个线程被wait,就唤醒优先级最高的那个

notifyAll():唤醒所有被wait的线程

sleep()方法和wait()方法对比

共同点:两者都可以暂停线程

区别:

  • sleep() 方法没有释放锁,而wait()方法释放了锁

  • wait()方法通常用于线程的交互/通信,sleep() 方法被用于暂停

  • wait()后,线程不会自动苏醒,需要别的线程调用同一个对象上的 notify()或者 notifyAll() 方法。sleep()方法执行完后,线程会自动苏醒,或者也可以使用 wait(long timeout) 超时后线程会自动苏醒。

  • sleep()Thread 类的静态本地方法,wait() 则是 Object 类的本地方法。

为什么wait()方法不定义在Thread中?

wait() 是让获得对象锁的线程实现等待,会自动释放当前线程占有的对象锁。既然要释放当前线程占有的对象锁并让其进入 WAITING 状态,自然是要操作对应的对象(Object)而非当前的线程(Thread

为什么 sleep() 方法定义在 Thread 中?

因为sleep()是让当前线程暂停,不涉及到对象类,也用不到对象锁

可以直接调用Thread的run方法吗?

调用 start() 方法方可启动线程并使线程进入就绪状态,直接执行 run() 方法的话不会以多线程的方式执行。会把run()方法认为是main线程下的普通方法

线程通信

线程通信指的是多个线程通过相互牵制,相互调度,即线程间的相互作用

经典案例

生产者(Productor)将产品放在柜台(Counter),而消费者(Customer)从柜台处取走产品,生产者一次只能生产固定数量的产品(比如:1),这时柜台中不能再放产品,此时生产者应停止生产等待消费者拿走产品,此时生产者唤醒消费者来取走产品,消费者拿走产品后,唤醒生产者,消费者开始等待

/*
   柜台 放商品  共享数据
 */
public class Counter {

    int num = 0;

    /*
      生产者生产商品
      synchronized修饰的是非静态方法 锁是this   就是counter对象,只有一个
     */

    public synchronized void add(){
        if (num==0){
            num++;
            System.out.println("生产者生产商品");
            this.notify();//唤醒消费者
        }else {
            try {
                this.wait();// 有商品,生产者等待  释放锁
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    /*
       消费者取走商品
     */
    public synchronized void sub(){
        if (num==0){
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }else {
            num--;
            System.out.println("消费者拿走了商品");
            this.notify();// 唤醒生产者
        }
    }
}
/*
  生产者线程
 */
public class ProductorThread extends Thread {

    Counter c;

    public ProductorThread(Counter c) {
        this.c = c;
    }

    @Override
    public void run() {
        while (true) {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            c.add();
        }
    }
}
/*
  消费者线程
 */
public class CustomerThread extends Thread {
    Counter c;

    public CustomerThread(Counter c) {
        this.c = c;
    }

    @Override
    public void run() {
        while (true) {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            c.sub();
        }
    }
}
public class Test {

    public static void main(String[] args) {

        Counter counter = new Counter();// 柜台 只有一个,是共享数据

        ProductorThread p = new ProductorThread(counter);
        CustomerThread c = new CustomerThread(counter);
        p.start();
        c.start();
    }
}

执行结果:

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

重开之Java程序员

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值