Java多线程简介

Java多线程概览

java的多线程是个比较高级的主题。正在学习python的多线程,同步复习以下java多线程的基础知识吧。

介绍一些概念,细节另外再慢慢探究吧。

Java多线程机制

Java语言内置对多线程的支持,在大数据、分布式、高并发类的主题上应用广泛。JVM控制线程的切换。

“进程是资源分配的最小单位,线程是CPU调度的最小单位置“。

主线程(main线程)

当JVM加载代码之后,找到main方法之后,就会启动一个第一个线程,称为”主线程(main线程)。该线程主要两个作用:执行main方法、创建新的线程。如果main方法中没有创建新的进程,那么执行完main中最后一条代码后JVM结束main线程,程序结束。而如果在main中创建了新的线程,那么JVM 就会控制新建的新程和main线程,使他们轮流切换,即使main线程结束,其他线程仍可能存在,JVM会在所有线程都结束后才结束整个程序。

单核和多核:

可以认为一个main线程产生意味着一个进程的产生,而一个核同时只能运行一个进程。单核情况下,多个进程轮流使用CPU,每个进程内部又轮流执行多个线程。而对于多核,同时可以运行多个进程,每个进程内部任然使多个线程轮流执行,但总的来看,多核情况下,有多个线程可以同步执行,这些线程来自不同的进程。

而python因为CPython解释器的GIL的原因,即使多核,也没法实现多个来自不同进程的线程的同步运行。

创建线程

本质上是一种方法,实现Runable接口,Runable是一个函数式接口。对于只有一个抽象方法的接口,需要这种接口时,可以提供一个lambda表达式,这样的接口称为函数式接口,它是一个可传递的代码块,可以由虚拟机在需要的适合调用。

实现Runable的基类是Thread类。

Java 不支持多继承,所以实现接口会比继承类更灵活。

Runable提供的唯一方法是run(),覆写这个方法即可。

线程的状态与生命周期

一个线程的完整生命周期通常要经历这样四个状态

1.新建:

​ 当一个线程对象被创建之后,新生的线程处于新建状态,拥有了相应的内存资源和其他资源。

2.运行:

​ 线程创建之后,只是占据资源,在JVM的线程管理中爱没有加入这个线程。调用start()方法之后通知JVM,这样JVM就会知道又有一个新线程排队等候切换。

3.中断

常见的中断原因:

  • JVM将CPU资源从当前线程切换给其他线程,这个当前线程会处于中断状态。
  • 线程使用CPU期间执行sleep(int millsecond)进入休眠。线程执行sleep方法后,会立刻让出CPU使用权,过了休眠时间后会重新加入JVM线程的队列中排队等待CPU资源。
  • 线程使用CPU期间,执行了wait()方法,,使得当前线程进入等待状态,等待状态的线程必须在被其他线程notify()或notifyAll()后通知后才能重新计入JVM线程队列。
  • 线程使用CPU期间,执行了某个操作进入了阻塞状态。进入阻塞状态的线程不能进入队列中排队,必须阻塞状态解除时才能进入队列

4死亡

​ 正常的线程完成了全部的工作;或者线程被强制性终止(避免这样的操作)。死亡后释放资源。

线程调度优先级

这个优先级制定级别,一共十级。注意这个优先级具有“随机性”,CPU会尽量将执行资源分配给高优先级的线程,但并公布代表高优先级的线程一定先执行完。

setPriority(int grade)设置优先级。grade在1~10之间。Thread.MIN、Thread.MAX、Thread.NORM_PRIORITY三个常量值,代表1、10、5(默认)。

目标对象与线程的关系

完全解耦

假设有线程house,控制房子中的一桶水,然后由new Thread(house)的方法构造出dog、cat线程,模拟狗和猫喝水的过程。这种情况下,无法获取目标对象的引用(house),所以获取线程的名字以便确定那个线程在执行。

String name = Thread.currentThread().getName()

弱解耦(组合)

在创建房子线程类的时候,包含dog和cat线程对象,可以获取目标对象的引用(house)。

Thread.currentThread()

两种方式各有优缺点(暂时还不是很清楚,设计模式的概念)

常用的几个方法

  1. start():使线程由新建状态进入JVM的线程队列,等待调度。只能调用一次,否则会抛出IlleaglThreadStateException异常。

  2. run():Runable这个函数式接口的唯一方法,需要重写,执行线程的功能。这个方法应该由JVM调用,如果主动调用,那它就是同步的(有顺序的)。

  3. sleep(int millsecond): 线程调度是有优先级的,优先级高的线程未死亡时低级别的线程没有获取CPU资源的机会。有时候优先级高的线程需要优先级低的线程执行一些工作配合,或者优先级高的线程需要执行一些费事的工作,此时应放优先级高的线程主动让出CPU资源。调用sleep方法后("假死",线程会休眠一段时间,此时低优先级低的线程可以执行。

    注意,如果线程在休眠的时候被interrupt()打断(吵醒),会抛出InterruptException异常,并解除休眠状态。捕获这个异常,即可会发线程,使其重新进入排队。

  4. isActive():start()调用之后,执行完任务之前的线程返回true。

    Thread thread = new Thread(dog);
    thread = new Thread(cat);
    
    

    这种情况下,newThread(dog)没有引用,是垃圾,但是不会被垃圾回收机制处理,JVM认为这个“垃圾”线程还在运行,释放的话可能会出问题。要格外注意:

    当引用的线程未死亡时,不变量不可重新关联一个新的线程引用。必须确保线程已经死亡,才可以分配新的线程引用。

  5. currentThread( ):是静态方法,返回当前线程的引用

  6. interrupt( ):吵醒休眠中的线程,捕获抛出的InterruptException异常并重新使线程进入队列。

线程同步

当多个线程访问同一个对象的变量并修改时,可能会出现数据不一致的问题(”非线程安全“)。比如银行卡里有100块,两个人同时用这种银行卡买一件60元的东西。查看余额时,都是一百元,于是都决定买,最后钱是不够的。

线程同步机制:当一个线程A使用synchronized方法时,其他线程想要使用这个synchronize方法时,必须等待A使用完。这有点类似于关系型数据库的事物机制。

协调同步的线程

那个排队买票凑零钱的问题挺经典的。售票员有两张5块的,电影票5元一张。张飞有20块,李逵有五块钱。排队时张飞在前,找不开。于是先暂停让李逵买,李逵买完就找得开了。

这个通过wait()和notify()\notifyAll()实现。

注意这三个方法都是Object类中的final方法,不可继承。

GUI线程

swing不是很常用,不熟悉,知道有两个比较重要的线程——AWT-Windows和AWT-EventQueues,分别控制事件处理和组件窗体的绘制,还有个计时器的概念,暂时跳过。

守护线程

线程默认是非守护线程,又叫user线程。对线程调用void setDaemon(boolean on)可以把线程设置为守护线程。

当用户线程都执行完毕之后,及时守护线程还没执行完,也会立刻停止。所以一般用守护线程做一些不是很严格的事,及时没执行完,对程序也不会产生不良影响。

posted @ 2019-05-29 02:13  落音  阅读(405)  评论(0编辑  收藏  举报