【JavaSE】多线程基础-概述

多线程基础

1. 程序、进程&线程

  1. 程序:为完成某个特定任务,用某种语言编写的一组指令集合,即一段静态的代码段。
  2. 进程:即运行的程序就是进程,进程可以理解为动态的程序,是操作系统分配和调用资源的最小单位。
  3. 线程:进程进一步细化则是线程,线程可以理解为进程中的一条工作路径,比如点开某个软件(相当于开启了一个进程),同时开启该软件中的多个功能,相当于开启了多个线程,如百度网盘中,同时开启多个下载任务,即相当于开启了多个线程。
  4. 进程和线程之间的关系:只有当一个进程中的所有线程都结束了,这个进程才会结束。

Alt

2. 并行和并发

  1. 并行:同一时刻,多个任务交替执行。单个cpu执行多项任务,即cpu在多个任务间快速切换执行,形成貌似在同时执行多个任务的样子。
  2. 并发:同一时刻,多个任务同时实行。多个cpu在同一时刻各自执行自己的任务。
  3. 说明:并行中也可能包含并发,如开启了后台很多任务时,多个并行的cpu也需要交替执行多个任务(并发)。

3. 开启线程的底层方法

Thread类中的run方法只是一个普通的方法,真正开启线程的方法是Thread类中的start()方法中的本地方法start0(),该方法由虚拟机调用,涉及到操作的系统的IO资源等的调用。

public synchronized void start() {
        /**
         * This method is not invoked for the main method thread or "system"
         * group threads created/set up by the VM. Any new functionality added
         * to this method in the future may have to also be added to the VM.
         *
         * A zero status value corresponds to state "NEW".
         */
        if (threadStatus != 0)
            throw new IllegalThreadStateException();

        /* Notify the group that this thread is about to be started
         * so that it can be added to the group's list of threads
         * and the group's unstarted count can be decremented. */
        group.add(this);

        boolean started = false;
        try {
            start0();
            started = true;
        } finally {
            try {
                if (!started) {
                    group.threadStartFailed(this);
                }
            } catch (Throwable ignore) {
                /* do nothing. If start0 threw a Throwable then
                  it will be passed up the call stack */
            }
        }
    }

