多线程

:::tips

并发和并行

  • 并行:在同一时刻,有多个指令在多个CPU上同时执行
  • 并发:在同一时刻,有多个指令在单个CPU上交替执行

进程

  • 进程指在内存中运行的应用程序

线程

  • 线程是进程的执行单元,是CPU调度的最小单位
  • 一个进程可以由多个线程组成,线程间共享进程的所有资源
  • 一个进程如果有多个线程在执行,则称为多线程程序 :::

多线程实现

:::tips

实现多线程的三种方式

  1. 继承Thread类的方式
  2. 实现Runnable接口的方式
  3. 使用线程池的方式。 :::

Thread类

:::tips

创建步骤

  1. 自定义一个线程类继承Thread类
  2. 重写run()方法
  3. 创建自定义线程类的对象
  4. 调用start方法,启动线程。

    为什么要重写run()方法?

  • 因为run()中的代码就是线程要执行的任务

    run()方法和start()方法的区别?

  • run():封装线程要执行的任务代码,当线程启动后,会被自动调用运行

  • start():启动线程,然后由JVM调用此线程的run()方法。 :::

实现Runnable接口

:::tips

创建步骤

  • 自定义类实现Runnable接口(任务类)
  • 重写接口的run()方法
  • 创建任务类的对象
  • 创建Thread类的对象,把任务对象作为构造方法的参数
  • 调用start方法,启动线程 :::

Thread类与Runnable接口对比

:::tips

继承Thread类

  • 优点
    • 编程比较简单,子类中可以直接使用Thread类中的方法
  • 缺点
    • 扩展性较差,子类不能再继承其他的类

实现Runnable接口

  • 优点
    • 扩展性强,任务类实现接口后还可以继承其他的类
  • 缺点
    • 编程相对复杂,任务类不能直接使用Thread类中的方法 :::

线程类常用方法

:::tips

  • String getName( ) 获取当前线程名称
  • void setName(String name) 设置线程名称
  • static Thread currentThread( ) 获取当前正在执行的线程对象
  • static void sleep(long time) 让线程休眠指定的时间,单位为毫秒(休眠时让出CPU执行权)
  • void setPriority(int newPriority) 设置线程优先级
  • int getPriority( ) 获取线程优先级

    线程优先级

  • 线程优先级从低到高分别有1~10级,通常CPU会优先执行优先级较高的线程任务

  • 但这也不是绝对的,因为线程执行还是有随机性,只是概率上来说优先级越高的线程越有机会先执行 :::

线程安全问题

:::tips

概述

  • 当多个线程访问共享数据,且多个线程对共享数据有更新操作时,就容易出现线程安全问题

同步代码块

  • 格式
  1. synchronized(同步锁) {
  2. 有线程安全问题的代码
  3. }
  • 原理
    • 在多线程环境下,多个线程会抢占同步代码块中的锁对象。当一个线程获取锁,就可以成功进入同步代码块,其他线程获取不到锁,需要在同步代码块外面等待(阻塞)
    • 获得锁的线程执行完同步代码块后就会释放锁,此时所有等待的线程会重新争夺锁对象(释放锁的线程也会再次参与争夺)抢到锁的线程就可以进入同步代码块执行
  • 同步锁特点
    • 任何对象都可作为同步锁使用
    • 多个线程会对锁对象进行抢夺,所以需要保证多个线程操作的是同一把锁(锁对象需要唯一)

