头部
logo
GT_WUBA

Java多线程

2019年10月06日 /By wuba/浏览量:76

线程内存模型

参考:

多线程-内存模型
java线程详解

概述:每个线程都有自己的工作内存,在JVM层面,包含:

  • 程序计数器

  • 线程栈


线程分类

常规划分为两类:

  • 用户线程:除守护线程都是用户线程

  • 守护线程:为用户线程提供一种通用的服务,典型如GC线程,当进程中不存在非守护线程,则守护线程会自动销毁

  • 用户线程转变为守护线程:用户线程在start之前可以通过setDaemo(true)来转变为守护线程。如果在start之后调用setDaemo(true),将会throw IllegalThreadStateException, 参考用户线程和守护线程


线程状态

参考:

线程的五种状态

线程可以分为5种状态:

  • 初始(new): 刚创建的线程

  • 就绪(Runnable): 线程被其他线程(如main线程)调用了该线程对象的start()方法,即会进入就绪线程池,等待cpu以随机的时间调用线程的run()方法,运行线程

  • 运行(Running): 线程获得了cpu 时间片(timeslice), 正在执行程序代码。

  • 阻塞(Blocked): 阻塞状态是指线程因为某种原因放弃了cpu 使用权,也即让出了cpu timeslice,暂时停止运行。直到线程进入可运行(runnable)状态,才有机会再次获得cpu timeslice 转到运行(running)状态。阻塞的情况分三种:

(一). 等待阻塞:运行(running)的线程执行o.wait()方法,JVM会把该线程放入等待队列(waitting queue)中。
(二). 同步阻塞:运行(running)的线程在获取对象的同步锁时,若该同步锁被别的线程占用,则JVM会把该线程放入锁池(lock pool)中。
(三). 其他阻塞:运行(running)的线程执行Thread.sleep(long ms)或t.join()方法,或者发出了I/O请求时,JVM会把该线程置为阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入可运行(runnable)状态。

  • 死亡(Dead) :线程run()、main() 方法执行结束,或者因异常退出了run()方法,则该线程结束生命周期。死亡的线程不可再次复生。


线程不安全

当多个线程对同一个对象的实例变量(全局变量),进行读写时,发生变量值不一致,在多个线程间值不同步的情况,进而影响程序执行流程


start()

  • 线程通过start()方法通知“线程规划器”此线程已经准备就绪,等待调用线程对象的run()方法运行线程

  • 如果有多个线程start,cpu对线程的调用是随机的,即执行start()方法的顺序不代表线程的启动顺序(具有异步执行的效果)

  • 如果调用thread.run()就不是异步执行,而是同步,那么此线程对象并不交给“线程规划器”来进行处理,而是由main主线程来调用run()方法,也就是必须等待run()方法中的代码执行完毕后才执行后面的代码

  • 线程执行run方法,即创建一个栈帧在自己的线程栈中


共享变量

  • 局部变量不是共享变量,当多个线程指向同一个线程对象时(该线程对象位于堆中),线程对象中的局部变量会在每个线程栈中单独读写,参考java中的变量类型

  • 共享变量,该变量位于堆区,多个线程都可以进行读写

  • 为了保证共享变量在多个线程间的可见性,可以通过synchronized和volatile关键字,锁机制等


synchronized关键字

  • synchronized关键字可以加在任意方法和对象前,即对它们上锁,而加锁的这段代码称为同步代码,解决多个线程之间访问资源的同步性(保证原子性和可见性)

  • 当一个运行状态的线程想要执行同步代码时,需要先拿到这上面的锁,如果拿不到(被别的线程占用),会和处于锁标志等待池(lock pool)的线程都会一直尝试争夺这把锁

  • synchronized取得的锁都是对象锁,如下:

    1: 修饰实例方法,锁为当前实例对象
    2: 修饰静态方法,锁为当前类的class对象
    3: 修饰代码块,锁可以设置this(当前实例对象),或是this.class(当前类的class对象),或是其他对象

  • synchronized锁重入,当一个线程得到一个对象锁后,再次请求此对象锁(同一把锁)时可以再次得到,如:

    1: 在一个synchronized方法/块的内部调用本地的其他synchronized方法/块时,是永远可以得到锁的
    2: 父子类继承关系中,子类可以通过可重入锁调用父类的同步方法

  • 深入理解Java并发之synchronized实现原理


