Java 基础入门 | 第十五章 Java多线程

简介: 目录线程的概念线程的使用线程的状态线程同步问题死锁和解决办法线程的概念什么是线程在一个程序中同时运行的多个独立流程,每一个独立的流程就是一个线程线程并发多个线程并发执行主线程 CPU 优先级 时间片 资源当

 线程的概念

什么是线程

在一个程序中同时运行的多个独立流程,每一个独立的流程就是一个线程

线程并发

多个线程并发执行

主线程  CPU  优先级   时间片  资源

当JVM启动之后,加载类文件,发现main方法,那么就会为main方法创建一个线程,用于main方法执行,这个为main方法创建的线程称为主线程

线程的使用

在Java中创建线程的方法有两种

    • 方法一  继承java.lang.Thread类
    • 方法二  实现java.lang.Runnable接口

    继承Thread类

      • 自定义一个线程类继承自Thread
      • 重写Thread的run方法
      • 创建一个该类的对象
      • 调用该类继承自Thread的start方法开启线程

      代码示例

      public class MyThread1 extends Thread{
          public void run(){
              for (int i = 0; i < 1000; i++) {
                  System.out.println(i+" $$$");
              }
          }
      }

      image.gif

      public class MyThread2 extends Thread{
          public void run(){
              for (int i = 0; i < 1000; i++) {
                  System.out.println(i+" ###");
              }
          }
      }

      image.gif

      public class ThreadTest {
          public static void main(String[] args) {
              Thread t1=new MyThread1();
              Thread t2=new MyThread2();
              //启动线程
              t1.start();
              t2.start();
          }
      }

      image.gif

      Runnable接口开发线程

        • 用户开发一个类实现Runnable接口
        • 实现run()
        • 运行线程

        代码示例

        public class MyRunnable1 implements Runnable{
            @Override
            public void run() {
                for (int i = 0; i < 1000; i++) {
                    System.out.println(i+" $$$");
                }
            }
        }

        image.gif

        public class MyRunnable2 implements Runnable{
            @Override
            public void run() {
                for (int i = 0; i < 1000; i++) {
                    System.out.println(i+" ###");
                }
            }
        }

        image.gif

        public class ThreadTest {
            public static void main(String[] args) {
                Runnable r1=new MyRunnable1();
                Thread t1=new Thread(r1);
                Runnable r2=new MyRunnable2();
                Thread t2=new Thread(r2);
                //启动线程
                t1.start();
                t2.start();
            }
        }

        image.gif

        线程使用总结

          • 根据学习过的知识判断,以上两种线程创建方法,哪一种更好--第二种实现了任务代码和API的分离
          • 程序的输出结果固定吗?
          • 程序中存在几个线程?执行的先后顺序
          • 可不可以直接在main方法中直接调用run()

          线程的状态

            • 初始状态
            • 可运行状态
            • 运行状态
            • 终结状态

            image.gif编辑

            sleep方法

            void sleep(long time)方法用于使当前线程休眠指定的毫秒数

            public class MyThread1 extends Thread{
                public void run(){
                    for (int i = 0; i < 1000; i++) {
                        System.out.println(i+" $$$");
                        try {
                            //睡眠1秒
                            Thread.sleep(1000);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }

            image.gif

            public class MyRunnable1 implements Runnable{
                @Override
                public void run() {
                    for (int i = 0; i < 1000; i++) {
                        System.out.println(i+" $$$");
                        try {
                            //睡眠1秒
                            Thread.sleep(1000);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }

            image.gif

            一旦调用了sleep方法,该线程就由运行状态进入了阻塞状态

            image.gif编辑join方法的使用

              • 利用sleep方法对线程的控制是非常不精确的
              • join方法可以精确控制线程
              • join方法也会导致线程阻塞
              • 特点:如果当前线程中调用了另外一个线程的 join方法,当前线程会立即阻塞,直到另外一个线程运行完成
              public class MyThread1 extends Thread{
                  public void run(){
                      for (int i = 0; i < 1000; i++) {
                          System.out.println(i+" $$$");
                      }
                  }
              }

              image.gif

              public class MyThread2 extends Thread{
                  Thread thread;
                  public void run(){
                      try {
                          thread.join();
                      } catch (InterruptedException e) {
                          e.printStackTrace();
                      }
                      for (int i = 0; i < 1000; i++) {
                          System.out.println(i+" ###");
                      }
                  }
              }

              image.gif

              public class ThreadTest {
                  public static void main(String[] args) {
                      MyThread1 t1=new MyThread1();
                      MyThread2 t2=new MyThread2();
                      //将线程1赋值到MyThread2的属性thread
                      t2.thread=t1;
                      //启动线程
                      t1.start();
                      t2.start();
                  }
              }

              image.gif

              join方法的问题

              如果2个线程彼此调用对方的join方法,会导致程序无法进行。

              解决办法

              调用 void join(long millis)  方法

              客户存取款多线程实例

              public class Account {
                  //银行账户余额
                  private int balance;
                  public Account(int balance) {
                      this.balance = balance;
                  }
                  //存款方法
                  public void deposit(int money){
                      balance=balance+money;
                  }
                  //取款方法
                  public void withdrawal(int money){
                      if(money>balance){
                          System.out.println("余额不足");
                      }else{
                          balance=balance-money;
                      }
                  }
                  public int getBalance() {
                      return balance;
                  }
              }

              image.gif

              public class PersonA extends Thread{
                  //用户A的账户属性
                  private  Account account;
                  public PersonA(Account account){
                      this.account=account;
                  }
                  public void run(){
                      for (int i = 0; i < 2; i++) {
                          System.out.println("此时账户还有"+account.getBalance()+"元");
                          account.deposit(500);
                          System.out.println("用户A存款500元");
                          System.out.println("此时账户还有"+account.getBalance()+"元");
                          try {
                              Thread.sleep(100);
                          } catch (InterruptedException e) {
                              e.printStackTrace();
                          }
                      }
                  }
              }

              image.gif

              public class PersonB extends Thread{
                  //用户B的账户属性
                  private  Account account;
                  public PersonB(Account account){
                      this.account=account;
                  }
                  public void run(){
                      for (int i = 0; i < 2; i++) {
                          System.out.println("此时账户还有"+account.getBalance()+"元");
                          account.withdrawal(1000);
                          System.out.println("用户B取款1000元");
                          System.out.println("此时账户还有"+account.getBalance()+"元");
                          try {
                              Thread.sleep(100);
                          } catch (InterruptedException e) {
                              e.printStackTrace();
                          }
                      }
                  }
              }

              image.gif

              public class BankDemo {
                  public static void main(String[] args) {
                      //初始账户余额3000块
                      Account account=new Account(3000);
                      PersonA personA=new PersonA(account);
                      PersonB personB=new PersonB(account);
                      //启动线程
                      personA.start();
                      personB.start();
                  }
              }

              image.gif

              线程同步问题

                • 产生数据不一致的原因:多个线程并发访问了同一个对象,如果破坏了不可分割的操作,从而就会造成数据不一致
                • 被多线程并发访问时如果一个对象有可能出现数据不一致的问题,那么这个对象称为线程不安全的对象

                如何解决多线程并发访问的问题

                利用synchronized 同步锁

                synchronized(Object o){
                      代码块
                    }

                image.gif

                public class Account {
                    //银行账户余额
                    private int balance;
                    private Object o=new Object();
                    public Account(int balance) {
                        this.balance = balance;
                    }
                    //存款方法
                    public void deposit(int money){
                        synchronized (o){
                            balance=balance+money;
                        }
                    }
                    //取款方法
                    public void withdrawal(int money){
                        synchronized (o){
                            if(money>balance){
                                System.out.println("余额不足");
                            }else{
                                balance=balance-money;
                            } 
                        }
                    }
                    public int getBalance() {
                        return balance;
                    }
                }

                image.gif

                死锁和解决办法

                什么是死锁

                死锁是因为多线程访问共享资源,由于访问的顺序不当所造成的,通常是一个线程锁定了一个资源A,而又想去锁定资源B;在另一个线程中,锁定了资源B,而又想去锁定资源A以完成自身的操作,两个线程都想得到对方的资源,而不愿释放自己的资源,造成两个线程都在等待,而无法执行的情况。

                public class MyThread1 extends Thread{
                    //资源a
                    private Object a;
                    //资源b;
                    private Object b;
                    public MyThread1(Object a,Object b){
                        this.a=a;
                        this.b=b;
                    }
                    public void run(){
                        //当前线程名称
                        String name = Thread.currentThread().getName();
                        System.out.println(name+"获取资源a");
                        synchronized (a){
                            System.out.println(name+"获取资源b");
                            await();
                            synchronized (b){
                                System.out.println(name+"释放资源b");
                                await();
                            }
                            System.out.println(name+"释放资源a");
                        }
                    }
                    private void await() {
                        try {
                            Thread.sleep(3000);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }

                image.gif

                public class MyThread2 extends Thread{
                    //资源a
                    private Object a;
                    //资源b;
                    private Object b;
                    public MyThread2(Object a,Object b){
                        this.a=a;
                        this.b=b;
                    }
                    public void run(){
                        //当前线程名称
                        String name = Thread.currentThread().getName();
                        System.out.println("获取资源b");
                        synchronized (b){
                            System.out.println(name+"获取资源b");
                            await();
                            synchronized (a){
                                System.out.println(name+"释放资源a");
                                await();
                            }
                            System.out.println(name+"释放资源b");
                        }
                    }
                    private void await() {
                        try {
                            Thread.sleep(3000);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }

                image.gif

                public class ThreadTest {
                    public static void main(String[] args) {
                        Object a=new Object();
                        Object b=new Object();
                        MyThread1 t1=new MyThread1(a,b);
                        MyThread2 t2=new MyThread2(a,b);
                        //启动线程
                        t1.start();
                        t2.start();
                    }
                }

                image.gif

                解决死锁

                利用wait()方法和notify()方法

                  • wait():等待,如果线程执行了wait方法,那么该线程会进入等待的状态,等待状态下的线程必须要被其他线程调用notify()方法才能唤醒。
                  • notify():唤醒,唤醒线程池等待线程其中的一个。
                  • notifyAll():唤醒线程池所有等待线程。

                  wait与notify方法要注意的事项:

                    1. wait方法与notify方法是属于Object对象的。
                    2. wait方法与notify方法必须要在同步代码块或者是同步函数中才能使用。
                    3. wait方法与notify方法必须要由所对象调用。
                    public class MyThread1 extends Thread{
                        //资源a
                        private Object a;
                        //资源b;
                        private Object b;
                        public MyThread1(Object a,Object b){
                            this.a=a;
                            this.b=b;
                        }
                        public void run(){
                            //当前线程名称
                            String name = Thread.currentThread().getName();
                            System.out.println(name+"获取资源a");
                            synchronized (a){
                                try {
                                    a.wait();
                                } catch (InterruptedException e) {
                                    e.printStackTrace();
                                }
                                System.out.println(name+"获取资源b");
                                await();
                                synchronized (b){
                                    System.out.println(name+"释放资源b");
                                    await();
                                }
                                System.out.println(name+"释放资源a");
                            }
                        }
                        private void await() {
                            try {
                                Thread.sleep(3000);
                            } catch (InterruptedException e) {
                                e.printStackTrace();
                            }
                        }
                    }

                    image.gif

                    public class MyThread2 extends Thread{
                        //资源a
                        private Object a;
                        //资源b;
                        private Object b;
                        public MyThread2(Object a,Object b){
                            this.a=a;
                            this.b=b;
                        }
                        public void run(){
                            //当前线程名称
                            String name = Thread.currentThread().getName();
                            System.out.println("获取资源b");
                            synchronized (b){
                                System.out.println(name+"获取资源b");
                                await();
                                synchronized (a){
                                    System.out.println(name+"释放资源a");
                                    a.notify();
                                    await();
                                }
                                System.out.println(name+"释放资源b");
                            }
                        }
                        private void await() {
                            try {
                                Thread.sleep(3000);
                            } catch (InterruptedException e) {
                                e.printStackTrace();
                            }
                        }
                    }

                    image.gif

                    public class ThreadTest {
                        public static void main(String[] args) {
                            Object a=new Object();
                            Object b=new Object();
                            MyThread1 t1=new MyThread1(a,b);
                            MyThread2 t2=new MyThread2(a,b);
                            //启动线程
                            t1.start();
                            t2.start();
                        }
                    }

                    image.gif


                    相关文章
                    |
                    6天前
                    |
                    设计模式 Java 关系型数据库
                    【Java笔记+踩坑汇总】Java基础+JavaWeb+SSM+SpringBoot+SpringCloud+瑞吉外卖/谷粒商城/学成在线+设计模式+面试题汇总+性能调优/架构设计+源码解析
                    本文是“Java学习路线”专栏的导航文章,目标是为Java初学者和初中高级工程师提供一套完整的Java学习路线。
                    |
                    6天前
                    |
                    存储 缓存 安全
                    【Java面试题汇总】多线程、JUC、锁篇(2023版)
                    线程和进程的区别、CAS的ABA问题、AQS、哪些地方使用了CAS、怎么保证线程安全、线程同步方式、synchronized的用法及原理、Lock、volatile、线程的六个状态、ThreadLocal、线程通信方式、创建方式、两种创建线程池的方法、线程池设置合适的线程数、线程安全的集合?ConcurrentHashMap、JUC
                    【Java面试题汇总】多线程、JUC、锁篇(2023版)
                    |
                    6天前
                    |
                    安全 Java API
                    【Java面试题汇总】Java基础篇——String+集合+泛型+IO+异常+反射(2023版)
                    String常量池、String、StringBuffer、Stringbuilder有什么区别、List与Set的区别、ArrayList和LinkedList的区别、HashMap底层原理、ConcurrentHashMap、HashMap和Hashtable的区别、泛型擦除、ABA问题、IO多路复用、BIO、NIO、O、异常处理机制、反射
                    【Java面试题汇总】Java基础篇——String+集合+泛型+IO+异常+反射(2023版)
                    |
                    6天前
                    |
                    缓存 安全 Java
                    【Java面试题汇总】Java基础篇——基础、修饰符和关键字(2023版)
                    Java的特点和优点,、Java 8的新特性、面向对象、基本数据类型和引用类型、自动拆装箱与自动装箱、==与equals()的区别、为什么重写equals()就要重写hashcode()、抽象类和接口的区别、重载和重写的区别、四种引用方式、wt()和sleep()的区别、java方法是值传递还是引用传递?访问修饰符、static、final、this和super、volatile的用法及原理
                    【Java面试题汇总】Java基础篇——基础、修饰符和关键字(2023版)
                    |
                    2天前
                    |
                    Java
                    深入理解Java中的多线程编程
                    本文将探讨Java多线程编程的核心概念和技术,包括线程的创建与管理、同步机制以及并发工具类的应用。我们将通过实例分析,帮助读者更好地理解和应用Java多线程编程,提高程序的性能和响应能力。
                    15 4
                    |
                    7天前
                    |
                    Java 调度 开发者
                    Java中的多线程基础及其应用
                    【9月更文挑战第13天】本文将深入探讨Java中的多线程概念,从基本理论到实际应用,带你一步步了解如何有效使用多线程来提升程序的性能。我们将通过实际代码示例,展示如何在Java中创建和管理线程,以及如何利用线程池优化资源管理。无论你是初学者还是有经验的开发者,这篇文章都将为你提供有价值的见解和技巧,帮助你更好地理解和应用多线程编程。
                    |
                    11天前
                    |
                    算法 Java 数据处理
                    Java并发编程:解锁多线程的力量
                    在Java的世界里,掌握并发编程是提升应用性能和响应能力的关键。本文将深入浅出地探讨如何利用Java的多线程特性来优化程序执行效率,从基础的线程创建到高级的并发工具类使用,带领读者一步步解锁Java并发编程的奥秘。你将学习到如何避免常见的并发陷阱,并实际应用这些知识来解决现实世界的问题。让我们一起开启高效编码的旅程吧!
                    |
                    16天前
                    |
                    存储 Java 程序员
                    优化Java多线程应用:是创建Thread对象直接调用start()方法?还是用个变量调用?
                    这篇文章探讨了Java中两种创建和启动线程的方法,并分析了它们的区别。作者建议直接调用 `Thread` 对象的 `start()` 方法,而非保持强引用,以避免内存泄漏、简化线程生命周期管理,并减少不必要的线程控制。文章详细解释了这种方法在使用 `ThreadLocal` 时的优势,并提供了代码示例。作者洛小豆,文章来源于稀土掘金。
                    |
                    13天前
                    |
                    Java 开发者
                    Java中的多线程编程基础与实战
                    【9月更文挑战第6天】本文将通过深入浅出的方式,带领读者了解并掌握Java中的多线程编程。我们将从基础概念出发,逐步深入到代码实践,最后探讨多线程在实际应用中的优势和注意事项。无论你是初学者还是有一定经验的开发者,这篇文章都能让你对Java多线程有更全面的认识。
                    17 1
                    |
                    2天前
                    |
                    安全 数据库连接 API
                    C#一分钟浅谈:多线程编程入门
                    在现代软件开发中,多线程编程对于提升程序响应性和执行效率至关重要。本文从基础概念入手,详细探讨了C#中的多线程技术,包括线程创建、管理及常见问题的解决策略,如线程安全、死锁和资源泄露等,并通过具体示例帮助读者理解和应用这些技巧,适合初学者快速掌握C#多线程编程。
                    12 0