多线程|线程的创建、线程的状态、线程不安全、如何保证线程安全

多线程

线程的模型

进程是系统分配资源的最小单位,线程是系统调度的最小单位

多线程:一个进程中不只一个线程

线程的优点

1.更好的利用CPU资源,多线程可在主线程执行任务同时执行其任务,不需要等待

2.同一进程的各线程之间可以共享该进程的所有资源

3.创建线程代价比较小,而系统创建进程要为该进程分配资源

4.与进程之间切换相比,线程之间的切换需要OS做的工作要少得多

5.线程占有的资源比较少

6.计算密集型应用,可将计算分解到多个线程实现

7.I/O密集型应用,可将I/O操作重叠,线程可以同时等待不同的I/O操作

线程的操作

线程的创建

  • 继承Thread类
         private static class MyThread extends Thread {
             @Override
             public void run() {
                 for (int i = 0; i < 10; i++) {
                     System.out.println(i);
                 }
             }
         }
         
    	Thread a = new MyThread();
    	a.start();
    
        Thread c = new Thread(new MyThread());
        c.start();
    
  • 实现Runnable接口
         private static class MyRunnable implements Runnable {
             @Override
             public void run() {
                 for (int i = 0; i < 10; i++) {
                     System.out.println(i);
                 }
             }
         }
         
          Thread b = new Thread(new MyRunnable());
          b.start();
    
  • 其他变形
        public static void UseAnonymous() {
             //使用匿名类创建子类对象
            // == 直接创建线程对象
            Thread a = new Thread() {
                @Override
                public void run() {
                    //线程要执行的内容
                }
            };
    
            //使用匿名类创建 Runnable 子类对象
            // == 先创建目标对象,再创建线程对象
            Thread b = new Thread(new Runnable() {
                @Override
                public void run() {
                    //线程要执行的内容
                }
            });
    
            //使用lambda表达式创建 Runnable 子类对象
            Thread c = new Thread(() -> {
                //线程要执行的内容
            });
        }
    

Thread类

  • 构造方法
  • 属性
    Thread t = Thread.currentThread();  //获取当前线程
    System.out.println(t.getId()); //线程的唯一标识
    System.out.println(t.getName()); //线程的名称
    System.out.println(t.getState()); //线程的状态
    System.out.println(t.getPriority()); //线程的优先级
    System.out.println(t.isDaemon()); //是否为后台进程
                        //JVM会在所有非后台线程结束后,结束运行
    System.out.println(t.isAlive()); //是否存活,只有 NEW/TERMINATED 返回false
    System.out.println(t.isInterrupted()); //是否被中断
    
  • 常见方法
    • run()

      • 提供线程一个指令清单
    • start()

      • 启动线程,把线程放到就绪队列,使其拥有被调度的资格
    • 通知终止:interrupt()

      • 中断线程的另一种方式:共享标记,当子线程中有sleep时,无法实时响应

      • A线程通知B线程终止 配图

        • B线程正在sleep/join/wait

          • 通知是以InterruptedException给出
          • 状态位仍为false
        • B线程清醒(没有以上行为)

          • t 通过 t.isInterrupted() / Thread.interrupted()判断
          • t.isInterrupted() 状态位不变、指t这个线程、用于第三方线程查看B状态
          • / Thread.interrupted() 执行后将状态位改为false、指本线程、用于B自己查看
    • 等待停止:join()

      • 主线程阻塞在这里,等待该线程的结束
    • sleep()

      • 休眠当前线程
    • Thread.currentThread()

    • Thread.yield()

      • 主动放弃CPU,但保留争抢CPU的资格

线程的状态

状态转移

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-crvnAVNK-1590112772165)(E:\sust.onedrive\OneDrive - sust.edu.cn\Java复习\tmp图片\image-20200521213333239.png)]

线程安全

概念

  • 多线程情况下代码运行的情况是100%符合预期的,就是线程安全的

