我是靠谱客的博主 忐忑钥匙,最近开发中收集的这篇文章主要介绍java学习笔记_进阶016,觉得挺不错的,现在分享给大家,希望可以做个参考。

概述

多线程

  • 多线程

    • 什么是进程?什么是线程?
      • 进程:进程是一个应用程序(1个进程是一个软件)

      • 线程:线程是一个进程中的执行场景/执行单元

      • 一个进程可以启动多个线程

      • 对于java程序来说,当在dos命令窗口中输入:java HelloWorld 回车之后,会先启动JVM,而JVM就是一个进程。JVM再启动一个主线程调用main方法,同时再启动一个垃圾回收线程负责看护,回收垃圾。最起码,现在的java程序中至少有两个线程并发。

        • 一个是垃圾回收线程,一个是执行main方法的主线程
    • 进程和线程是什么关系?举个例子
      • 阿里巴巴:进程
        • 马云:阿里巴巴的一个线程
      • 京东:进程
        • 刘强东:京东的一个线程
      • 进程可以看做是现实生活当中的公司
      • 线程可以看做是公司当中的某个员工
      • 注意点:
        • 进程A和进程B的内存独立不共享
        • 线程A和线程B呢?
          • 在java语言中:线程A和线程B,堆内存和方法区内存共享。但是栈内存独立,一个线程一个栈
          • 假设启动10个线程,会有10个栈空间,每个栈和每个栈之间互不干扰,各自执行各自的,这就是多线程并发。
            • 多线程并发可以提高效率
          • java中之所以有多线程机制,目的就是为了提高程序的处理效率。
    • 使用了多线程机制之后,main方法结束,是不是有可能程序也不会结束?
      • main方法结束只是主线程结束,主栈空了。其他的栈(线程)可能还在压栈弹栈。
      • main方法结束只代表主线程结束了,其它线程可能还在执行
    • 对于单核的CPU来说,真的可以做到真正的多线程并发吗?
      • 什么是真正的多线程并发
        • t1线程执行t1的,t2线程执行t2的。 t1不会影响t2,t2也不会影响t1,这叫做真正的多线程并发
      • 单核的CPU表示只有一个大脑
        • 不能够做到真正的多线程并发,但是可以做到给人一种多线程并发的感觉。
        • 对于单核的CPU来说,在某一个时间点上实际上只能处理一件事情,但是由于CPU的处理速度极快,多个线程之间频繁切换执行,给人的感觉是:多个事情同时在做。。。
      • 对于多核的CPU电脑来说,真正的多线程并发是没问题的。
        • 4核CPU表示同一个时间点上,可以真正的有4个线程并发执行。
    • java语言中,实现线程有两种方式,哪两种方式呢?
      • java支持多线程机制,并且java已经将多线程实现了,我们只需要继承就行了。
      • way1:编写一个类,直接继承java.lang.Thread,重写run方法
        • 怎么创建线程对象
          • new就行了
        • 怎么启动线程呢
          • 调用线程对象的start()方法。
            • start()方法的作用是:启动一个分支线程,在JVM中开辟一个新的栈空间,这段代码瞬间就结束
              • start()的任务只是为了开启一个新的栈空间,只要新的栈空间开出来,start()方法就结束。线程就启动成功了。‘

              • 启动成功的线程会自动调用run方法(不需要手动调用,是由JVM线程调度机制来运作的),并且run方法在分支栈的栈底部(压栈)

              • run方法在分支栈的栈底部,main方法在主栈的栈底部。run和main是平级的。

              • 调run方法而不是调用start()方法,不会启动线程,不会分配新的分支栈,这种方式就是单线程

              • 在分支线程中run方法必须写

      • way2:编写一个类,实现java.lang.Runnable接口,实现run方法
        • 定义一个可运行的类
        • 创建线程对象
        • 启动线程
      • 注意:第二种方式实现接口比较常用,因为一个类实现了接口,它还可以去继承其他的类,更灵活。
    • 线程生命周期
      • 新建状态---->调用start方法---->就绪状态<–JVM的调度—>运行状态----->run结束----->死亡状态
      • 就绪状态:就绪状态的线程又叫做可运行状态,表示当前线程具有抢夺CPU时间片的权利(CPU时间片就是执行权)。当一个线程抢夺到CPU时间片之后,就开始执行run方法,run方法的开始执行标志着线程进入运行状态
      • run方法的开始执行标志着这个线程进入运行状态,当之前占有的CPU时间片用完之后,会重新回到就绪状态继续抢夺CPU时间片。当再次抢到CPU时间之后,会重新进入run方法接着上一次的代码继续往下执行。
      • 线程在就绪状态和运行状态两个状态中频繁变化
      • 运行状态—>遇到阻塞事件---->进入阻塞状态—>阻塞解除—>就绪状态
      • 阻塞状态:当一个线程遇到阻塞事件,例如接收用户键盘输入,或者sleep方法等,此时线程会进入阻塞状态,阻塞状态的线程会放弃之前占有的CPU时间片
      • 运行状态—>synchronized—>锁池lockpool—>就绪状态
        • 锁池lockpool
          • 在这里找共享对象的对象锁线程进入锁池找共享对象的对象锁的时候,会释放之前占有的CPU时间片,有可能找到了,有可能没找到,没找到则在锁池中等待,如果找到了会进入就绪状态继续抢夺CPU时间片。
    • 关于线程对象的生命周期
      • 新建状态
      • 就绪状态
      • 运行状态
      • 阻塞状态
      • 死亡状态
    • 怎么获取当前线程对象?
      • static Thread currentThread();
      • Thread t = Thread.currentThread(); //返回值t就是当前线程
    • 获取线程对象的名字
      • 线程.getName();
    • 修改线程对象的名字
      • 线程.setName(“线程名字”);
    • 当线程没有设置名字的时候,默认的名字有什么规律?
      • Thread-0
      • Thread-1
      • Thread-2
    • 关于线程的sleep方法
      • static void sleep(long millis)
        • 静态方法static
        • 参数是毫秒
        • 作用:让当前线程进入休眠,进入“阻塞状态”,放弃占有CPU时间片,让给其它线程使用。这行代码出现在A线程中,A线程就会进入休眠;出现在B线程中,B线程就会进入休眠。
        • Thread.sleep()方法,可以做到这种效果
          • 间隔特定的时间,去执行一段特定的代码,每隔多久执行一次
      • 关于Thread.sleep()方法的一个面试题
        • 休眠详见下边的代码
      • 终止线程的睡眠
        • sleep睡眠太久了,如果希望半道上醒来,怎么办?
          • 注意:这个不是终断线程的执行,是终止断线程的睡眠
            • 使用interrupt方法来终断线程的睡眠,这种终断睡眠的方式依靠了java的异常处理机制
      • 强行终止线程的执行
        • way1:使用stop()方法(已过时,不建议使用)
          • 缺点:容易丢失数据。因为这种方式是直接将线程杀死了。线程没有保存的数据将会丢失。不建议使用。
        • way2:
          • 通过设计标记,在想终止的时候,将标记修改为false,就结束了。在else中,实现算法将需要的内容保存下来即可。
    • run()当中的异常不能throws,只能try catch.因为run()方法在父类中没有抛出任何异常,子类不能比父类抛出更多的异常
  • 线程调度概述(了解)

    • 常用的线程调度模型有哪些?
      • 抢占式调度模型
        • 哪个线程的优先级比较高,抢到的CPU时间片的概率就高一些/多一些
        • java采用的就是抢占式调度模型
      • 均分式调度模型
        • 平均分配CPU时间片,每个线程占有的CPU时间片时间长度一样。平均分配,一切平等。
    • java中提供了哪些方法是和线程调度有关系的呢?
      • 实例方法
        • void setPriority(int newPriority) 设置线程的优先级
        • int getPriority() 获取线程优先级
        • 最低优先级1,默认优先级5,最高优先级10
        • 优先级比较高的获取CPU时间片可能会多一些(但也不完全是,大概率是多的)
      • 静态方法
        • static void yield() 让位方法。暂停当前正在执行的线程对象,并执行其他线程。yield()方法不是阻塞方法,让当前线程让位,让给其他线程使用。yield()方法的执行会让当前线程从“运行状态”回到“就绪状态”。
    • 线程合并
      • 利用join()方法实现合并,比如:t.join(); //t合并到当前线程中,当前线程受阻塞,t线程执行直到结束。
        • 当前线程进入阻塞,t线程执行,直到t线程结束,当前线程才可以
  • 关于多线程并发环境下,数据的安全问题(重点)

    • 为什么这个是重点

      • 以后在开发中,我们的项目都是运行在服务器当中,而服务器已经将线程的定义,线程对象的创建,线程的启动等,都已经实现完了。
      • 这些代码不需要编写
        • 需要知道,自己编写的程序需要放到一个多线程的环境下运行,更需要关注的是这些数据在多线程并发的环境下是否是安全的(重点)
    • 什么时候数据在多线程并发的环境下会存在安全问题呢?

      • 三个条件
        • 条件1:多线程并发
        • 条件2:有共享数据
        • 条件3:共享数据有修改的行为
        • 满足以上三个条件之后,就会存在线程安全问题
    • 如何解决线程安全问题呢?

      • 当多线程并发的环境下,有共享数据,并且这个数据还会被修改,此时就存在线程安全问题,怎么解决这个恶棍问题?
        - 线程排队执行(不能并发)。用排队执行解决线程安全问题。这种机制被称为:线程同步机制
        - 专业术语叫做:线程同步,实际上就是线程不能并发了,线程必须排队执行
      • 怎么解决线程安全问题呢?
        • 使用“线程同步机制”
      • 线程同步就是线程排队了,线程排队了就会牺牲一部分效率,没办法,数据安全第一位,只有数据安全了,我们才可以谈效率。数据不安全,没有
        效率的事儿。
    • 哪些变量具有线程安全问题

      • java中的三大变量?
        • 实例变量:在堆中
        • 静态变量:在方法区中
        • 局部变量:在栈中
      • 以上三大变量中,局部变量永远都不会存在线程安全问题。因为局部变量不共享(一个线程一个栈)。局部变量在栈中,所以局部变量永远都不会共享。
      • 实例变量在堆中,堆只有一个。静态变量在方法区中,方法区只有一个。堆和方法区都是多线程共享的,所以可能存在线程安全问题。
      • 常量没有线程安全问题
      • 局部变量没有线程安全问题
      • 成员变量可能会有线程安全问题
      • 如果使用局部变量的话,建议使用:StringBuilder,因为局部变量不存在线程安全问题,选择StringBuilder。StringBuffer效率比较低。
      • ArrayList是非线程安全的,Vector是线程安全的。HashMap HashSet是非线程安全的,HashTable是线程安全的
    • 同步和异步

      • 同步编程模型

        • 线程t1和线程t2,在线程t1执行的时候,必须等待t2线程执行结束,或者说在t2线程执行的时候,必须等待t1线程执行结束,两个线程之间发生了等待关系,这就是同步编程模型,效率较低。线程排队执行
        • 同步代码块synchronized
          • 需要线程排队,不能并发。一个线程把这里的代码全部执行结束之后,另一个线程才能进来
            • 线程同步机制的语法是:
              synchronized(){
              //线程同步代码块
              }
              //synchronized后面小括号中的传的这个数据必须是多线程共享的数据,才能达到多线程排队
              • ()中写什么?
                • 那要看你想让哪些线程同步,假设t1,t2,t3,t4,t5有5个线程,你只希望t1,t2,t3排队,t4,t5不需要排队。怎么办?你一定要在()中写一个t1 t2 t3共享的对象。而这个对象对于t4 t5来说不是共享的。()中只要是多线程共享的那个对象就行。
                • 这个共享对象一定要选好了。这个共享对象一定是你需要排队执行的这些线程对象所共享的。
              • 同步代码块越长,执行的效率越低
              • 在实例方法上可以使用synchronized
                • synchronized出现在实例方法上,一定锁的是this。没得挑,只能是this。不能是其他的对象了,所以这种方式不灵活。另外一个缺点:synchronized出现在实例方法上,表示整个方法都需要同步,可能会无故扩大同步的范围,导致程序的执行效率降低,所以这种方式不常用。synchronized使用在实例方法上有什么优点:1)代码写到少,节俭了。2)如果共享的对象就是this,并且需要同步的代码块是整个方法体,建议使用这种方式。
                • 总结
                  • synchronized有两种写法
                  • 写法1:同步代码块,灵活
                    synchronized(线程共享对象){
                    //线程同步代码块
                    }
                  • 写法2:在实例方法上使用synchronized,表示共享对象一定是this,并且同步代码块是整个方法体
                  • 写法3:在静态方法上使用synchronized,表示找类锁。类锁永远只有1把。就算创建了100个对象,那类锁也只有一把。
                  • 对象锁:1个对象1把锁,100个对象100把锁
                  • 类锁:100个对象,也可能只是1把类锁
      • 异步编程模型

        • 线程t1和线程t2,各自执行各自的,t1不管t2,t2不管t1.谁也不需要等谁。这种编程模型叫做:异步编程模型。其实就是:多线程并发(效率较高)
  • 开发中应该怎么解决线程安全问题

    • 是一上来就选择线程同步吗?synchronized
      • 不是,synchronized会让程序的执行效率降低,用户体验不好。系统的用户吞吐量降低,用户体验差,在不得已的情况下再选择线程同步机制
    • 第一种方案:尽量使用局部变量代替“实例变量和静态变量”
    • 第二种方案:如果必须是实例变量,那么可以考虑创建多个对象,这样实例变量的内存就不共享了(一个线程对应一个对象,100个线程对应100个对象,对象不共享,就没有数据安全问题了)
    • 第三种方案**:如果不能使用局部变量,对象也不能创建多个,这个时候就只能选择synchronized了**。线程同步机制
  • 线程这块还有哪些内容呢?列举以下

    • 守护线程

      • 实现方式
        • 线程.setDaemon(true);
      • 守护线程就是后台线程,类似垃圾回收线程
      • java语言中线程分为两大类
        • 一类是:用户线程
        • 一类是: 守护线程(后台线程),其中具有代表性的就是:垃圾回收线程(守护线程)
      • 守护线程的特点
        • 一般守护线程是一个死循环。所有的用户线程只要结束,守护线程自动结束。
        • 注意
          • 主线程main方法是一个用户线程
      • 守护线程用在什么地方
        • 例子
          • 每天零点的时候,系统数据自动备份。这个需要使用到定时器,并且我们可以将定时器设置为守护线程。所有的用户线程如果结束了,守护线程自动退出,没有必要进行数据备份了。
    • 定时器

      • 定时器的作用
        • 间隔特定的时间,执行特定的程序
      • 方法
        • way1:采用sleep方法,睡眠,设置睡眠时间,每到这个时间点醒来,执行任务。这种方式是最原始的定时器(low)
        • way2:在java的类库中已经写好了一个定时器:java.util.Timer,可以直接拿来用。不过,这种方式在目前的开发中也很少用,因为现在有很多高级框架都是支持定时任务的。
        • way3:在实际的开发中,目前使用较多的是Spring框架中提供的SpringTask框架,这个框架只要进行简单的配置,就可以完成定时器的任务。
    • 实现线程的第三种方式:FutureTask方式,实现Callable接口(JDK8新特性)

      • 这种方式实现的线程可以获取线程的返回值。之前讲解的那两种方式是无法获取线程返回值的,因为run方法返回void
      • 思考
        • 系统委派一个线程去执行一个任务,该线程执行完任务之后,可能会有一个执行结果,我们怎么能拿到这个执行结果呢
          • 使用第三种方式:实现Callable接口方式
    • 关于Object类中的wait和notify方法(生产者和消费者模式

      • wait和notify方法

        • wait和notify方法不是线程对象的方法,是java中任何一个java对象都有的方法,因为这两个方法是Object类中自带的。
        • wait方法和notify方法不是通过线程对象调用的,而是通过java对象
      • wait()方法的作用

        • Object o = new Object();
          o.wait();
          表示:让正在o对象上活动的线程进入等待状态,无期限 等待,直到被唤醒为止。
        • o.wait();方法的调用,会让“当前线程(正在o对象上活动的线程)”进入等待状态
        • eg:
          • T线程在o对象上活动,T线程是当前线程对象,当调用o.wait()方法之后,T线程进入无期限等待。当前线程进入等待状态。直到最终调用o.notify()方法
          • o.notify()方法的调用可以让正在o对象上等待的线程唤醒。
      • notify()方法的作用

        • Object o = new Object();
          o.notify();
          //表示:唤醒正在o对象上等待的线程
        • notifyAll()方法:这个方法是唤醒o对象上处于等待的所有线程
      • 生产者和消费者模式

        • 生产者和消费者模式是为了专门解决某个特定需求的
        • 一个线程负责生产,一个线程负责消费,最终要达到生产和消费必须均衡
        • 仓库是多线程共享,所以需要考虑仓库的线程安全问题
        • 仓库对象最终调用wait和notify方法
        • wait方法和notify方法建立在synchronized线程同步的基础之上
        • 重点
          • o.wait()方法会让正在o对象上活动的当前线程进入等待状态,并且释放之前占有的o对象的锁
          • o.notify()方法只会通知,不会释放之前占有的o对象的锁
  • 亘古不变

    • 方法体当中的代码永远都是自上而下的顺序依次逐行执行的
    • 在java语言中,任何一个对象都有“一把锁”,其实这把锁就是标记(只是把它叫做锁。).100个对象,100把锁。1个对象1把锁。

最后

以上就是忐忑钥匙为你收集整理的java学习笔记_进阶016的全部内容,希望文章能够帮你解决java学习笔记_进阶016所遇到的程序开发问题。

如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(75)

评论列表共有 0 条评论

立即
投稿
返回
顶部