4. 创建线程的两种方式

  1. 继承Thread类,实现run()方法,调用start()方法开启线程:

    public class Thread01 {
        public static void main(String[] args) {
            Dog dog = new Dog();
            dog.start();
        }
    }
    
    
    class Dog extends Thread {
    
        int time = 0;
    
        @Override
        public void run() {
    
            while (true) {
                try {
                    Thread.sleep(1000);
                    System.out.println("旺旺旺" + ++time);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
    
            }
    
        }
    
    
    }
    
  2. 实现Runnable接口,实现run()方法,通过Thread类静态代理,开启线程:

    public class Thread02 {
    
        public static void main(String[] args) {
            Computer computer = new Computer();
            //通过Thread类静态代理,开启线程
            Thread thread = new Thread(computer);
            thread.start();
            System.out.println("main .... ");
        }
    
    }
    
    class Computer implements Runnable{
        @Override
        public void run() {
            try {
                Thread.sleep(3000);
                System.out.println(Thread.currentThread().getName() + " " +"begin to running");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
    
    

5. 线程终止

让线程终止一般有两种方式:

  1. 线程执行完任务(代码语句)后,会自动结束线程;

  2. 通知线程结束,如通过控制一个变量结束线程中的run方法,即可终止线程,demo如下:

    public class ThreadExit {
        public static void main(String[] args) throws InterruptedException {
            Thread01 thread01 = new Thread01();
    
            //开启线程
            thread01.start();
    
            //让主线程沉睡,给线程多一些运行的时间
            Thread.sleep(20 * 1000);
    
            //通过修改变量,控制线程退出--通知方式
            thread01.setLoop(false);
    
        }
    
    }
    
    
    class Thread01 extends Thread {
    
        int time = 0;
    
        //设置变量,控制线程退出
        private boolean loop = true;
    
    
        @Override
        public void run() {
    
            while (loop) {
                try {
                    Thread.sleep(50);
                    System.out.println("run..." + ++time);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
    
            }
    
            System.out.println(Thread.currentThread().getName() + ": 线程终止");
    
        }
    
        public void setLoop(boolean loop) {
            this.loop = loop;
        }
    }
    

6. Thread类中常用的API

方法描述
start()用于启动线程并调用run()方法,使线程进入就绪状态。
run()定义了线程的操作,会在新线程中执行。
sleep(long millis)使当前线程暂停执行一段时间,让其他线程有机会继续执行。
join()允许一个线程等待另一个线程完成其执行。
interrupt()用于中断正在执行的线程,向线程发送一个中断信号,一般用于中断线程的休眠。
isAlive()用于检查线程是否还活着。
setPriority(int priority)用于设置线程的优先级。
yield()使当前线程放弃CPU并允许其他线程运行,当系统资源较充足的时候,可能会放弃失败。

Demo: 中断线程休眠

public class ThreadMethod01 {
    public static void main(String[] args) throws InterruptedException{
        //创建一个线程对象
        Eat eat = new Eat();

        //设置线程名字
        eat.setName("Amin");

        //设置线程优先级
        eat.setPriority(Thread.MIN_PRIORITY);

        //开启线程
        eat.start();

        //主线程通知Eat线程中断睡眠
        for (int i = 0; i < 3; i++) {
            Thread.sleep(1000);
            System.out.println("Hi " + eat.getName());
        }

        //中断线程
        eat.interrupt();

    }

}

class Eat extends Thread{

    int times = 0;

    @Override
    public void run() {
        while (true){

            for (int i = 0; i < 20; i++) {
                System.out.println(Thread.currentThread().getName() + "吃饺子..." + i);
            }

            //线程沉睡
            try {
                Thread.sleep(20 * 1000);
            } catch (InterruptedException e) {
                System.out.println(Thread.currentThread().getName() + " is interrupted ...");
            }


        }
    }
}

demo2: 协调线程

public class ThreadMethod02 {
    public static void main(String[] args) throws InterruptedException {
        //创建线程对象
        ThreadHighLevel threadHighLevel = new ThreadHighLevel();

        threadHighLevel.start();

        for (int i = 0; i < 20; i++) {
            Thread.sleep(1000);
            System.out.println("main:" + "hi" + " -> " + i);
            if (i == 4){
                System.out.println("开始暂停...");
                //直接让线程插队
                threadHighLevel.join();
                //根据系统资源是否充足,礼让别的线程(让出cpu)
                //Thread.yield();
            }


        }
    }
}

class ThreadHighLevel extends Thread{

    int times = 0;

    @Override
    public void run() {
        while (times < 20){
            try {
                Thread.sleep(1000);
                System.out.println(Thread.currentThread().getName() + ":hello" + times);
                times++;
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

        }


    }
}

7. 多线程可能出现的问题

同步互斥问题

线程同步,即多个线程操作同一内存(数据)时,同一时刻,只允许一个线程对数据进行修改,该线程退出后,才能由下一个线程进行修改。

  1. 多线程同步导致数据异常,即多线程操作同一内存,导致数据发生异常。
public class BookingOffice {
    public static void main(String[] args) {
        //模拟互斥同步现象

        //创建3个线程,即同时售卖票
        SellingTicket01 sellingTicket01 = new SellingTicket01();
        SellingTicket01 sellingTicket02 = new SellingTicket01();
        SellingTicket01 sellingTicket03 = new SellingTicket01();

        //开启线程
        sellingTicket01.start();
        sellingTicket02.start();
        sellingTicket03.start();

    }
}


class SellingTicket01 extends Thread{

    //创建静态变量,让多个线程共享,--票剩余数量
    public static Integer ticketNumber = 100;

    @Override
    public void run() {
        //模拟买票的场景
        try {
            while (true){
                //1. 判断票数是否为0
                if (ticketNumber == null || ticketNumber <= 0){
                    System.out.println("售票结束...");
                    break;
                }

                Thread.sleep(50);

                //2. 开始售出票
                System.out.println(Thread.currentThread().getName() + ":售出一张票, " + "剩余票数为:" + (--ticketNumber));

            }


        } catch (InterruptedException e) {
            e.printStackTrace();
        }




    }
}
  1. 超卖现象

Alt

  1. 原因分析

比如票数只剩余1张或者两张的这一时刻,三个线程同时通过了售空退出循环的判断,导致三个线程都能够对票数–,出现负数。

  1. 解决方案:
    可以在需要同步的代码块或者方法上添加sychronized关键字,即给代码块或者方法添加了一个对象互斥锁(通过改变同一对象的某个位实现加锁)。

    • 代码块:可以是静态代码块和普通代码块,前者需要绑定当前类,后者需要绑定同一对象(Object类对象都可以),绑定代码块的使用场景更广泛,因为代码块限制的范围更小,同步代码越少,程序执行效率一般会更高。
    • 方法:可以是静态方法或者是实例方法,前者需要绑定当前类,后者需要绑定同一对象(Object类对象都可以)。

    抽离出买票代码,封装成实例方法,添加synchronized关键字,设置为同步方法

    public class BookingOffice {
        public static void main(String[] args) {
            //模拟互斥同步现象
    
          /*  //创建3个线程,即同时售卖票
            SellingTicket01 sellingTicket01 = new SellingTicket01();
            SellingTicket01 sellingTicket02 = new SellingTicket01();
            SellingTicket01 sellingTicket03 = new SellingTicket01();
    
            //开启线程
            sellingTicket01.start();
            sellingTicket02.start();
            sellingTicket03.start();*/
    
            //实现Runnable接口开启线程,目的为让三个线程调用同一个run方法
            SellingTicket02 sellingTicket021 = new SellingTicket02();
    
    
            new Thread(sellingTicket021).start();
            new Thread(sellingTicket021).start();
            new Thread(sellingTicket021).start();
    
    
        }
    }
    
    
    class SellingTicket01 extends Thread{
    
        //创建静态变量,让多个线程共享,--票剩余数量
        public static Integer ticketNumber = 100;
    
        @Override
        public void run() {
            //模拟买票的场景
            try {
                while (true){
                    //1. 判断票数是否为0
                    if (ticketNumber == null || ticketNumber <= 0){
                        System.out.println("售票结束...");
                        break;
                    }
    
                    Thread.sleep(50);
    
                    //2. 开始售出票
                    System.out.println(Thread.currentThread().getName() + ":售出一张票, " + "剩余票数为:" + (--ticketNumber));
    
                }
    
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
    
    
        }
    }
    
    class SellingTicket02 implements Runnable{
    
        //使用的为同一个对象,不需要创建静态变量,让多个线程共享,--票剩余数量
        public Integer ticketNumber = 100;
    
        public boolean loop = true;
    
        public synchronized void sell(){
            //模拟买票的场景
            try {
                    //1. 判断票数是否为0
                    if (ticketNumber == null || ticketNumber <= 0){
                        System.out.println("售票结束...");
                        loop = false;
                        return;
                    }
    
                    Thread.sleep(50);
    
                    //2. 开始售出票
                    System.out.println(Thread.currentThread().getName() + ":售出一张票, " + "剩余票数为:" + (--ticketNumber));
    
    
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    
    
        /**
         * 不能直接在run方法上面添加synchronize关键字,因为run方法中while中的代码才是执行逻辑,直接添加会导致只有一个线程能够进行买票。
         */
        @Override
        public void run() {
            while (loop){
                sell();
            }
            
        }
    }
    

    Demo

     //1. 同步静态代码块
        static{
            synchronized (SellingTicket02.class){
                System.out.println("同步静态代码块");
            }
        }
    
    
        public void m1(){
            //2. 同步代码块,注意在方法中才可以使用,这里使用的this关键字,也可以替换成其它对象
            synchronized (this){
                System.out.println("同步代码块");
            }
            
        }
        
        //3. 静态同步方法
        public synchronized static void m2(){
            System.out.println("静态同步方法");
        }
        
        //4. 同步实例方法
        public synchronized void m3(){
            System.out.println("同步实例方法");
        }
    

    死锁

    ​ 在多线程中,可能会出现两个线程互相等待获取对方资源的场景,即一个线程优先拿到了一个锁A,另一个线程拿到了锁B,而第一个线程需要再获取一个资源(这个资源需要锁B)才能进行下一步,第二个线程也需要再获取一个资源(这个资源需要锁A)才能进行下一步,从而出现两个线程相互等待,导致整个程序无法执行下一步。

    • 出现场景:一般出现在某些线程需要获取多把锁的情况。

    Demo(老韩)

    public class DeadBlock01 {
        public static void main(String[] args) {
            //模拟死锁现象
            DeadLockDemo A = new DeadLockDemo(true);
            A.setName("A 线程");
            DeadLockDemo B = new DeadLockDemo(false);
            B.setName("B 线程");
            A.start();
            B.start();
        }
    }
    //线程
    
    class DeadLockDemo extends Thread {
        static Object o1 = new Object();// 保证多线程,共享一个对象,这里使用 static
        static Object o2 = new Object();
        boolean flag;
    
        public DeadLockDemo(boolean flag) {//构造器
            this.flag = flag;
        }
    
        @Override
        public void run() {
            //下面业务逻辑的分析
            //1. 如果 flag 为 T, 线程 A 就会先得到/持有 o1 对象锁, 然后尝试去获取 o2 对象锁
            //2. 如果线程 A 得不到 o2 对象锁,就会 Blocked
            //3. 如果 flag 为 F, 线程 B 就会先得到/持有 o2 对象锁, 然后尝试去获取 o1 对象锁
            //4. 如果线程 B 得不到 o1 对象锁,就会 Blocked
            if (flag) {
                synchronized (o1) {//对象互斥锁, 下面就是同步代码
                    System.out.println(Thread.currentThread().getName() + " 进入 1");
                    synchronized (o2) { // 这里获得 li 对象的监视权
                        System.out.println(Thread.currentThread().getName() + " 进入 2");
                    }
                }
            } else {
    
                synchronized (o2) {
                    System.out.println(Thread.currentThread().getName() + " 进入 3");
                    synchronized (o1) { // 这里获得 li 对象的监视权
                        System.out.println(Thread.currentThread().getName() + " 进入 4");
                    }
                }
            }
        }
    }
    

8. 用户线程&守护线程

  1. 用户线程:即工作线程,上述通过Thread类创建的普通线程就是工作线程;

  2. 守护线程:守护线程是为工作线程服务的,守护线程的特点就是,当所有用户进程结束之后,守护线程会自动退出。

    • 常见的守护线程应用场景:监控线程、垃圾回收机制…

    • 使用方法:调用daemon()方法设置为true,即可将调用方的线程设置为守护线程。

       //创建线程对象
              ThreadHighLevel threadHighLevel = new ThreadHighLevel();
                  
              //注意设置守护线程,需要在开启线程之前(即调用start方法之前)
              threadHighLevel.setDaemon(true);
              
              threadHighLevel.start();
      

9. 线程的生命周期(虚拟机)

在Java虚拟机中,线程的生命周期可以分为七个(注意是在虚拟机中,并非是操作系统中关于线程的所有状态)。

  1. NEW:即新建状态,对应到代码阶段,就是创建了一个线程对象(Thread类)。
  2. RUNNABLE:即可运行状态,这个状态可以细分为两个状态,这两个状态之间的切换设计到底层的操作系统,即内核态关于线程的调度。
    • Ready:即就绪态,这时线程并没有真正开始运行。
    • running:即运行态,这时候,线程开始真正地运行。
  3. TIMED_WAITING:指定一个时间段,一般为等待另一个线程完成一些特定的操作,然后重新进入RUNNABLE状态。
  4. WAITING:线程无限期地等待。
  5. BLOCK:即线程的阻塞状态,这里涉及到加锁等方面的内容。
  6. TERMINATED:即线程处于终止状态。

状态图(韩老师)

Alt


Demo:查看线程的一些状态

public class ThreadState {
    public static void main(String[] args) throws InterruptedException{
        ThreadStateExample threadStateExample = new ThreadStateExample();

        Thread thread = new Thread(threadStateExample);
        //查看线程状态
        System.out.println(thread.getName() + "'s state: " + thread.getState());

        //开启线程
        thread.start();
        System.out.println(thread.getName() + "'s state: " + thread.getState());


        while (thread.getState() != Thread.State.TERMINATED){
            //main线程沉睡
            Thread.sleep(500);
            System.out.println(thread.getName() + "'s state: " + thread.getState());
        }


    }
}


class ThreadStateExample implements Runnable{
    @Override
    public void run() {

        int times = 20;

        while (times > 0){
            
            try {
                System.out.println(Thread.currentThread().getName() + ": 吃饭、睡觉、打豆豆");
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            times--;

        }


    }
}

10. 释放锁

释放锁,一般是指线程拿到锁,使用完资源之后,归还锁,让该资源能够让别的线程使用。

场景

  • 线程使用完资源:同步代码块执行完(或者遇到break、return关键字、出现error和异常)。
  • 执行了线程对象中的wait()方法。

细节

  • 线程睡眠不会释放锁
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值