Java学习笔记之多线程

1 进程与线程

  • 程序:指令和数据的有序集合,本身无任何运行的含义,是静态的概念

  • 进程:执行程序的一次执行过程,是一个动态的概念,时系统资源分配的单位

  • 线程:CPU 调度和执行的单位,一个进程中可以包含多个线程,其本身就是独立的执行路径

  • 在一个进程中,若开辟了多个线程,则运行顺序是由 CPU 中的调度器安排的,人为无法干预
  • 对统一资源进行操作时,会存在资源抢夺的问题,需要加入并发控制
  • 线程会带来额外的开销,内存控制不当会造成数据不一致

2 线程的创建

2.1 创建方式
1 继承 Thread 类
public class ThreadInfo extends Thread{
    @Override
    public void run() {
        for (int i = 0; i < 200; i++) {
            System.out.println("这是多线程" + i);
        }
    }

    public static void main(String[] args) {
        ThreadInfo threadInfo = new ThreadInfo();
        
        threadInfo.start();

        for (int i = 0; i < 1000; i++) {
            System.out.println("主进程" + i);
        }
    }
}

执行结果

线程例子

结论:线程开启不一定立即执行,由 CPU 进行调度

2 实现 Runnable 接口
public class ThreadInfo2 implements Runnable{

    public static void main(String[] args) {
        ThreadInfo threadInfo = new ThreadInfo();
        
        //推荐使用,避免 Java 单继承的局限性
        new Thread(threadInfo).start();

        for (int i = 0; i < 1000; i++) {
            System.out.println("主进程" + i);
        }
    }

    @Override
    public void run() {
        for (int i = 0; i < 200; i++) {
            System.out.println("多线程" + i);
        }
    }
}

多线程同时使用同一个资源的情况下,线程不安全,会导致数据紊乱

以抢车票为例

public class ThreadInfo3 implements Runnable{

    private int ticketNum = 10;

    @Override
    public void run() {
        while (ticketNum > 0){
            try {
                Thread.sleep(200);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + "拿到了第" + ticketNum-- + "张票");
        }
    }

    public static void main(String[] args) {
        ThreadInfo3 ticket = new ThreadInfo3();

        new Thread(ticket, "A").start();
        new Thread(ticket, "B").start();
        new Thread(ticket, "C").start();
        new Thread(ticket, "D").start();
    }
}

可以看到,B 和 D 同时抢到了同一张票,发生了数据紊乱

抢车票例子

龟兔赛跑例子

public class Race implements Runnable{

    private static String winner;

    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {

            boolean flag = isGameOver(i);
            if (flag) break;

            System.out.println(Thread.currentThread().getName() + "到了第" + i + "米");
        }
    }
    
    private boolean isGameOver(int steps){
        if (winner != null) return true;
        if (steps == 99){
            winner = Thread.currentThread().getName();
            System.out.println("winner is " + winner);
            return true;
        }
        return false;
    }

    public static void main(String[] args) {
        Race race = new Race();

        new Thread(race, "兔子").start();
        new Thread(race, "乌龟").start();

    }
}
3 实现 Callable 接口
public class ThreadInfo4 implements Callable<Boolean> {
    @Override
    public Boolean call(){
        for (int i = 0; i < 5; i++) {
            System.out.println("多线程" + i);
        }
        return true;
    }

    public static void main(String[] args) throws Exception {
        ThreadInfo4 info1 = new ThreadInfo4();
        ThreadInfo4 info2 = new ThreadInfo4();
        ThreadInfo4 info3 = new ThreadInfo4();

        //创建执行服务
        ExecutorService ser = Executors.newFixedThreadPool(3);

        //提交执行
        Future<Boolean> res1 = ser.submit(info1);
        Future<Boolean> res2 = ser.submit(info2);
        Future<Boolean> res3 = ser.submit(info3);

        //获取结果
        boolean r1 = res1.get();
        boolean r2 = res2.get();
        boolean r3 = res3.get();

        //关闭服务
        ser.shutdown();
    }
}

2 静态代理

  • 代理对象和真是对象都要实现同一个接口
  • 代理对象要代理真实角色
  • 代理对象可以实现很多真实对象无法实现的功能
  • 使用代理后,真实对象可以专注于自己的事
public class ProxyStatic {

    public static void main(String[] args) {

        new MarryCompany(new You()).happyMarry();
    }
}

interface Marry{
    void happyMarry();
}

