Java多线程基础
Posted wuba
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了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: 父子类继承关系中,子类可以通过可重入锁调用父类的同步方法
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对象中的值
以上是关于Java多线程基础的主要内容,如果未能解决你的问题,请参考以下文章