线程不安全

  • 出现线程不安全的原因
    • 线程被CPU调度具有不确定性
    • 线程被CPU调度下来具有不确定性
  • 条件
    • 多线程之间有共享资源

      • 哪些是共享的

        • [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8ItZi58j-1590112772172)(E:\sust.onedrive\OneDrive - sust.edu.cn\Java复习\tmp图片\image-20200521213400328.png)]
        • 变量的类型(基本/引用)不能决定该变量是否是线程共享的
        • [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Bcn8uDqz-1590112772175)(E:\sust.onedrive\OneDrive - sust.edu.cn\Java复习\tmp图片\image-20200521214824533.png)]
    • 且对共享资源有修改操作

  • 出现线程不安全的情况
    • automic原子性被破坏

      • 什么是原子性

        • 是指在CPU在执行一个操作时不可以在中途暂停然后再调度,不能被中断操作,要不执行完成,要不就不执行
      • 为什么会出现问题

        • 一条Java语句中不一定是原子的,也不一定只是一条指令

          • i++/i–:a.读取i的值 b.修改i的值 c.把i的值写回共享区域
          • Object o = new Object():a.分配空间 b.构造方法初始化 c.赋值给变量
          • 原子操作:变量 = 常量 (long/double除外)
        • 如果不保证原子性,当线程正对着变量操作,中间其他线程进来打断操作,就会造成错误的结果

      • 如何解决问题

        • synchronized

          • 保证方法和代码块内的操作是原子性的
        • volatile

          • 保证 long/double类型变量 = 常量 是原子性的
    • visible内存的可见性没有遵守

      • 什么是内存可见性

        • 当多个线程访问同一个变量时,一个线程修改了这个变量的值,其他线程能够立即看得到修改的值。
      • 为什么会出现问题

        • [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-geRD4xGV-1590112772179)(E:\sust.onedrive\OneDrive - sust.edu.cn\Java复习\tmp图片\image-20200521215002796.png)]
        • 高速缓存带来的问题:执行的中间值只保存在高速缓存中,内存中没有及时变化。如果有多个CPU的话,每个CPU有自己的高速缓存,而CPU共享数据是用内存的,某CPU当前计算的结果,其他CPU是看不到的
        • [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ZqwZnnQb-1590112772181)(E:\sust.onedrive\OneDrive - sust.edu.cn\Java复习\tmp图片\image-20200521215018321.png)]
        • JMM规定了所有的变量都存储在主内存中。每条线程中还有自己的工作内存,不同工作内存间是不可见的,工作内存中保存了线程需要的变量,不同线程间变量的传递需要经过主内存才可以;
        • 1.把数据从主内存中load到工作内存中 2.修改工作内存中的数据 3.在合适的时候,把数据save回主内存
        • 普通的共享变量则无法保证可见性,因为不同线程在各自工作内存中更改变量值后,再重新写回主内存的时间是不确定的,再当其他线程读取主内存值时,也可能是原来的旧值;
      • 如何解决问题

        • 变量被修改后立即同步到工作内存中

          • 保证主内存的数据最新
        • 同时把其他线程中的工作内存的该变量存为过期。目的使其他线程使用该变量时,必须从主内存中重新加载

          • 保证其他线程使用的数据最新
    • reorder代码重排序产生副作用

      • 什么是代码重排序

        • 指令的最终执行顺序可能不是代码的书写顺序。要求,不能因为代码重排序出现问题
      • 为什么代码重排序

        • 提升代码的执行效率,程序的代码,往往不是最优解
      • 谁在代码重排序

        • 1.编译期间 编译器 2.运行期间 JVM 3.运行期间 CPU指令
      • 为什么会出现问题

        • 多线程情况下会有问题
      • 如何解决问题

        • happen-befor
        • 某些机制限制了重排序的自由度

如何保证线程安全

  • 1.减少资源的共享。只要没有资源共享,线程就是安全的

  • 2.若必须共享,尽量不修改共享资源或者共享不可变对象。只要没有修改共享资源,线程就是安全的

  • 3.若必须对共享资源进行修改,通过某些机制保证

    • synchronized
      • 语法

        public class Synchronized {
            synchronized void 普通方法() {
                //同步代码块
            }
        
            void 普通方法2() {
                synchronized (this){
                    //同步代码块
                }
            }
        
            synchronized static void 静态方法() {
                //同步代码块
            }
        
            void 静态方法2() {
                synchronized (Synchronized.class){
                    //同步代码块
                }
            }
        
            void 其他方法() {
                Object o = new Object();
                synchronized (o) {
        
                }
            }
        }
        
        • 每个对象都有一把锁,对象是堆上内存区域的抽象
      • 过程

        • 线程AB共同争抢一把锁

          • A抢到锁,则B抢锁失败,B必须让出CPU且没有资格抢锁,B:Runnable->Blocked,就绪队列->阻塞队列
          • Aunlock,把当时因为抢这把锁失败的线程释放,如B:Blocked->Runnable(Ready),阻塞队列->就绪队列
      • 判断是抢的哪一把锁

        • [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3p5qx4x2-1590112772184)(E:\sust.onedrive\OneDrive - sust.edu.cn\Java复习\tmp图片\image-20200521215541174.png)]
      • 互斥

        • 都在抢锁且抢的是同一把锁
      • 从开始抢锁到抢到锁可能会很久

      • 作用

        • 原子性

          • 保证lock到unlock的这段时间不会被其他互斥线程中断
        • 适度的保证可见性

          • sync请求锁成功的时候,强制把当前的工作内存清掉,从主内存中重新读取
          • sync释放锁的时候,强制把当前锁的持有工作内存刷新到主内存中
        • 适度的影响重排序

    • volatile
      • 语法

        public class Volatile {
            volatile int a;
            static volatile int b;
        }
        
      • 作用

        • 修饰变量,保证long/double 变量 = 常数 具有原子性
        • 修饰变量,保证可见性
        • volatile Person p = new Person() 不允许重排序

等待唤醒机制

  • 语法

    •     public static void main(String[] args) throws InterruptedException {
              Object o = new Object();
              o.wait();
              o.notify();
              o.notifyAll();
          }
      
    • 必须配合synchronized使用

  • wait()

    • 作用

      • 当A线程调用 o.wait()后

        • A会放弃CPU,并且失去争抢CPU的资格
        • Runable->Waiting;就绪队列->o指向对象的等待集(wait-set)
        • 等待被线程唤醒
    • 执行流程

      • 更改线程状态
      • 把调用的线程放到o的等待集上
      • 会把o这把锁释放掉
      • 当前线程放弃CPU
      • 当前线程重新拥有CPU
      • 再次请求o这把锁,继续向下执行
      • 请求成功后,wait调用结束
  • notify()/notifyAll()

    • 作用

      • 当B线程调用 o.notify()后

        • B没有什么变化
        • o指向的等待集上的任意一个线程,Waiting->Runnable; 等待集->就绪队列
      • o.notifyAll()

        • 唤醒等待集上的所有线程
    • 注意点

      • 只会唤醒当前时间片正在休眠的线程
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值