class You implements Marry{
    @Override
    public void happyMarry() {
        System.out.println("今天结婚了,很开心");
    }
}

//婚庆公司就是一个代理对象
class MarryCompany implements Marry {

    private final Marry target;

    public MarryCompany(Marry target) {
        this.target = target;
    }

    @Override
    public void happyMarry() {
        before();
        this.target.happyMarry();	//真实对象
        after();
    }

    public void before(){
        System.out.println("结婚之前,布置现场");
    }

    public void after(){
        System.out.println("结婚之后,算流水");
    }
}

3 Lambda 表达式

  • 为什么使用 Lambda 表达式

    • 为了避免匿名内部类定义过多
    • 代码看起来更简洁
    • 去掉了无意义代码,只留下核心
  • 函数式接口定义

    • 任何接口,如果只包含唯一一个抽象方法,那么就是一个函数式接口
    • 对于函数式接口,就可以通过 lambda 表达式来创建该接口的对象
    (params) -> expression[表达式]
    (params) -> statement[语句]
    (params) -> {statements}
    

问:平时实现函数式接口,怎么实现?

答:外部类 -> 静态内部类 -> 局部内部类 -> 匿名内部类(依次简化)

  • 使用外部类实现
public class LambdaInfo {
    public static void main(String[] args) {
        MyLike myLike = new MyLike();
        myLike.lambda();
    }
}

interface ILike {
    void lambda();
}

class MyLike implements ILike{
    @Override
    public void lambda() {
        System.out.println("i like lambda");
    }
}
  • 简化,使用静态内部类实现
public class LambdaInfo {
    public static void main(String[] args) {
        MyLike myLike = new MyLike();
        myLike.lambda();
    }

    static class MyLike implements ILike{
        @Override
        public void lambda() {
            System.out.println("i like lambda");
        }
    }
}

interface ILike {
    void lambda();
}
  • 再简化,使用局部内部类实现
public class LambdaInfo {
    public static void main(String[] args) {
        class MyLike implements ILike{
            @Override
            public void lambda() {
                System.out.println("i like lambda");
            }
        }

        MyLike myLike = new MyLike();
        myLike.lambda();
    }
}

interface ILike {
    void lambda();
}
  • 再简化,使用匿名内部类实现
public class LambdaInfo {
    public static void main(String[] args) {
        ILike like = new ILike() {
            @Override
            public void lambda() {
                System.out.println("i like lambda");
            }
        };

        like.lambda();
    }
}

interface ILike {
    void lambda();
}
  • 更加简化,使用 lambda 表达式
public class LambdaInfo {
    public static void main(String[] args) {
        ILike like = () -> {
            System.out.println("i like lambda");
        };
        like.lambda();
    }
}

interface ILike {
    void lambda();
}
  • 当 lambda 表达式内部是多行代码时,大括号不能去掉,要用大括号包裹代码块
public class LambdaInfo {
    public static void main(String[] args) {
        ILike like = (a, b, c) -> {
            System.out.println("i like lambda" + a);
            System.out.println("i like lambda" + b + c);
        };
        like.lambda(1, true, "");
    }
}

interface ILike {
    void lambda(int a, boolean b, String c);
}
  • 当 lambda 表达式内部只有一行代码时,可以省略大括号
public class LambdaInfo {
    public static void main(String[] args) {
        ILike like = (a, b, c) -> System.out.println("i like lambda" + a);
        like.lambda(1, true, "");
    }
}

interface ILike {
    void lambda(int a, boolean b, String c);
}
  • lambda 表达式的参数类型,要么都要留,要么都省略
public class LambdaInfo {
    public static void main(String[] args) {
        //都要留
        ILike like1 = (int a, boolean b, String c) -> System.out.println("i like lambda" + a + b + c);
        //都不留
        ILike like = (a, b, c) -> System.out.println("i like lambda" + a + b + c);
        
        like1.lambda(1, true, "");
        like2.lambda(1, true, "");
    }
}

interface ILike {
    void lambda(int a, boolean b, String c);
}

4 线程状态

4.1 五大状态
  • 创建状态:Thread t = new Thread(); 一旦被创建,就进入了创建状态

  • 就绪状态:当调用 start() 方法时,线程立即进入就绪状态,但不意味着立即调度执行

  • 运行状态:CPU 调用该线程,则进入运行状态

  • 阻塞状态:运行途中遇到 sleep()、wait() 等方法时,线程进入阻塞状态,当阻塞事件结束后,CPU 重新调用该线程,再次进入运行状态

  • 死亡状态:线程中断或者结束,就进入了死亡状态,无法再次启动

    线程五大状态

