JAVA多线程基础

本文介绍了JAVA多线程的基础知识,包括线程的创建、常用方法、死锁概念及解决,以及多线程售票问题的解决方案。通过Thread和Runnable两种方式创建线程,讨论了线程生命周期和并发并行的区别,并探讨了死锁现象及其释放锁的条件。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

目录

线程的创建

Thread

Runnable

区别

线程的创建流程图

线程常用方法

第一类:

第二类:

两种线程模式:

线程的生命周期:

死锁

概念

释放锁

多线程售票问题

遇见的问题

解决方案


线程相关概念

  • 程序:是一些指令的集合,也就是我们写的代码

  • 进程:就是运行中的程序,操作系统会为一个进程分配内存空间,分配资源。

    进程是程序的一次执行过程,是动态过程:有它自身的产生、存在和消亡的过程

  • 线程:由进程创建的,是进程的一个实体,一个进程可以拥有多个线程

    • 单线程:一个时刻只允许一个线程

    • 多线程:一个时刻可以有多个线程

  • 并发:同一个时刻,多个任务交替执行,造成一种“貌似同时”的错觉,单核CPU实现的多任务就一定是并发

  • 并行:同一个时刻,多个任务可以同时执行。需要多核CPU实现并行

    • 电脑中并发和并行会同时存在

线程的创建


Thread

通过继承Thread类,调用class.start()方法开启一个线程

package com.pc.threadUse;

public class Thread01 {
    public static void main(String[] args) throws InterruptedException {
        //创建Cat,可以当做线程使用
        Cat cat = new Cat();
        /**这里不用run的原因,是因为run方法就是一个普通的方法,相当于函数调用
         * 不会真正的启动一个线程,也就是会阻塞主线程。
         * */
        cat.start();//启动线程
        /**追源码发现,start方法使用的是start0()这个方法,
         * start0()是native方法,由JVM调用,最终交给操作系统进行统一调度
         * 涉及到操作系统的调度知识
         * start0()是真正启用线程的函数
         * */
        //main启动一个子线程后,Thread-0,主线程不会阻塞在这里
        System.out.println("主线程继续执行" + Thread.currentThread().getName());
        for (int i=0; i < 10; i++) {
            System.out.println("主线程");
            Thread.sleep(1000);
        }

    }
}

class Cat extends Thread {
    int time = 0;

