一键制作单页网站,郑州专业高校网站建设公司,视频课程网站建设,温州建设集团有限公司网站首页1.1 什么是并发编程的原子性
JMM#xff08;Java Memory Model#xff09;。不同的硬件和不同的操作系统在内存上的操作有一定差异的。Java为了解决相同代码在不同操作 系统上出现的各种问题#xff0c;用JMM屏蔽掉各种硬件和操作系统带来的差异。 让Java的并发编程可以做到…1.1 什么是并发编程的原子性
JMMJava Memory Model。不同的硬件和不同的操作系统在内存上的操作有一定差异的。Java为了解决相同代码在不同操作 系统上出现的各种问题用JMM屏蔽掉各种硬件和操作系统带来的差异。 让Java的并发编程可以做到跨平台。 JMM规定所有变量都会存储在主内存中在操作的时候需要从主内存中复制一份到线程内存CPU内存在线程内部做计 算。**然后再写回主内存中不一定。** 原子性的定义原子性指一个操作是不可分割的不可中断的一个线程在执行时另一个线程不会影响到他。 并发编程的原子性用代码阐述
private static int count;
public static void increment(){
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
count;
}
public static void main(String[] args) throws InterruptedException {
Thread t1 new Thread(() - {
for (int i 0; i 100; i) {
increment();
}
});
Thread t2 new Thread(() - {
for (int i 0; i 100; i) {
increment();
}
});
t1.start();
t2.start();
t1.join();t2.join();
System.out.println(count);
}
当前程序多线程操作共享数据时预期的结果与最终的结果不符。 原子性多线程操作临界资源预期的结果与最终结果一致。 通过对这个程序的分析可以查看出的操作一共分为了三部首先是线程从主内存拿到数据保存到CPU的寄存器中然后 在寄存器中进行1操作最终将结果写回到主内存当中。
1.2 保证并发编程的原子性
1.2.1 synchronized
因为操作可以从指令中查看到 [image] 可以在方法上追加synchronized关键字或者采用同步代码块的形式来保证原子性 synchronized可以让避免多线程同时操作临街资源同一时间点只会有一个线程正在操作临界资源 [image]
1.2.2 CAS
到底什么是CAS compare and swap也就是比较和交换他是一条CPU的并发原语。 他在替换内存的某个位置的值时首先查看内存中的值与预期值是否一致如果一致执行替换操作。这个操作是一个原子性操 作。 Java中基于Unsafe的类提供了对CAS的操作的方法JVM会帮助我们将方法实现CAS汇编指令。 但是要清楚CAS只是比较和交换在获取原值的这个操作上需要你自己实现。
private static AtomicInteger count new AtomicInteger(0);
public static void main(String[] args) throws InterruptedException {
Thread t1 new Thread(() - {
for (int i 0; i 100; i) {
count.incrementAndGet();
}
});
Thread t2 new Thread(() - {
for (int i 0; i 100; i) {
count.incrementAndGet();
}
});
t1.start();
t2.start();
t1.join();
t2.join();System.out.println(count);
}
Doug Lea在CAS的基础上帮助我们实现了一些原子类其中就包括现在看到的AtomicInteger还有其他很多原子类…… CAS的缺点CAS只能保证对一个变量的操作是原子性的无法实现对多行代码实现原子性。CAS的问题 ● ABA问题问题如下可以引入版本号的方式来解决ABA的问题。Java中提供了一个类在CAS时针对各个版本追加版 本号的操作。 AtomicStampeReference[image] ● AtomicStampedReference在CAS时不但会判断原值还会比较版本信息。
public static void main(String[] args) {
AtomicStampedReferenceString reference new AtomicStampedReference(AAA,1);
String oldValue reference.getReference();
int oldVersion reference.getStamp();
boolean b reference.compareAndSet(oldValue, B, oldVersion, oldVersion 1);
System.out.println(修改1版本的 b);
boolean c reference.compareAndSet(B, C, 1, 1 1);
System.out.println(修改2版本的 c);
}
● 自旋时间过长问题 ● 可以指定CAS一共循环多少次如果超过这个次数直接失败/或者挂起线程。自旋锁、自适应自旋锁 ● 可以在CAS一次失败后将这个操作暂存起来后面需要获取结果时将暂存的操作全部执行再返回最后的结果
1.2.3 Lock锁
Lock锁是在JDK1.5由Doug Lea研发的他的性能相比synchronized在JDK1.5的时期性能好了很多多但是在JDK1.6对 synchronized优化之后性能相差不大但是如果涉及并发比较多时推荐ReentrantLock锁性能会更好。 实现方式
private static int count;
private static ReentrantLock lock new ReentrantLock();
public static void increment() {
lock.lock();
try {
count;
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
} finally {
lock.unlock();
}
}
public static void main(String[] args) throws InterruptedException {
Thread t1 new Thread(() - {
for (int i 0; i 100; i) {
increment();
}
});
Thread t2 new Thread(() - {
for (int i 0; i 100; i) {
increment();
}
});
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println(count);
}
ReentrantLock可以直接对比synchronized在功能上来说都是锁。 但是ReentrantLock的功能性相比synchronized更丰富。 ReentrantLock底层是基于AQS实现的有一个基于CAS维护的state变量来实现锁的操作。
1.2.4 ThreadLocal
Java中的四种引用类型 Java中的使用引用类型分别是**强软弱虚**。 User user new User 在 Java 中最常见的就是强引用把一个对象赋给一个引用变量这个引用变量就是一个强引用。当一个对象被强引用变量引用 时它始终处于可达状态它是不可能被垃圾回收机制回收的即使该对象以后永远都不会被用到 JVM 也不会回收。因此强引 用是造成 Java 内存泄漏的主要原因之一。 SoftReference 其次是软引用对于只有软引用的对象来说当系统内存足够时它不会被回收当系统内存空间不足时它会被回收。软引用通常 用在对内存敏感的程序中作为缓存使用。 然后是弱引用它比软引用的生存期更短对于只有弱引用的对象来说只要垃圾回收机制一运行不管 JVM 的内存空间是否 足够总会回收该对象占用的内存。可以解决内存泄漏问题ThreadLocal就是基于弱引用解决内存泄漏的问题。 最后是虚引用它不能单独使用必须和引用队列联合使用。虚引用的主要作用是跟踪对象被垃圾回收的状态。不过在开发中 我们用的更多的还是强引用。 ThreadLocal保证原子性的方式是不让多线程去操作**临界资源**让每个线程去操作属于自己的数据 代码实现
static ThreadLocal tl1 new ThreadLocal();
static ThreadLocal tl2 new ThreadLocal();
public static void main(String[] args) {
tl1.set(123);
tl2.set(456);
Thread t1 new Thread(() - {
System.out.println(t1: tl1.get());
System.out.println(t1: tl2.get());
});
t1.start();
System.out.println(main: tl1.get());
System.out.println(main: tl2.get());
}
ThreadLocal实现原理 ● 每个Thread中都存储着一个成员变量ThreadLocalMap ● ThreadLocal本身不存储数据像是一个工具类基于ThreadLocal去操作ThreadLocalMap ● ThreadLocalMap本身就是基于Entry[]实现的因为一个线程可以绑定多个ThreadLocal这样一来可能需要存储多个数 据所以采用Entry[]的形式实现。 ● 每一个现有都自己独立的ThreadLocalMap再基于ThreadLocal对象本身作为key对value进行存取 ● ThreadLocalMap的key是一个弱引用弱引用的特点是即便有弱引用在GC时也必须被回收。这里是为了在 ThreadLocal对象失去引用后如果key的引用是强引用会导致ThreadLocal对象无法被回收 ThreadLocal内存泄漏问题 ● 如果ThreadLocal引用丢失key因为弱引用会被GC回收掉如果同时线程还没有被回收就会导致内存泄漏内存中的 value无法被回收同时也无法被获取到。 ● 只需要在使用完毕ThreadLocal对象之后及时的调用remove方法移除Entry即可