4.2 停止线程
  • 不推荐使用 jdk 提供的 stop()destory() ,已经废弃
  • 推荐方式:设置一个标志位来终止线程
public class StopThread implements Runnable{

    private boolean flag = true;

    @Override
    public void run() {
        int count = 0;
        while (flag){
            System.out.println("run...Thread" + count++);
        }
    }

    public void myStop(){
        this.flag = false;
    }

    public static void main(String[] args) {
        StopThread stopThread = new StopThread();
        new Thread(stopThread).start();

        for (int i = 0; i < 300; i++) {
            System.out.println("main...Thread" + i);
            if (i == 168){
                stopThread.myStop();
                System.out.println("线程已经终止");
            }
        }
    }
}
4.3 线程休眠
  • sleep(int times) 指定当前线程阻塞的毫秒数
  • sleep 存在异常 Interruptedexception
  • sleep 时间达到后线程进入就绪状态
  • sleep 可以模拟网络延时,倒计时等
  • 每一个对象都有一个锁, sleep 不会释放锁
public class SleepThread implements Runnable{

    @Override
    public void run() {
        int num = 10;
        Date startTime = new Date(System.currentTimeMillis());
        while (num-- > -1){
            System.out.println(new SimpleDateFormat("HH:mm:ss").format(startTime));
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            startTime = new Date(System.currentTimeMillis());
        }
    }

    public static void main(String[] args) {
        new SleepThread().run();
    }
}
4.4 线程礼让
  • 礼让线程,让当前正在执行的线程暂停但不阻塞
  • 将线程从运行状态转为就绪状态
  • 让 CPU 重新调度,礼让不一定成功
public class YieldThread{

    public static void main(String[] args) {
        MyYield myYield = new MyYield();
        new Thread(myYield, "a").start();
        new Thread(myYield, "b").start();
    }
}

class MyYield implements Runnable{

    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName() + "线程开始执行");
        Thread.yield();
        System.out.println(Thread.currentThread().getName() + "线程停止执行");
    }
}
4.5 线程强制执行
  • Join 合并线程,等待此线程执行完,其他线程才可继续执行,该线程执行时,其他线程阻塞
  • 类似于插队(尽量少使用,会让线程阻塞)
public class JoinThread implements Runnable{
    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            System.out.println("线程 vip 插队了" + i);
        }
    }

    public static void main(String[] args) throws InterruptedException {
        JoinThread joinThread = new JoinThread();
        Thread thread = new Thread(joinThread);
        thread.start();

        for (int i = 0; i < 10; i++) {
            if (i == 3) {
                thread.join();
            }
            System.out.println("主线程" + i);
        }
    }
}
4.6 查看线程状态
  • NEW:尚未启动的线程处于此状态
  • RUNNABLE:在 Java 虚拟机中执行的线程处于此状态
  • BLOCKED:被阻塞等待监视器锁定的线程处于此状态
  • WAITING:正在等待另一个线程执行特定动作的线程处于此状态
  • TIMED_WAITING:正在等待另一个线程执行动作达到指定等待时间的线程处于此状态
  • TERMINATED:已退出的线程处于此状态
public class StatusThread{

    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(() -> {
            for (int i = 0; i < 2; i++) {
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println("Thread Finished...");
        });

        Thread.State state = thread.getState();
        System.out.println(state);

        thread.start();
        state = thread.getState();
        System.out.println(state);

        while (state != Thread.State.TERMINATED){
            Thread.sleep(100);
            state = thread.getState();
            System.out.println(state);
        }
    }
}
4.7 线程优先级
  • Java 提供一个线程调度器来监控程序中启动后进入就绪状态的所有线程,线程调度器按照优先级决定应该调度哪个线程来执行
  • 线程的优先级用数字表示,范围从 1~10
    • Thread. MIN_PRIORITY =1
    • Thread. MAX_PRIORITY= 10
    • Thread. NORM_PRIORITY =5
  • 使用 getpriority()setpriority(int num) 方式改变或获取优先级
  • 线程必须先设置优先级,然后再启动
  • 线程优先级低,并不意味着获得调度的概率低,并不是优先级低就不会被调用
public class PriorityThread {