volatile关键字

  • 当修饰共享变量时,可以保证变量在多线程下的可见性,不保证原子性

  • 当CPU发现是volatile修饰的共享变量时,会通知其他线程缓存的该变量无效,当其他线程读写该变量时,发现无效后会重新从主存中加载数据

  • 全面理解Java内存模型(JMM)及volatile关键字


线程中断

  • 停止线程即在一个线程未完成任务前放弃当前的操作

  • java中停止正在运行的线程:

    1:使用退出标志,之后当线程运行完run方法后线程终止
    2:使用stop()方法强行终止,不推荐,stop(),suspend(),resume()方法都是过期作废的
    3: 使用 return 停止异常,即run()方法中符合条件后return,也会终止线程
    4:使用异常法(推荐),如:

    (1)先调用线程的interrupt()方法,在运行run()方法中出现sleep(),会抛出InterruptedException异常,终止线程
    (2)先在run()方法中出现sleep()方法,之后被调用interrupt()方法,也会抛出InterruptedException异常
    (3)未处理的RuntimeException,发生异常后线程终止


wait(),notify(),notifyAll()

  • wait()方法即让当前线程释放对象琐,进入线程等待队列

  • notify()即从线程等待队列中移出任意一个线程放入锁池中,争抢对象锁

  • notifyAll()即将等待队列的所有线程移入锁池中


join()

  • join()会让所属线程X运行run方法,而让当前线程Y进入无限期阻塞,等待X销毁后,再继续执行Y后面的代码

  • 在Y的run()方法中执行X.join()后,如果此时被Z线程调用Y.interrupt(),则Y线程会抛出InterruptedException异常,X线程正常运行

  • join(long)内部采用的是wait(long),会让当前线程释放锁,而sleep(long)不会让当前线程释放锁,即调用sleep(long)后,其他线程不能执行当前线程的同步方法


ThreadLocal

  • ThreadLocal对象有get()和set()方法,会将放入其中的变量与调用set()方法的线程绑定,使每个线程都可以向其中存放自己的私有变量

  • InheritableThreadLocal类,可以让子线程获取到父线程存放在InheritableThreadLocal对象中的值


ReentrantLock类

  • 是一个java类,调用该类实例对象的 lock() 方 法获取锁,调用 unlock() 方法释放锁

  • 与synchronized的区别

  • Lock使用的是乐观锁,synchronized使用的是悲观锁,具体


Condition

  • 是一个类,通过Lock对象来创建

     Lock lock = new ReentrantLock();
     Condition condition = lock.newCondition();
  • await() , signal() , signalAll() 方法即实现线程间通信,注意:在调用它们前需要先调用 lock() 方法获得同步监视器,记得再通过 unlock() 方法释放


公平锁与非公平锁

  • 公平锁 表示线程获取锁的顺序是按照线程加锁的顺序来分配的,即先来先得的FIFO先进先出顺序

  • 非公平锁 是一种锁的抢占机制,是随机获取锁的

  • ReentrantLock类 默认 使用的是非公平锁

    //公平锁 
    ReentrantLock lock = new ReentrantLock(true); 
    //查看是否为公平锁 
    Boolean bo = lock.isFair() //true 
    //非公平锁 
    ReentrantLock lock = new ReentrantLock(false); 
    //查看是否为公平锁 
    Boolean bo = lock.isFair() //false

ReentrantReadWriteLock类

  • 类ReentrantLock同一时间只有一个线程执行ReentrantLock.lock()方法后面的任务,保证了实例变量的安全性,但是效率较低

  • 读写锁ReentrantReadWriteLock表示有两个锁,一个读操作相关的锁,是共享锁;一个是写操作相关的锁,是排他锁

  • 同一时刻多个线程能进行读操作,只有一个线程能进行写操作

    ReentrantReadWriteLock lock = new ReentrantReadWriteLock(); 
    //读锁 
    lock.readLock().lock(); 
    lock.readLock().unlock(); 
    
    //写锁 lock.writeLock().lock(); 
    lock.writeLock.unlock();