网站上广告动图是怎么做的,wordpress referer,保定市网站建设公司,canva在线设计平台Java 线程安全
什么是线程安全#xff1f;
当多个线程访问某个类时#xff0c;这个类始终都能表现出正确的行为#xff0c;那么就称这个类是线程安全的。
一#xff1a;基本概念
共享资源#xff1a;能够被多个线程同时访问的资源竞态条件#xff1a;当两个线程竞争统…Java 线程安全
什么是线程安全
当多个线程访问某个类时这个类始终都能表现出正确的行为那么就称这个类是线程安全的。
一基本概念
共享资源能够被多个线程同时访问的资源竞态条件当两个线程竞争统一资源时如果对资源的访问顺序敏感就称存在静态条件临界区导致竞态条件发生的代码区
原子性
一个操作包含多个子操作要么全部执行要么全部不执行。 例如银行转账A转给B 1000元那么需要执行A-1000B1000这操作必须是原子的
可见性 当多个线程并发访问共享变量时一个线程对共享变量的修改其它线程能立刻看到。
由于CPU和内存之前会有几层缓存所以会涉及到缓存更新算法由操作系统或硬件层面支持。
顺序性
顺序性指的是程序执行的顺序按照代码的先后顺序执行。
这里就涉及到JVM为了提高性能会对部分Java代码进行重排序所以程序执行的顺序不一定会按照代码的先后顺序执行。 但是JVM会保证在单线程情况下程序最终的执行结果和代码顺序执行一致。
二线程安全级别
1.不可变 像String、Integer、Long这些都是final类型的类任何一个线程都改变不了它们的值要改变除非新创建一个因此这些不可变对象不需要任何同步手段就可以直接在多线程环境下使用
2.绝对线程安全 不管运行时环境如何调用者都不需要额外的同步措施。要做到这一点通常需要付出许多额外的代价Java中标注自己是线程安全的类实际上绝大多数都不是线程安全的不过绝对线程安全的类Java中也有比方说CopyOnWriteArrayList、CopyOnWriteArraySet
3.相对线程安全
相对线程安全也就是我们通常意义上所说的线程安全像Vector这种add、remove方法都是原子操作不会被打断但也仅限于此如果有个线程在遍历某个Vector、有个线程同时在add这个Vector99%的情况下都会出现ConcurrentModificationException也就是fail-fast机制。
4.线程非安全 ArrayList、LinkedList、HashMap等都是线程非安全的类
三线程安全及解决方案
可能会存在线程安全的点
对可变共享资源的操作当前的锁对象
例如
public class Counter {protected long count 0;public void add(long value){this.count this.count value;}
}如果有两个线程A、B同时对Counter类的同一个实例上执行add方法A、B执行顺序是不可控的。
从内存中读取count值到CPU缓存CPU执行value操作并赋值给count写回count值到内存
由于上述过程并不是原子性的所以A、B线程交叉执行时很可能就会出问题。
Java如何保证原子性
锁和同步
用来保证Java操作的原子性的方式是锁和同步。 使用锁能够保证同一时间只有一个线程能拿到锁也就保证了同一时间只有一个线程能执行锁所保护的代码。 使用同步synchronized
synchronized修饰静态方法锁的是该类的Class对象synchronized修饰非静态方法锁的是该类的对象实例synchronized
CAS(Compare and swap) 基础类型变量自增i虽然代码只有一行但是并不是原子操作。Java中提供了对应的原子操作类来实现该操作并保证原子性其本质是利用了CPU级别的CAS指令。由于是CPU级别的指令其开销比需要操作系统参与的锁的开销小。AtomicInteger使用方法如下
public static void main(String[] args) throws InterruptedException {final AtomicInteger atomicInteger new AtomicInteger(0);int count 1000;final CountDownLatch countDownLatch new CountDownLatch(count);for(int i0;icount;i) {Thread thread new Thread(new Runnable() {public void run() {atomicInteger.incrementAndGet();countDownLatch.countDown();}});thread.start();}//等待所有线程执行完成countDownLatch.await();//此处输出必等于countSystem.out.println(atomicInteger.get());}Java如何保证可见性
Java提供了volatile关键字来保证可见性。当使用volatile修饰某个变量时它会保证对该变量的修改会立即更新到内存中并且将其他缓存中对该变量的缓存置为失效。因此其他线程读取该值时只能从主内存中读取从而保证可见性。
Java如何保证顺序性
上文讲过编译器和处理器对指令进行重新排序时会保证重新排序后的执行结果和代码顺序执行的结果一致所以重新排序过程并不会影响单线程程序的执行却可能影响多线程程序并发执行的正确性。
Java中可通过volatile在一定程序上保证顺序性另外还可以通过synchronized和锁来保证顺序性。
synchronized和锁保证顺序性的原理和保证原子性一样都是通过保证同一时间只会有一个线程执行目标代码段来实现的。
除了从应用层面保证目标代码段执行的顺序性外JVM还通过被称为happens-before原则隐式地保证顺序性。两个操作的执行顺序只要可以通过happens-before推导出来则JVM会保证其顺序性反之JVM对其顺序性不作任何保证可对其进行任意必要的重新排序以获取高效率。
happens-before原则先行发生原则
传递规则如果操作1在操作2前面而操作2在操作3前面则操作1肯定会在操作3前发生。该规则说明了happens-before原则具有传递性锁定规则一个unlock操作肯定会在后面对同一个锁的lock操作前发生。这个很好理解锁只有被释放了才会被再次获取volatile变量规则对一个被volatile修饰的写操作先发生于后面对该变量的读操作程序次序规则一个线程内按照代码顺序执行线程启动规则Thread对象的start()方法先发生于此线程的其它动作线程终结原则线程的终止检测后发生于线程中其它的所有操作线程中断规则 对线程interrupt()方法的调用先发生于对该中断异常的获取对象终结规则一个对象构造先于它的finalize发生