    public static void main(String[] args) {
        System.out.println(Thread.currentThread().getName() + "->" + Thread.currentThread().getPriority());
        MyPriority myPriority = new MyPriority();
        Thread t1 = new Thread(myPriority);
        Thread t2 = new Thread(myPriority);
        Thread t3 = new Thread(myPriority);
        Thread t4 = new Thread(myPriority);
        Thread t5 = new Thread(myPriority);

        t1.setPriority(Thread.MIN_PRIORITY);
        t1.start();

        t2.setPriority(Thread.NORM_PRIORITY);
        t2.start();

        t3.setPriority(7);
        t3.start();

        t4.setPriority(3);
        t4.start();

        t5.setPriority(Thread.MAX_PRIORITY);
        t5.start();
    }
}

class MyPriority implements Runnable{

    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName() + "->" + Thread.currentThread().getPriority());
    }
}
4.8 守护线程
  • 线程分为用户线程和守护线程
  • 虚拟机必须确保用户线程(main 线程等)执行完毕
  • 虚拟机不用等待守护线程(gc 垃圾回收线程等)执行完毕
public class DaemonThread {

    public static void main(String[] args) {
        God god = new God();
        My my = new My();

        Thread thread = new Thread(god);
        thread.setDaemon(true);
        thread.start();

        new Thread(my).start();
    }
}

class God implements Runnable {

    @Override
    public void run() {
        while (true) System.out.println("荒天帝守护着你");
    }
}

class My implements Runnable {

    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            System.out.println("You Living -> " + i );
        }
        System.out.println("You Dead!");
    }
}

5 线程同步

  • 并发:多个线程同时操作同一个资源

  • 为了保证数据在方法中被访可时的正确性,在访问时加入锁机制 synchronized ,当一个线程获得对象的排它锁,独占资源,其他线程必须等待使用后释放锁即可。存在以下问题:

    • 一个线程持有锁会导致其他所有需要此锁的线程挂起
    • 在多线程竟争下,加锁,释放锁会导致比较多的上下文切換和调度延时,引起性能冋题
    • 如果一个优先级高的线程等待一个优先级低的线程释放锁,会导致优先级倒置,引起性能问题

三大不安全案例

  • 买票
  public class UnsafeTickets {
      public static void main(String[] args) {
          Tickets tickets = new Tickets();
          new Thread(tickets, "你").start();
          new Thread(tickets, "我").start();
          new Thread(tickets, "他").start();
      }
  }
  
  class Tickets implements Runnable {
  
      int ticketNum = 10;
      boolean flag = true;
  
      @Override
      public void run() {
          while (flag) {
              try {
                  buy();
              } catch (InterruptedException e) {
                  e.printStackTrace();
              }
          }
      }
  
      public void buy() throws InterruptedException {
          if (ticketNum < 0){
              flag = false;
              return;
          }
  
          Thread.sleep(1000);
          System.out.println(Thread.currentThread().getName() + "买到了第" + ticketNum-- + "张票" );
      }
  }
  • 银行取钱
  public class UnsafeBank {
      public static void main(String[] args) {
          Account account = new Account(100, "存款");
  
          new Bank(account, 50, "You").start();
          new Bank(account, 100, "Girl").start();
      }
  }
  
  class Account{
      int money;
      String name;
  
      public Account(int money, String name) {
          this.money = money;
          this.name = name;
      }
  }
  
  class Bank extends Thread {
      Account account;
      int drawingMoney;
      int nowMoney;
  
      public Bank(Account account, int drawingMoney, String name){
          super(name);
          this.account = account;
          this.drawingMoney = drawingMoney;
      }
  
      @Override
      public void run(){
          if (account.money - drawingMoney < 0){
              System.out.println(Thread.currentThread().getName() + "钱不够");
              return;
          }
  
          try {
              Thread.sleep(1000);
          } catch (InterruptedException e) {
              e.printStackTrace();
          }
  
          account.money -= drawingMoney;
          nowMoney += drawingMoney;
  
          System.out.println(account.name + "余额为:" + account.money);
          System.out.println(this.getName() + "手里的钱:" + nowMoney);
      }
  }
  • ArrayList
  public class UnsafeList{
      public static void main(String[] args) {
  
          List<String> list = new ArrayList<>();
          for (int i = 0; i < 5000; i++) {
              new Thread(() -> {
                 list.add(Thread.currentThread().getName());
              }).start();
          }
          System.out.println(list.size());
      }
  }
posted @   悟道九霄  阅读(36)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· Docker 太简单,K8s 太复杂?w7panel 让容器管理更轻松!
点击右上角即可分享
微信分享提示