同步方法

  • 概述
    • 使用synchronized修饰的方法,叫做同步方法
  • 格式
  1. 修饰符 synchronized 返回值类型 方法名(参数列表) {
  2. 方法体;
  3. }
  • 作用
    • 调用方法的线程需要先抢到锁才能执行方法中的代码
  • 同步方法中的锁:
    • 实例方法(无static修饰) : 同步锁对象就是this(当前方法调用者对象)
    • 静态方法(static修饰) : 同步锁对象为当前类的Class对象(类名.class 或者 对象名.getClass() 可以获取类的字节码对象)
  • 同步代码块和同步方法区别

    • 同步方法是锁住方法中所有代码;同步代码块可以锁定指定代码,锁的控制粒度更细
    • 同步方法不能指定锁对象,同步代码块可以指定锁对象

      Lock锁机制

  • 概述

  • JDK1.5开始,并发包(java.util.concurrent)中新增了Lock接口和相关实现类来实现锁的功能,它提供了与synchronized关键字类似的同步功能。
  • Lock中提供了获得锁和释放锁的方法:
    • void lock():获得锁
    • void unlock():释放锁 ::: ```java / 自定义一个线程类继承Thread类 重写run()方法 创建自定义线程类的对象 调用start方法,启动线程。 / public static void main(String[] args) { MyThread myThread = new MyThread(); //开启线程 myThread.start(); for (int i = 0; i < 10; i++) { System.out.println(“main: “ + i); } }

public class MyThread extends Thread{ @Override public void run() { for (int i = 0; i < 10; i++) { System.out.println(“Thread: “ + i); } } }

  1. ```java
  2. /*
  3. 目标:实现Runnable接口创建线程
  4. 步骤:
  5. 1.定义一个类(任务类)实现Runnable接口。
  6. 2.重写接口的run()方法。
  7. 3.创建任务类的对象。
  8. 4.创建Thread类的对象,把任务对象作为构造方法的参数。
  9. 5.调用start方法,启动线程。
  10. */
  11. public class DemoRunnable {
  12. public static void main(String[] args) {
  13. //创建任务类对象
  14. DemoMyRunnable runnable = new DemoMyRunnable();
  15. //创建线程
  16. Thread thread = new Thread(runnable);
  17. //启动线程
  18. thread.start();
  19. for (int i = 0; i < 10; i++) {
  20. System.out.println("main:" + i);
  21. }
  22. }
  23. }
  24. //任务类
  25. public class DemoMyRunnable implements Runnable{
  26. @Override
  27. public void run() {
  28. for (int i = 0; i < 10; i++) {
  29. System.out.println("Runnable: " + i);
  30. }
  31. }
  32. }
//获取当前正在执行的线程对象:Thread.currentThred()
public class DemoRunnable {
    public static void main(String[] args) {
        //任务类
        MyRunnable myRun = new MyRunnable();
        //创建线程
        Thread thread = new Thread(myRun);
        //设置名字
        thread.setName("Runnable");
        //启动线程
        thread.start();
    }
}

class MyRunnable implements Runnable {
    @Override
    public void run() {
        //获取当前正在执行的线程对象
        Thread thread = Thread.currentThread();
        //获取当前线程名称
        String name = thread.getName();
        System.out.println(name + "线程正在执行");
    }
}
public static void main(String[] args) {
    //重写Runnabl接口匿名内部类
    Thread t1 = new Thread(new Runnable() {
        @Override
        public void run() {
            for (int i = 0; i < 10; i++) {
                System.out.println("t1: " + i);
            }
        }
    });
    Thread t2 = new Thread(() -> {
        for (int i = 0; i < 10; i++) {
            System.out.println("t2: " + i);
        }
    });
    //获取优先级
    System.out.println("t1线程默认优先级" + t1.getPriority());
    System.out.println("t2线程默认优先级" + t2.getPriority());
    //设置优先级
    t1.setPriority(1);
    t2.setPriority(10);
    //启动线程
    t1.start();
    t2.start();
}
public static void main(String[] args) throws InterruptedException {
    for (int i = 0; i < 5; i++) {
        Date date = new Date();
        System.out.println("时间" + date);
        Thread.sleep(1000);
    }
}
public static void main(String[] args) {
    //创建任务类
    //Ticket02 ticket = new Ticket02();
    Ticket03 ticket = new Ticket03();
    //创建线程。并命名
    Thread thread1 = new Thread(ticket, "window_1");
    Thread thread2 = new Thread(ticket, "window_2");
    Thread thread3 = new Thread(ticket, "window_3");
    //启动线程
    thread1.start();
    thread2.start();
    thread3.start();
}

//synchronized
public class Ticket02 implements Runnable{
    //定义成员变量,表示总票数
    private static int total = 100;
    //实现卖票业务
    @Override
    public void run() {
        while (true) {
            //锁的对象可以是任意的java对象,前提是保证唯一
            synchronized (Ticket02.class) {

                if (total > 0) {
                    total--;
                    String name = Thread.currentThread().getName();
                    System.out.println("[" + name + "]卖票成功,当前剩余:" +
                            total + "张");
                } else {
                    break;//没有票结束循环
                }

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

//lock
public class Ticket03 implements Runnable {
    private static int ticket = 10;
    Lock lock = new ReentrantLock();

    @Override
    public void run() {

        Thread thread = Thread.currentThread();
        String name = thread.getName();
        while (true) {

            try {
                //获得锁
                lock.lock();
                if (ticket > 0) {
                    System.out.println(name + "窗口售票成功,现在余:" + (--ticket));
                } else {
                    break;
                }
            } finally {
                //释放锁,每条进锁线程必须执行,不然程序不会停止
                lock.unlock();
            }
            //线程休眠模拟消耗
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

    }
}