【JavaSE】多线程基础学习笔记

多线程基础

-线程相关概念

  • 程序(Program)

    是为完成特定任务、用某种语言编写的一组指令的集合简单的说:就是我们写的代码

  • 进程

    1. 进程是指运行中的程序,比如我们使用QQ,就启动了一个进程,操作系统就会为该进程分配内存空间。当我们使用迅雷,又启动了一个进程,操作系统将为迅雷分配新的内存空间。
    2. 进程是程序的一次执行过程,或是正在运行的一个程序。是动态过程:有它自身的产生、存在和消亡的过程
  • 什么是线程

    1. 线程是由进程创建的,是进程的一个实体
    2. 一个进程可以拥有多个线程
    3. 线程也可以创建线程
  • 其他相关概念

    1. 单线程:同一个时刻,只允许执行一个线程
    2. 多线程:同一个时刻,可以执行多个线程,比如:一个qq进程,可以同时打开多个聊天窗口,一个迅雷进程,可以同时下载多个文件
    3. 并发:同一个时刻,多个任务交替执行,造成一种"貌似同时"的错觉,简单说,单核cpu实现的多任务就是并发
    4. 并行:同一个时刻,多个任务同时执行。多核cpu可以实现并行。

-线程基本使用

  • 创建线程的两种方式

    1. 继承Thread 类,重写 run方法
    2. 实现Runnable接口,重写run方法
  • 线程应用案例1-继承Thread类

    java 复制代码
    package com.xijie.useThread;
    
    /**
     * 请编写程序,开启一个线程,该线程每隔1秒。在控制台输出"喵喵, 我是小猫咪"
     * 对上题改进:当输出5次 喵喵, 我是小猫咪,结束该线程
     * 使用JConsole 监控线程执行情况,并画出程序示意图!:指令jconsole
     */
    public class ThreadUse {
        public static void main(String[] args) throws InterruptedException {
            Cat cat = new Cat();
            //开始运行线程
            cat.start();
    
            //获取主线程名称
            System.out.println("主线程开始运行,主线程名为:"+Thread.currentThread().getName());
    
            //主线程内循环输出40次
            for (int i = 0; i < 40; i++) {
                System.out.println("线程名:"+Thread.currentThread().getName()+" 循环次数:"+(i+1));
                Thread.sleep(1000);
            }
        }
    }
    
    //继承Thread或实现Runnable以实现线程类
    class Cat extends Thread{
        @Override
        public void run() {
            //super.run();
            //计数器,到达5次就停止
            int count=0;
            //死循环,一直执行
            while(true){
                //输出内容、线程名、循环次数
                System.out.println("喵喵, 我是小猫咪 第"+(++count)+"次循环,线程名:"+Thread.currentThread().getName());
                //输出60次就停止线程
                if(count>=60){
                    break;
                }
                //休眠1秒,需处理异常
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    start方法底层:start方法调用start0方法。start0方法由JVM获取对应的实现,底层一般由c或c++语言实现,创建线程完毕后再由线程执行run方法。

  • 线程应用案例2-实现Runable接口

    说明

    1. java是单继承的,在某些情况下一个类可能已经继承了某个父类,这时在用继承Thread类方法来创建线程显然不可能了。
    2. java设计者们提供了另外一个方式创建线程,就是通过实现Runnable接口来创建线程

    案例代码

    java 复制代码
    package com.xijie.useRunnable;
    
    /**
     * 请编写程序,该程序可以每隔1秒。在控制台输出"hi!",当输出10次后,自动退出。
     * 请使用实现Runnable接口的方式实现。
     * 写一个演示代理模式实现线程的类
     */
    public class UseRunnable {
        public static void main(String[] args) {
            HiTalker talker = new HiTalker();
            //使用Runnable开辟线程
            Thread t1 = new Thread(talker);
            t1.start();
    
            //模拟用代理模式实现线程(没有另开线程,因为开辟线程需要调用操作系统的方法实现)
            ProxyThread proxy = new ProxyThread(talker);
            proxy.start();
        }
    }
    
    class HiTalker implements Runnable {
        public void run() {
            for (int i = 0; i < 10; i++) {
                System.out.println("hi!"+Thread.currentThread().getName());
                try {
                    if(i == 9)
                        break;
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
    
    /**
     * 模拟用代理模式实现线程(没有另开线程,因为开辟线程需要调用操作系统的方法实现)
     */
    class ProxyThread implements Runnable {
        private Runnable target;
    
        public ProxyThread(Runnable target) {
            this.target = target;
        }
    
        @Override
        public void run() {
            //如果target已初始化,就在线程中执行run方法
            if(target != null) {
                target.run();
            }
        }
    
        /**
        开始线程的方法
         */
        public void start(){
            this.start0();
        }
    
        /**
         * 在Thread类中JVM会重写start0方法调用操作系统中开辟线程的方法
         */
        private void start0() {
            System.out.println("调用操作系统方法创建线程");
            this.run();
        }
    }
  • 线程应用案例-多线程执行

    java 复制代码
    package com.xijie.multiThread;
    
    /**
     * 用实现3个Runnable接口的进程,一个每隔1秒输出hi,10次,一个每隔2秒输出,4次nice,一个每隔3秒输出,3次wtf
     */
    public class MultiThread {
        public static void main(String[] args) {
            Thread t1 = new Thread(new MyThread(10,"hi",1000));
            Thread t2 = new Thread(new MyThread(4,"nice",2000));
            Thread t3 = new Thread(new MyThread(3,"wtf",3000));
            t1.start();
            t2.start();
            t3.start();
        }
    }
    
    //每隔1秒输出,10次hi
    class MyThread implements Runnable {
        int count;
        String str;
        int delay;
    
        public MyThread(int count, String str, int delay) {
            this.count = count;
            this.str = str;
            this.delay = delay;
        }
    
        @Override
        public void run() {
            int count=0;
            while(true){
                System.out.println(this.str);
                if(++count==this.count)
                    break;
                try {
                    Thread.sleep(this.delay);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        }
    }
  • 线程的理解

    线程像工厂里的多条流水线,各自忙着不同工序,共享机器设备(内存等资源)。CPU 是调度员,快速切换让它们 "同时" 运转,协作完成程序的复杂任务,互不干扰又紧密配合。

-继承Thread与实现Runnable的区别

  1. 从java的设计来看,通过继承Thread或者实现Runnable接口来创建线程本质上没有区别,从jdk帮助文档我们可以看到Thread类本身就实现了Runnable接口

  2. 实现Runnable接口方式更加适合多个线程共享一个资源的情况,并且避免了单继承的限制

  3. 售票系统超卖问题演示(竞态条件问题)

    java 复制代码
    package com.xijie.sellerProblem;
    
    /**
     * 创建三个售票员线程,演示超卖问题
     */
    public class SellerProblem {
        public static void main(String[] args) {
            Thread thread1 = new Thread(new SellerRunnable("jack"));
            Thread thread2 = new Thread(new SellerRunnable("mary"));
            Thread thread3 = new Thread(new SellerRunnable("mike"));
    
            //执行后余票很可能会小于0,出现超卖问题
            thread1.start();
            thread2.start();
            thread3.start();
        }
    }
    
    class SellerRunnable implements Runnable {
        private static int ticketNum;
    
        private String sellerName;
    
        static{
            ticketNum = 100;
        }
    
        public SellerRunnable(String sellerName) {
            this.sellerName = sellerName;
        }
    
        @Override
        public void run() {
            while(true){
                //售票
                System.out.println("售票员"+sellerName+"卖出一张票,余票为"+(--ticketNum)+"张");
                //若无票则下班
                if(ticketNum <= 0){
                    break;
                }
                //延迟10毫秒
                try {
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        }
    }

-线程终止

  • 基本说明

    1. 当线程完成任务后,会自动退出,
    2. 还可以通过使用变量来控制run方法退出的方式停止线程,即通知方式
  • 使用通知变量通知线程结束的案例

    java 复制代码
    package com.xijie.threadEnd;
    
    public class ThreadEnd {
        public static void main(String[] args) throws InterruptedException {
            MyThread myThread = new MyThread();
            myThread.start();
    
            Thread.sleep(2000);
    
            System.out.println("结束执行");
            myThread.stopRunning();
        }
    }
    
    class MyThread extends Thread{
        //设置结束通知变量
        private boolean loop=true;
    
        @Override
        public void run() {
            int count=0;
            while(loop){
                System.out.println("线程执行"+(++count)+"次");
                try {
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        }
    
        public void stopRunning(){
            loop=false;
        }
    }

-线程常用方法

  • 常用方法

    1. setName //设置线程名称,使之与参数 name 相同
    2. getName //返回该线程的名称
    3. start //使该线程开始执行;Java 虚拟机底层调用该线程的 start0 方法
    4. run //调用线程对象 run 方法:
    5. setPriority //更改线程的优先级
    6. getPriority //获取线程的优先级
    7. sleep//在指定的毫秒数内让当前正在执行的线程休眠(暂停执行)
    8. interrupt //中断线程
    9. yield:线程的礼让。让出cpu,让其他线程执行,但礼让的时间不确定,所以也不一定礼让成功
    10. join:线程的插队。插队的线程一旦插队成功,则肯定先执行完插入的线程所有的任务
  • 注意事项和细节

    1. start 底层会创建新的线程,调用run,run 就是一个简单的方法调用,不会启动新线程线程
    2. 优先级的范围:Thread.MAX_PRIORITY/MIN_PRIORITY/NORM_PRIORITY
    3. interrupt,中断线程,但并没有真正的结束线程。所以一般用于中断正在休眠线程
    4. sleep:线程的静态方法,使当前线程休眠
  • 线程中断和插队练习

    java 复制代码
    package com.xijie.interruptJoin;
    
    /**
     * 主线程开辟一个子线程
     * 子线程输出一次"我要睡觉了",随后休眠10秒,主线共输出10次hi!,间隔1秒,在主线程输出3次hi后,中断子线程,随后子线程输出3次"wtf"后,主线程再输出剩余的hi
     */
    public class InterruptJoin {
        public static void main(String[] args) throws InterruptedException {
            Thread thread = new Thread(new MyRunnable());
            thread.start();
    
            for (int i = 0; i < 10; i++) {
                System.out.println("hi!"+Thread.currentThread().getName());
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                if(i==2){
                    thread.interrupt();
                    thread.join();
                }
            }
    
            System.out.println("我嗨完了"+Thread.currentThread().getName());
        }
    }
    
    class MyRunnable implements Runnable {
        @Override
        public void run() {
            System.out.println("我要睡觉了"+Thread.currentThread().getName());
            try {
                Thread.sleep(10000);
            } catch (InterruptedException e) {
                System.out.println("我醒了"+Thread.currentThread().getName());
            }
    
            for (int i = 0; i < 3; i++) {
                System.out.println("wtf!"+Thread.currentThread().getName());
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        }
    }
  • 用户线程和守护线程

    1. 用户线程:也叫工作线程,当线程的任务执行完或通知方式结束
    2. 守护线程:一般是为工作线程服务的,当所有的用户线程结束,守护线程自动结束
    3. 常见的守护线程:垃圾回收机制

    守护线程案例:

    java 复制代码
    package com.xijie.daemon;
    
    /**
     * 演示守护线程
     * 主线程输出10次"王宝强在辛苦工作",间隔200ms
     * 子线程一直输出"宋喆和马蓉在愉快的聊天",间隔100ms,直到主线程结束
     */
    public class ShowDaemon {
        public static void main(String[] args) {
            Dating dating = new Dating();
            Thread thread = new Thread(dating);
            //设置为守护线程
            thread.setDaemon(true);
            thread.start();
    
            for (int i = 0; i < 10; i++) {
                System.out.println("王宝强在辛苦工作"+(i+1));
                try {
                    Thread.sleep(200);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        }
    }
    
    class Dating implements Runnable{
        @Override
        public void run() {
            for (;;){
                System.out.println("宋喆和马蓉在愉快的聊天");
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        }
    }

-线程的生命周期

  • JDK中用Thread.State枚举表示了线程的几种状态(6+1)
    1. NEW
      尚未启动的线程处于此状态,
    2. RUNNABLE(在内部包含READY和RUNNING两个状态)
      在Java虚拟机中执行的线程处于此状态。
    3. BLOCKED
      被阻塞等待监视器锁定的线程(等待进入同步代码块)处于此状态。
    4. WAITING
      正在等待另一个线程执行特定动作的线程处于此状态,
    5. TIMED WAITING正在等待另一个线程执行动作达到指定等待时间的线程(例如sleep的线程)处于此状态,
    6. TERMINATED
      已退出的线程处于此状态

-Synchronized

  • 线程同步机制

    1. 在多线程编程,一些敏感数据不允许被多个线程同时访问,此时就使用同步访问技术,保证数据在任何时刻,最多有一个线程访问,以保证数据的完整性。
    2. 也可以这里理解:线程同步,即当有一个线程在对内存进行操作时,其他线程都不可以对这个内存地址进行操作,直到该线程完成操作,其他线程才能对该内存地址进行操作.
  • 同步具体方法-Synchronized

    1. 同步代码块

      java 复制代码
      synchronized(对象){//得到对象的锁,才能操作同步代码
          // 需要被同步代码;
      }
    2. synchronized还可以放在方法声明中,表示整个方法-为同步方法

      java 复制代码
      public synchronized void m (String name){
      //需要被同步的代码
      }
    3. 如何理解:就好像 某小伙伴上厕所前先把门关上(上锁),完事后再出来(解锁),那么其它小伙伴就可在使用厕所了

    4. 使用synchronized 解决售票问题

      java 复制代码
      package com.xijie.synchronized_;
      
      /**
       * 创建三个售票员线程,演示超卖问题
       */
      public class SynchronizedTickedSelling {
          public static void main(String[] args) {
              SellerRunnable sellerRunnable=new SellerRunnable("MMM");
              new Thread(sellerRunnable).start();
              new Thread(sellerRunnable).start();
              new Thread(sellerRunnable).start();
              new Thread(sellerRunnable).start();
          }
      }
      
      class SellerRunnable implements Runnable {
          private static int ticketNum;
          private String sellerName;
      
          static{
              ticketNum = 200;
          }
      
          public SellerRunnable(String sellerName) {
              this.sellerName = sellerName;
          }
      
          @Override
          public void run() {
              while(this.ticketNum > 0) {
                  sellATicket();
              }
              System.out.println("票卖完了,大家下班了");
          }
      
          private synchronized void sellATicket(){
              if(ticketNum <= 0){
                  System.out.println("售票员"+Thread.currentThread().getName()+"出售失败,余票为"+(ticketNum)+"张");
                  return;
              }
              System.out.println("售票员"+Thread.currentThread().getName()+"卖出一张票,余票为"+(--ticketNum)+"张");
              //延迟10毫秒
              try {
                  Thread.sleep(10);
              } catch (InterruptedException e) {
                  throw new RuntimeException(e);
              }
          }
      }

-互斥锁

  • 基本介绍

    1. Java语言中,引入了对象互斥锁的概念,来保证共享数据操作的完整性。
    2. 每个对象都对应于一个可称为"互斥锁"的标记,这个标记用来保证在任一时刻,只能有一个线程访问该对象。
    3. 关键字synchronized 来与对象的互斥锁联系。当某个对象用synchronized修饰时,表明该对象在任一时刻只能由一个线程访问
    4. 同步的局限性:导致程序的执行效率要降低
    5. 同步方法(非静态的)的锁可以是this,也可以是其他对象(要求是同一个对象)
    6. 同步方法(静态的)的锁为当前类本身。
  • 注意事项和细节

    1. 同步方法如果没有使用static修饰:默认锁对象为this

    2. 如果方法使用static修饰,默认锁对象:当前类.class

    3. 实现的落地步骤:

      需要先分析上锁的代码

      选择同步代码块或同步方法

      要求多个线程的锁对象为同一个即可!

-线程死锁

  • 基本介绍

    多个线程都占用了对方的锁资源,但不肯相让,导致了死锁,在编程是一定要避免死锁的发生.

-释放锁

  • 下面操作会释放锁

    1. 当前线程的同步方法、同步代码块执行结束

      案例:上厕所,完事出来

    2. 当前线程在同步代码块、同步方法中遇到break、return经理叫他修改bug,不得已出来

      案例:没有正常的完事,

    3. 当前线程在同步代码块同步方法中出现了未处理的Error或Exception,导致异常结束

      案例:没有正常的完事,发现忘带纸,不得已出来

    4. 当前线程在同步代码块、同步方法中执行了线程对象的wait0)方法,当前线程暂停,并释放锁。

      案例:没有正常完事,觉得需要酝酿下,所以出来等会再进去

  • 下面操作不会释放锁

    1. 线程执行同步代码块或同步方法时,程序调用Thread.sleep()、Thread.yield()方法暂停当前线程的执行,不会释放锁

      案例:上厕所,太困了,在坑位上眯了一会

    2. 线程执行同步代码块时,其他线程调用了该线程的suspend()方法将该线程挂起该线程不会释放锁。

      提示:应尽量避免使用suspend()和resume()来控制线程,方法不再推荐使用

-本章练习

java 复制代码
package com.xijie.homework;

import java.util.Random;
import java.util.Scanner;

/**
 * 在main方法中启动两个线程
 * 第1个线程循环随机打印100以内的整数
 * 直到第2个线程从键盘读取了"Q"命令,
 */
public class Homework01 {
    public static void main(String[] args) {
        LotteryRunnable lotteryRunnable1=new LotteryRunnable(null,1);
        Thread thread1 = new Thread(lotteryRunnable1);
        thread1.start();

        LotteryRunnable lotteryRunnable2=new LotteryRunnable(lotteryRunnable1,2);
        Thread thread2 = new Thread(lotteryRunnable2);
        thread2.start();
    }
}

//抽奖机
class LotteryRunnable implements Runnable {
    LotteryRunnable lotteryRunnable;
    private boolean loop=true;
    private int workType;

    public LotteryRunnable(LotteryRunnable lotteryRunnable,int workType) {
        this.lotteryRunnable=lotteryRunnable;
        this.workType = workType;
    }

    @Override
    public void run() {
        if (workType == 1) {
            NumberTyper();
        } else if (workType == 2) {
            if(lotteryRunnable==null){
                System.out.println("传参错误");
                return;
            }
            qGetter();
        }
    }

    //循环打印
    private void NumberTyper() {
        while(loop){
            int number = new Random().nextInt(100);
            System.out.println(number);
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    //接收q
    private void qGetter(){
        while(true){
            Scanner scanner = new Scanner(System.in);
            System.out.println("输入q停止摇奖机");
            String str=scanner.next().trim();
            if(str.equals("q")){
                lotteryRunnable.loop=false;
                break;
            }
        }
    }
}
java 复制代码
package com.xijie.homework;

/**
 * 有2个用户分别从同一个卡上取钱(总额:10000)
 * 每次都取1000,当余额不足时,就不能取款了
 * 不能出现超取现象 =》 线程同步问题,
 */
public class Homework02 {
    public static void main(String[] args) {
        BankAccount bankAccount=new BankAccount(10000);
        AccountOwner accountOwner1=new AccountOwner("小明",bankAccount,10);
        AccountOwner accountOwner2=new AccountOwner("小李",bankAccount,10);

        Thread t1=new Thread(accountOwner1);
        Thread t2=new Thread(accountOwner2);
        t1.start();
        t2.start();
    }
}

class BankAccount{
    private double money;

    BankAccount(double money){
        this.money = money;
    }

    public double getMoney() {
        return money;
    }

    public boolean withdraw(double money){
        if(money <= this.money){
            this.money -= money;
            return true;
        }else{
            return false;
        }
    }
}

class AccountOwner implements Runnable{
    private String name;
    //对象锁
    private BankAccount account;
    private int withdrawInter;

    AccountOwner(String name,BankAccount account,int withdrawInter){
        this.name = name;
        this.account = account;
        this.withdrawInter = withdrawInter;
    }

    @Override
    public void run() {
        boolean flag = false;
        while(true){
            //使用对象锁实现同步
            synchronized (account){
                flag=account.withdraw(1000);
                if(!flag){
                    System.out.println(name+"取款失败,余额不足,余额还有"+account.getMoney());
                    break;
                }
                System.out.println(name+"取款1000成功,余额还有"+account.getMoney());
            }

            try {
                Thread.sleep(withdrawInter);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
    }
}
相关推荐
freexyn9 分钟前
Matlab自学笔记六十一:快速上手解方程
数据结构·笔记·matlab
try2find22 分钟前
安装llama-cpp-python踩坑记
开发语言·python·llama
博观而约取1 小时前
Django ORM 1. 创建模型(Model)
数据库·python·django
ytttr8732 小时前
matlab通过Q学习算法解决房间路径规划问题
学习·算法·matlab
精灵vector3 小时前
构建专家级SQL Agent交互
python·aigc·ai编程
Zonda要好好学习3 小时前
Python入门Day2
开发语言·python
Vertira3 小时前
pdf 合并 python实现(已解决)
前端·python·pdf
寻丶幽风3 小时前
论文阅读笔记——NoPoSplat
论文阅读·笔记·三维重建·3dgs·相机位姿·dustr
太凉3 小时前
Python之 sorted() 函数的基本语法
python
项目題供诗3 小时前
黑马python(二十四)
开发语言·python