15 - 多线程

美人赠我金错刀,何以报之英琼瑶。

1.线程相关概念

  • 程序

为完成特定任务、用某种语言编写的一组指令的集合。(即我们写的代码)

  • 进程

进程就是运行中的程序,比如,打开QQ、或者微信,就启动了一个进程,操作系统会为该进程分配内存空间。

  • 线程

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

  • 单线程

同一时刻,只允许执行一个线程。

  • 多线程

同一个时刻,可以执行多个线程。如一个QQ进程,可以同时打开多个聊天窗口。

  • 并发

同一个时刻,多个任务交替执行,造成一种“貌似同时”的错觉,简单来说,单核cpu实现的多任务就是并发。

  • 并行

同一个时刻,多个任务同时执行,多核cpu实现。

2.线程基本使用

1. 继承 Thread 类,重写 run 方法。

2. 实现 Runnable 接口,重写 run 方法。

2.1 继承 Thread 类

代码示例

    public static void main(String[] args) throws InterruptedException
    {
        Cat cat = new Cat();
        // 启动线程-> 最终会执行 cat 的 run 方法
        cat.start();

        // 说明: 当 main 线程启动一个子线程 Thread-0, 主线程不会阻塞, 会继续执行
        // 这时主线程和子线程是交替执行..
        System.out.println("主线程继续执行" + Thread.currentThread().getName());
        for (int i = 0; i < 60; i++)
        {
            System.out.println("主线程 i=" + i);
            Thread.sleep(1000);
        }
    }

class Cat extends Thread {
    int times = 0;

    @Override
    public void run()
    { 
        //重写 run 方法,写上自己的业务逻辑
        while (true)
        {
            System.out.println("喵喵, 我是小猫咪" + (++times) + " 线程名=" + Thread.currentThread().getName());
            try
            {
                Thread.sleep(1000);
            } catch (InterruptedException e)
            {
                e.printStackTrace();
            }
            if (times == 80)
            {
                break; //当 times 到 80, 退出 while, 这时线程也就退出..
            }
        }
    }
}
    

运行输出

2.2 实现 Runnable 接口

Java是单继承的,在某些情况下一个类可能已经继承了某个父类,这时再用继承Thread类方法来创建线程显然不可能了。

因此,Java设计者提供了另外一个方式创建线程,就是通过实现Runable接口来创建线程。

代码示例

     public static void main(String[] args)
    {
        Dog dog = new Dog();
        // 创建了 Thread 对象,把 dog 对象(实现 Runnable),放入 Thread
        Thread thread = new Thread(dog);
        thread.start();
    }


class Dog implements Runnable
{
    //通过实现 Runnable 接口,开发线程
    int count = 0;

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

输出结果

  • 继承 Thread vs 实现 Runnable 的区别

本质上没有区别,Thread类本身就实现了 Runnable 接口

实现 Runnable 接口方式更加适合多个线程共享一个资源的情况,并且避免了单继承的机制,建议使用 Runnable。

3.线程终止

使用定义变量来控制停止线程

代码示例

public class ThreadTerminationExample {

    public static class MyRunnable implements Runnable {
        private volatile boolean interrupted = false;

        @Override
        public void run() {
            while (!interrupted) {
                // 模拟线程正在执行一些任务
                System.out.println("Thread is running...");
                try {
                    Thread.sleep(500); 
                } catch (InterruptedException e) {
                    // 如果线程在休眠时被中断,将中断标志设置为true
                    interrupted = true;
                    System.out.println("Thread was interrupted while sleeping.");
                }
            }
            System.out.println("Thread has terminated.");
        }

        public void requestTermination() {
            interrupted = true;
        }
    }

