赞
踩
线程安全是指在多线程环境下,程序能够正确处理共享内存中的数据,而不会出现数据不一致、数据丢失、死锁等问题。在多线程环境下,多个线程可能会同时访问同一个共享变量或者对象,如果没有采取相应的措施,就可能出现线程安全问题。
为了保证线程安全,可以采取以下措施:
进程:1、进程是资源分配的最小单位。
2、 一个进程包含多个线程。
3、每个进程拥有独立的地址空间。
4、进程的创建、销毁和切换的开销比较大
线程:1、线程是cpu调度与分配的最小单位。
2、一个进程中的多个线程拥有相同的地址空间,各个线程共享进程中的所有资源。
3、线程的创建、销毁和切换的开销小
1、什么是死锁
是指两个或两个以上的线程在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象,若无外力作用,它们都将无法推进下去。
2、死锁的必要条件
3、什么是活锁
并未产生线程阻塞,但是由于某种问题的存在,导致无法继续执行的情况。
例如:消息一直重试导致其他消息无法进行。
相互协作的线程彼此相应从而修改自己的状态。
1、继承Thread类
- public class MyThread extends Thread(){
- @Override
- public void run(){
- for(int i=0;i<10;i++){
- System.out.println("MyThread执行+"i);
- }
- }
- }
- public class test {
- public static void main(String[] args) {
- MyThread thread = new MyThread();
- thread.start();
- }
- }
2、实现Runnable接口
- public class MyRunnable implements Runnable(){
- @Override
- public void run(){
- for(int i=0;i<10;i++){
- System.out.println("MyRunnable执行+"i);
- }
- }
- }
- public class test {
- public static void main(String[] args) {
- Thread thread = new Thread(new MyRunnable());
- thread.start();
- }
- }
3、实现Callable接口
- public class MyCallable implements Callable<String>(){
- @Override
- public String call() throws Exception{
- for(int i=0;i<10;i++){
- System.out.println("MyCallable执行+"i);
- }
- return "MyCallable执行成功!"
- }
- }
- public class test {
- public static void main(String[] args) {
- FutureTask<String> task = new FutureTask<String>(new MyCallable());
- Thread thread = new Thread(task);
- thread.start();
- }
- }
callable的call方法可以抛出异常,runnable的run方法不能抛出异常。
callable的call方法有返回值,runnable的run方法没有返回值。
新建:new关键字创建一个线程后,该线程为新建状态。
就绪:当线程调用start()方法后,该线程为就绪状态。
运行:当线程获得cpu资源,执行run()方法后,该线程为运行状态。
阻塞:1、当线程调用sleep()方法主动放弃所占用的处理器资源。
2、当线程试图获取一个同步锁,该同步锁被其他线程所持有。
3、当线程调用了一个阻塞式IO方法,在该方法返回之前,该线程是阻塞的。
4、当线程在等待某个通知(notify)
5、当程序调用了线程的suspend方法将该线程挂起。这个方法容易导致死锁。
死亡:1、run()或者call()方法执行完成,线程正常结束。
2、线程抛出一个未捕获的Exception或Error。
3、调用线程的stop()方法结束线程,容易产生死锁。
join讲解参考博客Java多线程中join方法的理解 - 每天进步一点点! - ITeye博客
JMM规范了Java虚拟机与计算机内存是如何协同工作的:规定了一个线程如何和何时可以看到由其他线程修改过后的共享变量的值,以及在必须时如何同步的访问共享变量。
java内存模型是java程序对内存的访问都能得到一致效果的机制与规范。每条线程有自己的工作内存,线程的工作内存中保存了该线程使用到的变量的主内存的副本拷贝,解决了由于多线程通过共享内存进行通信时存在的原子性、可见性(缓存一致性)以及有序性问题。java还提供了一些常用的关键字:synchronized、volatile、并发包等。
原子性:线程是CPU调度的基本单位,CPU有时间片的概念,会根据不同的调度算法进行调度,在多线程场景下,一系列操作可能没有都执行完就被要求放弃CPU,等待重新调度,这种情况下,这一系列操作就不是一个原子操作,就存在原子性问题。
可见性(缓存一致性):所有的变量都存在主存中,每个线程有自己的工作内存,里面保存了线程用到的变量的主内存副本拷贝,线程不能直接操作主内存。就可能存在缓存不一致的情况。
有序性:由于处理器优化和指令重排导致CPU可以对代码进行乱序执行,在多线程情况下就有可能出现有序性问题。
ReentrantLock是可重入的互斥锁,基于AQS的实现的,AQS是一个先进先出的双端队列,ReentrantLock维护了一个同步队列,线程在获取锁时,如果锁已经被占用,就会进入同步队列等待。如果同步队列不为空,那么队列的第一个节点就是当前持有锁的线程。获取锁时,会先尝试CAS操作修改同步状态,如果成功则获取锁,如果失败则加入同步队列并自旋等待锁的释放。
ReentrantLock定义了一个volatile int的变量计数器表示同步状态。每个线程在获取锁的时候都会将计数器加一,每次释放锁的时候都会将计数器减一。如果计数器为0,则标识锁已经完全释放,任何线程都可以对锁进行获取。
非公平锁:lock()
方法的逻辑: 多个线程调用lock()方法, 如果当前计数器
为0, 说明当前没有线程占有锁, 那么只有一个线程会CAS获得锁, 并设置此线程为独占锁线程。那么其它线程会调用acquire方法来竞争锁,acquire方法调用tryAcquire,成功获取锁返回true,不成功返回false,并把当前线程放到AQS队列中去。队列中的线程只能按顺序获取锁。
公平锁:lock()
方法的逻辑:不检查计数器
字段,直接调用acquire方法,直接放到AQS队列尾部,按顺序获取锁。
放到队列后如果队列为空的情况下不用挂起可以直接获取锁。
队列中的线程什么时候被唤醒:前驱节点是同步队列的头节点时被唤醒。
深入剖析ReentrantLock公平锁与非公平锁源码实现_研发之道的博客-CSDN博客
synchronized和volatile都是java中关键字。
synchronized:
volatile:
只能保证可见性,不能保证有序性、原子性。
为了提升计算机各个方面能力,在硬件层面做了一优化,比如指令重排序等,但是这些技术的引入就会导致有序性的问题。但是,在java中,不管怎么排序,都不能影响单线程程序的执行结果 ,就是遵守as-if-serial语义。当某个线程执行到一段被synchronized修饰的代码之前,会先进行加锁,执行完之后解锁,在加锁和解锁之间,其他线程是无法获得这段代码的锁,所以就保证了同一时间内,被synchroized修饰的代码是单线程执行的,满足as-if-serial语义的单线程的关键前提,所以可以保证有序性。
synchronized是基于对象的监视器锁来实现的,每一个对象都有一个监视器锁,当一个线程执行synchronize代码块时,会尝试获取这个对象的监视器锁。如果该锁没有被其他线程占用,则获取该锁并执行synchronize代码块,如果该锁被其他区线程占用,则该线程进入阻塞状态,直到获取锁为止。
monitor实现原理
对象与monitor怎么关联
参考Synchronized解析——如果你愿意一层一层剥开我的心 - 掘金
锁升级的过程:最开始是无锁状态,当有线程请求时是偏向锁,会偏向于第一个获得这个锁的线程,如果偏向锁不成功的话,这个时候锁会升级为轻量级锁,是用cas实现的锁,如果cas没有设置成功的话,他会进行一个自旋,自旋到一定次数的时候会升级为synchronized这样一个重量级锁。
10次
,用户可以使用参数-XX:PreBlockSpin
来更改。XX:+UseBiasedLocking
,这是JDK1.6的默认值),那么,当锁对象第一次被线程获取的时候,虚拟机将会把对象逆流而上中的标志位设为“01”。即偏向模式。同时使用CAS操作把获取到这个锁的线程ID记录在对象的Mark Word之中,如果CAS操作成功,持有偏向锁的线程以后每次进入这个锁相关的同步块时,虚拟机都可以不再进行任何同步操作。当有另外一个线程去尝试获取这个锁时,偏向模式就宣告结束。synchronized
和ReentrantLock
等独占锁就是悲观锁思想的实现。悲观锁先加锁,效率低。更新失败的概率比较低。适合读少写多的场景。java.util.concurrent.atomic
包下面的原子变量类就是使用了乐观锁的一种实现方式CAS实现的。乐观锁并未真正加锁,效率高。一旦锁的粒度掌握不好,更新失败的概率就会比较高,容易发生业务失败。适合读多写少的场景。乐观锁只能保证一个共享变量的原子操作。CAS 长时间不成功而一直自旋,会 给 CPU 带来很大的开销。参考既生synchronized,何生volatile-HollisChuang's Blog
1、Java并发包原子类automic
AtomicBoolean、AtomicInteger、AtomicLong、AtomicIntegerArray、AtomicLongArray、AtomicReferenceArray
原理:CAS(比较并交换)、volatile修饰变量。
CAS:它包含 3 个参数 CAS(V,E,N),V表示要更新变量的值,E表示预期值,N表示新值。仅当 V值等于E值时,才会将V的值设为N,如果V值和E值不同,则说明已经有其他线程完成更新,则当前线程则什么都不做,最后CAS 返回当前V的真实值。
AtomicInteger原理:AtomicInteger有一个volatile修饰的value属性,可以保证多个线程修改时能够看到最新的值。通过CAS来保证多个线程之间对value的操作是互斥的。
AtomicIntegerArray原理:AtomicIntegerArray内部维护了一个volatile修饰的数组,每个数组元素都是一个AtomicIntegerArray类型。
2、同步器CountDownLatch、CyclicBarrier、Semaphore
是基于AQS实现的,通过一个计数器来实现的,计数器的初始值是线程的数量。每当一个线程执行完毕后,计数器的值就-1,当计数器的值为0时,表示所有线程都执行完毕,然后在闭锁上等待的线程就可以恢复工作了。
是 ReentrantLock 和 Condition 的组合使用,在CyclicBarrier类的内部有一个计数器,每个线程在到达屏障点的时候都会调用await方法将自己阻塞,此时计数器会减1,当计数器减为0的时候所有因调用await方法而被阻塞的线程将被唤醒。CyclicBarrier有一个静态内部类Generation,该类的对象代表栅栏的当前代,就像玩游戏时代表的本局游戏,利用它可以实现循环等待。barrierCommand表示换代前执行的任务,当count减为0时表示本局游戏结束,需要转到下一局。在转到下一局游戏之前会将所有阻塞的线程唤醒,在唤醒所有线程之前你可以通过指定barrierCommand来执行自己的任务。
参考深入理解CyclicBarrier原理_晨初听雨的博客-CSDN博客_cyclicbarrier
线程可以通过acquire()方法来获取信号量的许可,当信号量中没有可用的许可的时候,线程阻塞,直到有可用的许可为止。线程可以通过release()方法释放它持有的信号量的许可。
在 Java 中,所谓的线程池中的“线程”,其实是被抽象为了一个内部类Worker,存放在线程池的HashSet<Worker> workers 成员变量中;而需要执行的任务则存放在成员变量 workQueue(BlockingQueue<Runnable> workQueue)中。这样,整个线程池实现的基本思想就是:从 workQueue 中不断取出 需要执行的任务,放在 Workers 中进行处理。
1、为什么要用线程池
2、线程执行过程
当一个任务被添加进线程池时:
3、线程池的参数
常用的workQueue类型:
拒绝策略
4、如何在 Java 线程池中提交线程
execute():没有返回值。
submit():有返回值,内部调用execute(),方便Exception处理。
5、线程池的关闭
ThreadPoolExecutor提供了两个方法,用于线程池的关闭,分别是shutdown()和shutdownNow(),其中:
shutdown():不会立即终止线程池,而是要等所有任务缓存队列中的任务都执行完后才终止,但再也不会接受新的任务
shutdownNow():立即终止线程池,并尝试打断正在执行的任务,并且清空任务缓存队列,返回尚未执行的任务
6、都有哪些已经实现的线程池
- public static ExecutorService newFixedThreadPool(int nThreads) {
- return new ThreadPoolExecutor(nThreads, nThreads,
- 0L, TimeUnit.MILLISECONDS,
- new LinkedBlockingQueue<Runnable>());
- }
- public static ExecutorService newSingleThreadExecutor() {
- return new FinalizableDelegatedExecutorService
- (new ThreadPoolExecutor(1, 1,
- 0L, TimeUnit.MILLISECONDS,
- new LinkedBlockingQueue<Runnable>()));
- }
CachedThreadPool 线 程 池:是无界线程池,如果线程池的大小超过了处理任务所需要的线程,那么就会回收部分空闲(60 秒不执行任务)线程,当任务数增加时,此线程池又可以智能的添加新线程来处理任务。 线程池大小完全依赖于操作系统(或者说 JVM)能够创建的最大线程大小。SynchronousQueue 是一个是缓冲区为 1 的阻塞队列。 缓存型池子通常用于执行一些生存期很短的异步型任务,因此适合生存期短的异步 任务。
- public static ExecutorService newCachedThreadPool() {
- return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
- 60L, TimeUnit.SECONDS,
- new SynchronousQueue<Runnable>());
- }
newScheduledThreadPool线程池:执行周期性任务,类似定时器。此线程 池支持定时以及周期性执行任务的需求。创建一个周期性执行任务的线 程池。
- public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
- return new ScheduledThreadPoolExecutor(corePoolSize);
- }
-
- //ScheduledThreadPoolExecutor():
- public ScheduledThreadPoolExecutor(int corePoolSize) {
- super(corePoolSize, Integer.MAX_VALUE,
- DEFAULT_KEEPALIVE_MILLIS, MILLISECONDS,
- new DelayedWorkQueue());
- }
threadlocal:为每个线程提供一个变量副本,从而实现访问互不干扰。多个线程之间数据相互隔离。
synchronized:只提供一份变量,让不同线程排队访问。多个线程访问资源同步。
threadlocalmap:
threadlocal内存结构:
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。