java多线程
来源:互联网 发布:数据库概念模式 编辑:程序博客网 时间:2024/06/06 08:42
基础概念
定义
进程:资源分配的最小单元,在java中就是一个程序;
线程:进程中可独立执行的子任务,是执行任务的最小单元,在JVM中,进程是线程的一个组件。
线程分为守护线程和用户线程。用户线程可以通过System.exit调用停止JVM的运行,也就是说在JVM停止之前,所有的用户线程必须先停止,而守护线程不会影响。
创建与运行
创建方式:1.继承java.lang.Thread类的实例;2.实现java.lang.Runnable接口
采用继承Thread类方式:
(1)优点:编写简单,如果需要访问当前线程,无需使用Thread.currentThread()方法,直接使用this,即可获得当前线程。
(2)缺点:因为线程类已经继承了Thread类,所以不能再继承其他的父类。
采用实现Runnable接口方式:
(1)优点:线程类只是实现了Runable接口,还可以继承其他的类。在这种方式下,可以多个线程共享同一个目标对象,所以非常适合多个相同线程来处理同一份资源的情况,从而可以将CPU代码和数据分开,形成清晰的模型,较好地体现了面向对象的思想。
(2)缺点:编程稍微复杂,如果需要访问当前线程,必须使用Thread.currentThread()方法。
运行:start方法而不是run方法
状态与上下文切换
状态(Thread.state):new,runnable,running,blocked,waiting,timed_waiting,terminated。
NEW:刚创建未启动,未执行start方法。
RUNNABLE:包含READY和RUNNING两个子状态,当调用yield方法时,或者线程调度器问题,相应的线程状态会有running转化成Ready。
BLOCKED:一个线程发起阻塞式IO或者试图获取一个由其他线程持有的锁时,相应的线程回处于改状态。改状态不占资源,当IO结束或者锁释放之后,改状态转化成RUNNABLE
WAITING:一个线程在执行某些方法之后,处于无限等待状态。比如object.wait(),thread.join(),LockSupport.park()。通过,Object.notify(),Object.notifyAll(),LockSupport.unpart(thread)唤醒线程。
TIMED_WAITING:改状态和waiting的差别在于处于该状态的线程,并非无限等待,而是由时间限制的等待,等超过时间就会被主动唤醒。
TERMINATED:已经执行结束的线程处于这个状态。
上下文切换是指存储和恢复cpu状态,使得程序能继续从断点处执行程序完成原先被中断的任务。
synchronized和volatile
指令重排列问题
原因:CPU数据计算流程:从内存读取数据–>CPU计算–>把计算结果写回内存。其中CPU与内存之间的IO速度,要比CPU的运算速度慢很多。为了解决这个问题,引入了高速缓存的概念,它一般位于内存与CPU之间,与CPU之间有较高的IO速度。因此就有了缓存命中率的问题。
int a = 0;a = a + 10;int b = 0;b = b + 5;a = a * 2;
以上代码,编译器认为在执行与a相关指令时,如第2行与第5行之间并没有其他与a有关的指令,因此,编译器的优化是在第2行执行完之后马上执行第5行,避免高速缓存的再次写入写出。也就可以发现,其实指令真正执行的顺序与所写的顺序并非一直。这就是指令的重排列问题。
java 内存结构及变量操作顺序
- lock(锁定)
作用于主内存的变量,它把一个变量标识为一个线程独占的状态;
- unlock(解锁)
作用于主内存的变量,它把一个处于锁定状态的变量释放出来,释放后的变量才可以被其他线程锁定;
- read(读取)
作用于主内存的变量,它把一个变量的值从主内存传送到线程中的工作内存,以便随后的load动作使用;
- load(载入)
作用于工作内存的变量,它把read操作从主内存中得到的变量值放入工作内存的变量副本中;
- use(使用)
作用于工作内存的变量,它把工作内存中一个变量的值传递给执行引擎;
- assign(赋值)
作用于工作内存的变量,它把一个从执行引擎接收到的值赋值给工作内存中的变量;
- store(存储)
作用于工作内存的变量,它把工作内存中的一个变量的值传送到主内存中,以便随后的write操作;
- write(写入)
作用于主内存的变量,它把store操作从工作内存中得到的变量的值写入主内存的变量中。
其中1,8与多线程有关。
线程调度关系
java采用的抢占式调用策略
多线程中考虑的问题
1.内存一致性:对于java内存之间其实包含两层内存,一层是高速缓存,另一层是内存。而对于某个变量,它在主内存中的值,应该和在线程工作内存中的值是一致的。
2.内存可见性: 某个变量,如果一个线程对它进行修改,那么其他线程应该能立即看到它的变化。
3.执行的有序性:如果一个线程A依赖另一个线程B的执行结果,那么在线程B看来是串行执行的指令(其实可能经过了指令重排),在线程A看来,就是一个错误的执行顺序。
Java 多线程之 synchronized 和 volatile 的比较
volatile 的原理
对于多线程编程来说,每个线程是可以拥有共享内存中变量的一个拷贝。如果一个变量被 volatile 关键字修饰时,那么对这的变量的写是将本地内存中的拷贝刷新到共享内存中;对这个变量的读会有一些不同,读的时候是无视他的本地内存的拷贝的,只是从共享变量中去读取数据。volatile只能保证内存的可见性,但是不能保证执行的原子性。
synchronized原理
我们说 synchronized 实际上是对变量进行加锁处理。那么不管是读也好,写也好都是基于对这个变量的加锁操作。如果一个变量被 synchronized 关键字修饰,那么对这的变量的写是将本地内存中的拷贝刷新到共享内存中;对这个变量的读就是将共享内存中的值刷新到本地内存,再从本地内存中读取数据。因为全过程中变量是加锁的,其他线程无法对这个变量进行读写操作。所以可以理解成对这个变量的任何操作具有原子性,即线程是安全的。正因为synchronized使得变量操作进行阻塞式操作,这就保证了只有一个线程对变量进行操作,也就使得变量的修改对内存是可见的,同时针对某一块代码块来说,只有一个线程对其进行执行,因此对其进行指令重排列不会影响执行的正确性。
综上所述,对于synchronized和volatile之间的区别,synchronized保证了原子性和内存可见性,并且在支持重排列,而volatile只保证了内存可见性,无法保证原子性,并且禁止了指令重排列。此外synchronized引起了上下文切换,而volatile没有,性能上synchronized开销更大。
ThreadLocal
ThreadLocal用于创建线程的本地变量,一个对象的所有线程会共享它的全局变量,所以这些变量不是线程安全的,我们可以使用同步技术。但是当我们不想使用同步的时候,我们可以选择ThreadLocal变量。
每个线程都会拥有他们自己的Thread变量,它们可以使用get()\set()方法去获取他们的默认值或者在线程内部改变他们的值。ThreadLocal实例通常是希望它们同线程状态关联起来是private static属性。
守护线程和用户线程
用户线程:我们在java程序中创建的线程
守护线程:程序后台的线程
线程优先级
可以自己给每个线程设置优先级,但并不能保证高优先级的线程先执行,主要还是看操作系统中的调度算法。
线程之间的通信
object类中的notify,notifyAll,wait方法能作为线程通信时,可以作为竞争资源的锁的状态。
之所以将这些方法放在object中,而不是将它们放在Thread中,主要的原因是java提供的锁是对象级的锁,而不是线程级的锁。如果将这些方法放在Thread中,那么当Thread进行wait时,整个线程不清楚因为什么状态进行等待了,而要是设置在对象中,那可以明确的知道是因为哪个对象使得线程被阻塞。
Thread中yield和sleep方法
yield方法是指放弃资源竞争的意思,sleep是指线程愿意等待一段时间之后。这对于正在运行的线程来说是没有意义的,因此这些方法不会出现在具体的某个实例中,只属于Thread类,是静态的。
线程安全
安全等级
不可变
可以是基本类型的final;可以是final对象,但对象的行为不会对其状态产生任何影响,比如String的subString就是new一个String对象各种Number类型如BigInteger和BigDecimal等大数据类型都是不可变的,但是同为Number子类型的AtomicInteger和AtomicLong则并非不可变。原因与它里面状态对象是unsafe对象有关,所做的操作都是CAS操作,可以保证原子性。
绝对线程安全
线程运行不需要与外部进行交互,不需要线程同步操作。
相对线程安全
保证对象的基本操作是线程安全的,比如Vector,HashTable,synchronizedCollection包装集合等。
线程兼容
对象本身不是线程安全的,但是可以同步实现,比如ArrayList,HashMap等。
线程对立
不管调用端是否采用了同步的措施,都无法在并发中使用的代码。
实现方式
互斥同步
在多线程访问的时候,保证同一时间只有一条线程使用。临界区(Critical Section),互斥量(Mutex),信号量(Semaphore)都是同步的一种手段。
java里最基本的互斥同步手段是synchronized,编译之后会形成monitorenter和monitorexit这两个字节码指令,这两个字节码都需要一个reference类型的参数来指明要锁定和解锁的对象,还有一个锁的计数器,来记录加锁的次数,加锁几次就要同样解锁几次才能恢复到无锁状态。
其实在“Java与线程”里已经提到,java的线程是映射到操作系统的原生线程之上的,不管阻塞还是唤醒都需要操作系统的帮忙完成,都需要从用户态转换到核心态,这是很耗费时间的,是java语言中的一个重量级(Heavyweight)操作,虽然虚拟机本身会做一点优化的操作,比如通知操作系统阻塞之前会加一段自旋等待的过程,避免频繁切换到核心态。
ReentrantLock相比于synchronized的优势:
等待可中断:在持有锁的线程长时间不释放锁的时候,等待的线程可以选择放弃等待.
公平锁:按照申请锁的顺序来一次获得锁称为公平锁.synchronized的是非公平锁,ReentrantLock可以通过构造函数实现公平锁.new RenentrantLock(boolean fair)
锁绑定多个条件:通过多次newCondition可以获得多个Condition对象,可以简单的实现比较复杂的线程同步的功能.通过await(),signal();
非阻塞同步
互斥和同步最主要的问题就是阻塞和唤醒所带来的性能问题,所以这通常叫阻塞同步(悲观的并发策略)。随着硬件指令集的发展,我们有另外的选择:基于冲突检测的乐观并发策略,通俗讲就是先操作,如果没有其他线程争用共享的数据,操作就成功,如果有,则进行其他的补偿(最常见就是不断的重试),这种乐观的并发策略许多实现都不需要把线程挂起,这种同步操作被称为非阻塞同步。
这类的指令有:
1)测试并设置(test-and-set)
2)获取并增加
3)交换
4)比较并交换(CAS)
5)加载链接/条件储存(Load-Linked/Store-Conditional LL/SC)
后面两条是现代处理器新增的处理器指令,在JDK1.5之后,java中才可以使用CAS操作,就是传说中的sun.misc.Unsafe类里面的compareAndSwapInt()和compareAndSwapLong()等几个方法的包装提供,虚拟机对这些方法做了特殊的处理,及时编译出来的结果就是一条平台相关的处理器CAS指令,没有方法调用的过程,可以认为是无条件的内联进去。
原来需要对i++进行同步,但现在有了这种CAS操作来保证原子性,比如用AtomicInteger。 但是CAS存在一个ABA的问题。可以通过AtomicStampedReference来解决(鸡肋)。
无同步
有一些代码天生就是线程安全的,不需要同步。其中有如下两类:
- 可重入代码(Reentrant Code)
纯代码,具有不依赖存储在堆上的数据和公用的系统资源,用到的状态量都由参数中传入,不调用非可重入的方法等特征,它的返回结果是可以预测的。 - 线程本地存储(Thread Local Storage)
把共享数据的可见范围限制在同一个线程之内,这样就无须同步也能保证线程之间不出现数据争用问题。可以通过java.lang.ThreadLocal类来实现线程本地存储的功能。
线程池
假设一个服务器完成一项任务所需时间为:T1 创建线程时间,T2 在线程中执行任务的时间,T3 销毁线程时间。如果:T1 + T3 远大于 T2,则可以采用线程池,以提高服务器性能。
一个线程池包括以下四个基本组成部分:
1、线程池管理器(ThreadPool):用于创建并管理线程池,包括 创建线程池,销毁线程池,添加新任务;
2、工作线程(PoolWorker):线程池中线程,在没有任务时处于等待状态,可以循环的执行任务;
3、任务接口(Task):每个任务必须实现的接口,以供工作线程调度任务的执行,它主要规定了任务的入口,任务执行完后的收尾工作,任务的执行状态等;
4、任务队列(taskQueue):用于存放没有处理的任务。提供一种缓冲机制
优点
1、降低资源消耗;
2、提高响应速度;
3、提高线程的可管理性
深入分析java线程池的实现原理
jdk1·5开始引入的Executor框架把任务的执行和提交进行解耦,只需要定义好任务,交给线程就好,不需要关心任务是如何执行,由哪个线程进行管理。
其他问题
同步方法与同步代码块
同步代码块更好,同步方法会锁住整个对象,哪怕这个类中有多个不相关联的同步块,这通常会导致他们停止执行并需要等待获得这个对象上的锁。
- 【Java多线程】多线程死锁
- Java 多线程
- java 多线程
- java多线程
- JAVA多线程
- java多线程
- JAVA多线程
- java多线程
- JAVA 多线程
- Java多线程
- java多线程
- JAVA 多线程
- Java 多线程
- Java 多线程
- java多线程
- Java 多线程
- Java多线程
- java 多线程
- CentOS g++: Internal Error: Killed (Program Cc1plus)
- Python中startswith和endswith的用法
- leetcode 486. Predict the Winner
- 【实战经验分享】如何对SSD固态硬盘下发SCSI command?
- 设计模式学习—适配器模式(Adapter Design Pattern)
- java多线程
- 解决IE低版本浏览器兼容性差问题
- 命令代换``和$()的学习以及eval命令的了解
- Selenium在python的操作
- ch追妹(思路题 杭电排位赛-2)
- 《剑指offer》扑克牌顺序
- 2017.7.18记
- HBase region 定位原理
- Leetcode-字符串问题--最长的公共子字符串长度--可不连续--#583