    public static void main(String[] args) {
        MyRunnable myRunnable = new MyRunnable();
        Thread myThread = new Thread(myRunnable);

        // 启动线程
        myThread.start();

        try {
            Thread.sleep(2000); 
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        // 请求线程终止
        myRunnable.requestTermination();

        try {
            myThread.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println("Main thread continues...");
    }
}

Java也提供了内置的中断机制

代码示例

public class ThreadInterruptionExample {

    public static class MyRunnable implements Runnable {
        @Override
        public void run() {
            while (!Thread.currentThread().isInterrupted()) {
                System.out.println("Thread is running...");
                try {
                    Thread.sleep(500); 
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt(); 
                    System.out.println("Thread was interrupted while sleeping.");
                    break; 
                }
            }
            System.out.println("Thread has terminated.");
        }
    }

    public static void main(String[] args) {
        Thread myThread = new Thread(new MyRunnable());

        // 启动线程
        myThread.start();

        try {
            Thread.sleep(2000); 
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        // 请求线程中断
        myThread.interrupt();

        try {
            // 等待线程自然终止,这里不调用myThread.join(),因为join会阻塞直到线程结束,
            // 但我们可以通过检查线程的状态或等待某个条件来判断线程是否已终止。
            while (myThread.isAlive()) {
                // 可以做一些其他工作,或者简单地等待
                Thread.sleep(100); 
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println("Main thread continues...");
    }
}

4.线程常用方法

setName:设置线程名称
getName:获取线程名称
start:使线程开始执行
run:调用线程的run方法
setPriority:更改线程的优先级
getPriority:获取线程的优先级
sleep:线程休眠
interrupt:中断线程
yield:线程礼让
join:线程插队

5.线程的生命周期

线程的几种状态

  1. 新建状态(New):当一个线程对象被创建,但还没有调用其start()方法时,它处于新建状态。此时,线程对象已经存在,但线程还没有开始执行。

  2. 就绪状态(Runnable):线程对象调用start()方法后,它进入就绪状态。这表示线程已经准备好执行,但具体是否执行取决于操作系统的调度。线程在就绪状态下可能处于“就绪”或“运行”两种子状态。

  3. 运行状态(Running):当线程获得CPU资源并开始执行其run()方法中的代码时,它处于运行状态。需要注意的是,线程只能从就绪状态进入运行状态。

  4. 阻塞状态(Blocked):线程可能因为某些原因(如等待获取锁)暂时放弃CPU使用权,并处于阻塞状态。当线程等待获取一个锁以进入或重新进入同步代码块时,它会进入阻塞状态。只有当锁被释放并且线程被调度去获取这个锁时,线程才能转换到就绪状态。

  5. 等待状态(Waiting):线程可以通过调用wait()方法或其他线程的join()方法,或者因为某些条件未满足而进入等待状态。在这种状态下,线程等待某个事件或条件发生才能继续执行。

  6. 定时等待状态(Timed Waiting):线程调用sleep()方法或带有指定时间的wait()join()方法时,会进入定时等待状态。当指定的时间过去后,线程会自动返回就绪状态。

  7. 终止状态(Terminated/Dead):当线程执行完run()方法或因异常而退出时,它进入终止状态。这意味着线程的生命周期已经结束,但线程对象仍然存在。

代码示例

   public static void main(String[] args) throws InterruptedException
    {
        T t = new T();
        System.out.println(t.getName() + " 状态 " + t.getState());
        t.start();
        while (Thread.State.TERMINATED != t.getState())
        {
            System.out.println(t.getName() + " 状态 " + t.getState());
            Thread.sleep(500);
        }
        System.out.println(t.getName() + " 状态 " + t.getState());
    }

class T extends Thread
{
    @Override
    public void run()
    {
        while (true)
        {
            for (int i = 0; i < 3; i++)
            {
                System.out.println("hi " + i);
                try
                {
                    Thread.sleep(1000);
                } catch (InterruptedException e)
                {
                    e.printStackTrace();
                }
            }
            break;
        }
    }
}

结果输出

6.线程同步

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

2. 简单来说,当有一个线程在对内存进行操作时,其它线程都不可以对这个内存地址进行操作,直到该线程完成操作,其它线程才能对该内存地址进行操作。

语法

// 同步代码块 得到对象的锁,才能操作同步代码
synchronized(对象) {    
    // 需要被同步的代码块
}


// 同步方法
public synchronized void m(String name) {
    // 需要被同步的代码
}

7.互斥锁

介绍

  • 引入对象互斥锁的概念,来保证共享数据操作的完整性。
  • 每个对象都对应与一个可称为“互斥锁”的标记。这个标记用来保证在任一时刻,只能有一个线程访问该对象。
  • 关键字 synchronized 来与对象的互斥锁联系。当某个对象用 synchronized 修饰时,表明该对象在任一时刻只能由一个线程访问。
  • 同步的局限性:导致程序的执行效率要降低。
  • 同步方法(非静态的)的锁可以是 this ,也可以是其它对象(要求是同一个对象)。
  • 同步方法(静态的)的锁为当前类本身。

代码示例

使用互斥锁来解决售票问题

    public static void main(String[] args)
    {
        SellTicket03 sellTicket03 = new SellTicket03();
        new Thread(sellTicket03).start();//第 1 个线程-窗口
        new Thread(sellTicket03).start();//第 2 个线程-窗口
        new Thread(sellTicket03).start();//第 3 个线程-窗口
    }


class SellTicket03 implements Runnable
{
    private int ticketNum = 100; //让多个线程共享 ticketNum
    private boolean loop = true; //控制 run 方法变量
    Object object = new Object();

    public /*synchronized*/ void sell()
    {
        synchronized (/*this*/ object)
        {
            if (ticketNum <= 0)
            {
                System.out.println("售票结束...");
                loop = false;
                return;
            }
            // 休眠 50 毫秒, 模拟
            try
            {
                Thread.sleep(50);
            } catch (InterruptedException e)
            {
                e.printStackTrace();
            }
            System.out.println("窗口 " + Thread.currentThread().getName() + " 售出一张票" + " 剩余票数=" + (--ticketNum));
        }
    }

    @Override
    public void run()
    {
        while (loop)
        {
            sell();//sell 方法是一共同步方法
        }
    }
}

结果输出

8.线程的死锁

介绍

多个线程都占用了对方的锁资源,但不肯相让,导致了死锁。

代码示例

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");
                }
            }
        }
    }
}

输出结果

9.释放锁        

释放锁的操作

  • 当前线程的同步方法、同步代码块执行结束。
  • 当前线程在同步代码块、同步方法中遇到break、return。
  • 当前线程在同步代码块、同步方法中出现了未处理的Error或Exception,导致异常结束。
  • 当前线程在同步代码块、同步方法中执行了线程对象的wait方法,当前线程暂停并释放锁。

不释放锁

  • 线程执行同步代码块、同步方法时,程序调用Thread.sleep()、Thread.yield()。
  • 线程执行同步代码块、同步方法时,其它线程调用了该线程的suspend()方法将该线程挂起。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值