    @Override
    public void run() {
        //创建Cat对象,可以当做线程使用
        while (time < 80) {
            System.out.println("喵喵,我是一只小猫咪" + (++time) + "线程名称:" + Thread.currentThread().getName());
            //休眠一秒
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

Runnable

通过实行Runable接口来创建一个线程,需要new Thread来调用start方法开启线程。

package com.pc.threadUse;

//使用Runable开发线程
public class thread02 {
    public static void main(String[] args) {
        Dog dog=new Dog();
        //dog.start(); 这里不能调用start
        Thread thread=new Thread(dog);
        thread.start();
    }
}



class Dog implements Runnable {

    int count = 0;

    @Override
    public void run() {
        while (count!=10) {
            System.out.println("小狗汪汪叫" + (++count) +"    "+Thread.currentThread().getName());
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

区别

  • 没有本质区别,底层都调用start0();

  • 实现Runable接口的方式更加适合多个线程共享一个资源的情况,避免了单继承的限制

  • Runable,Thread的设计使用了代理模式。

    • 代理模式

      • 概念:一个类代表另一个类的功能。

      • 优点:职责清晰、高扩展性

      • 缺点:造成请求的处理速度变慢、实现代理模式需要额外的工作,有些代理模式的实现非常复杂

线程的创建流程图

    一个进程的开启,会启动主线程,主线程会开启新的子线程,子线程中也可以创建另一个线程……

    一个进程的消亡,是等该进程中所有的线程都消亡。

    如图:

注意点:主线程消亡进程不一定消亡,线程中也可以开启新的线程

线程常用方法


第一类:

  • setName(); 设置线程名称

  • getName(); 活动线程名称

  • start(); 启动一个线程

  • run(); 线程真正执行的方法

  • setPriority 更改线程优先级(优先级范围 1-10,1最低,10最高)

  • getPriority 获得线程优先级

  • sleep 休息(毫秒)

  • interrupt 中断线程(但没有终止线程,所以一般用于中断正在休眠的线程)

第二类:

  • yield 线程礼让,让其他线程先执行,但礼让的时间不确定,所以也不一定成功

  • join 线程插队,一旦插队成功,肯定先执行完插入的线程的所有任务

两种线程模式:

  • 用户线程:独立于其他线程,自己运行。默认的线程模式

  • 守护线程:其他用户线程结束,守护线程也会结束,Thread.setDeamon(true)可以将Thread设置成守护线程

线程的生命周期:

一般的,线程有6个状态。

  1. New 尚未启动的线程

  2. Runable 可运行状态

  3. TimeWaiting 休眠状态,计时等待

  4. Waiting 等待状态,等待别的线程运行完

  5. Blocked 上锁等待

  6. Teminated 结束

其中Runable状态又分为Ready 就绪状态,Running 正在运行状态。因此有些地方称线程有七种状态。

各状态转换如图:

 

死锁


概念

  • 一个线程拿到锁A而得不到锁B,另一个线程拿到锁B而拿不到锁A,而线程1,2释放锁的条件为拿到锁A和锁B,这种情况程序就运行不下去了,称为死锁

释放锁

以下情况会释放锁:

  1. 同步代码块执行完毕

  2. 同步代码块中遇到break,return

  3. 同步代码块中遇见了Error或者Exception异常

  4. 同步代码块中执行了wait(),线程暂停,释放锁

以下情况不会释放锁:

  1. 当前线程的sleep()和yield()方法

  2. 其他线程调用了该线程的suspend()方法将该线程挂起。(尽量避免使用suspend()和resume()方法,这两种方法已经被淘汰)

多线程售票问题


遇见的问题

    多个线程同时操作一个敏感数据-->剩余车票,会导致数据的更新不及时而导致超售。也就是剩余车票变为负数的问题。

 解决方案

    利用线程同步机制

  • 概念:在多线程编程,一些敏感数据不允许被多个线程同时访问,此时就需要使用同步访问技术,保证数据在任何同一时刻,最多只能有一个线程访问,以保证数据的完整性

  • 方法:使用Synchronized关键字,写同步代码块synchronized(对象){}

  • 原理:互斥锁。synchronized修饰的代码块,调用时会拿到“互斥锁”,结束后,将“互斥锁”返回给底层,多个线程继续去抢这个“锁”,拿到锁的线程进入代码块。

  • 局限性:导致程序的执行效率降低

  • 注意点:

    • 静态方法加锁,synchronized(对象){}需要改为-->synchronized(类名.class){}

    • 细节:要让多个线程共用一个对象的一把锁,不要锁错对象!

具体实现的代码:

package com.pc.syn;

public class sellTicket {
    public static void main(String[] args) throws InterruptedException {

        //synchronized关键词
        SellTicket03 sellTicket02=new SellTicket03();
        Thread thread = new Thread(sellTicket02);
        thread.start();
        new Thread(sellTicket02).start();
        new Thread(sellTicket02).start();


    }
}

//使用synchronized
class SellTicket03 implements Runnable{
    private static int ticketNum=100; //多个线程共享
    private boolean loop=true;

    //这个代码块会被三个线程“同步访问”
    private synchronized void sell(){
        if(ticketNum<=0){
            System.out.println("售票结束....");
            loop=false;
            return;
        }

        //休眠
        try {
            Thread.sleep(10);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println("窗口"+Thread.currentThread().getName()+":售出了一张票"+"剩余票数:"+(--ticketNum));
    }

    @Override
    public void run() {
        while (loop){
            //没抢到“锁”的线程,会被阻塞在这里
            sell();
        }
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值