1、内存模型:
在这里插入图片描述
2作用特性
2.1可见性
1volatile会发出【lock指令对当前cpu缓存】进行上锁,这样就会让当前cpu独占缓存,让其他cpu的此缓存段失效,因为加了lock,接下来操作的指令会独占数据写回【主存和缓存】,其他cpu因为缓存失效就会去【主存读数据重新写入缓存】,就保证了不同cpu之间的缓存一致性
不可见性举例:
package com.iteima.MutiThread;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
import static jdk.nashorn.internal.runtime.regexp.joni.Config.log;
/**
* @Author: wxf
* @Date: 2024/6/1516:38
* 多线程下变量发个文的不可见性【不及时同步共享】
*存在两个线程:
*
* 主线程:执行 main 方法的线程。
* 子线程(Mythread 实例):由主线程启动的新线程。
* //1、结果分析:
* thread进入主线程循环
* thread进入主线程循环
* thread进入主线程循环
* thread进入主线程循环
* thread进入主线程循环
* thread进入主线程循环
* thread进入主线程循环
* thread进入主线程循环
* thread进入主线程循环
* thread进入主线程循环
* thread进入主线程循环
* thread进入主线程循环
* 线程启动
* 主线程首先进入循环,并检查 flag 的值。此时,flag 是 false。
* 子线程启动,并设置 flag 为 true。但是,由于 flag 不是 volatile 的,这个更改可能对主线程不可见。
* 主线程循环继续执行,由于它没有看到 flag 的更改,所以继续执行循环体,打印 "thread进入主线程循环"。
* 经过若干次循环后,由于某种原因(可能是 JVM 内部的优化或缓存机制),主线程最终看到了 flag 变量的更改,循环终止。
* 子线程在设置 flag 后立即打印 "线程启动",并退出。
* //2、代码执行顺序与并发执行顺序
* 代码确实是按照顺序执行的,但在多线程环境中,不同的线程可以并发运行,它们之间的执行顺序不是预先确定的,而是受多种因素影响,包括线程调度、CPU 分配、以及线程间的同步机制等。
*
*
* 主线程执行顺序:
* 创建 Mythread 实例 t。
* 调用 t.start(),启动子线程。这会导致子线程的 run 方法被添加到线程调度队列中。
* 主线程继续执行,进入 while 循环。
* 子线程执行顺序:
* 子线程的 run 方法在被调度时执行。
* 在 run 方法中,首先将 flag 设置为 true。
*/
public class demo1 {
public static void main(String[] args) throws InterruptedException {
/*1\启动子线程*/
Mythread t=new Mythread();
t.start();
int i=100;
/*主线程*/
/* while(i>=0)
{
// Thread.sleep(1000);
// 调取成员变量
if( t.isFlag())
{
i--;
System.out.println("thread进入主线程循环"+i);
}
}*/
//
Runnable r1=()->System.out.println("运行线程LAMDA");
Runnable r2=new Runnable() {
@Override
public void run() {
System.out.println("runnable");
}
};
Thread t3=new Thread(r1,"线程");
t3.start();
FutureTask<Integer> futureTask=new FutureTask<>(() -> 100);
new Thread(futureTask,"future").start();
try {
Integer result=futureTask.get();
} catch (ExecutionException e) {
throw new RuntimeException(e);
}
}
}
class Mythread extends Thread
{
private boolean flag=false;//加载到主内存中
@Override
public void run() {
try {
sleep(100);//没进入while
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
/*触发修改共享成员变量*/
flag=true;
System.out.println("线程启动");
}
public boolean isFlag(){
return flag;
}
}
2.2禁止重排序
eg:
package com.iteima.MutiThread;
import static java.lang.Thread.sleep;
/**
* @Author: wxf
* @Date: 2024/6/1612:00
* JVM不保证重排序的安全性
*/
public class OutOfOrderDemo {
private volatile static int a=0,b=0;
public volatile static int i=0,j=0;
public static void main(String[] args) throws InterruptedException {
int count=0;
while(true)
{
a=0;b=0;
i=0;j=0;
count++;
Thread t1=new Thread(new Runnable() {
@Override
public void run() {
a=1;
i=b;
}
});
Thread t2=new Thread(new Runnable() {
@Override
public void run() {
b=1;
j=a;
}
});
// 1和t2不一定会按照它们在代码中编写的顺序执行。这是因为在多线程环境中,JVM不保证指令的顺序和执行顺序,除非显式地使用同步操作来确保顺序性。
// 因此,t1和t2可能会以不同的顺序执行其操作,这可能会导致意外的结果。
t1.start();
t1.join();//t1优先执行完毕,优先结束
t2.start();//这样测试发现没有出现i=1,j=1的
t2.join();
System.out.println("i="+i+"j="+j);
if( i==0&&j==1)
{
System.out.println("正常"+count);
}
else {
System.out.println("不正常============="+count+";"+i+" "+j);
break;
}
}
}
}
2.3不保证原子性
package com.iteima.MutiThread;
/**
* @Author: wxf
* @Date: 2024/6/1520:17
* 不保证原子性
*
*/
public class demo4_yuanzixing {
public static void main(String[] args) {
//
ThreadTarget target=new ThreadTarget();
for(int i=0;i<100;i++)
{
new Thread(target,"第"+i+"个线程").start();
}
}
}
//线程任务
class ThreadTarget implements Runnable{
private volatile int j=0;
@Override
public void run() {
/*触发修改共享成员变量*/
for(int i=0;i<100;i++)
{
j++;
}
System.out.println("线程启动"+";"+j);
/*结果:
* 。。。
* 线程启动;4899
线程启动;4899
线程启动;4899
线程启动;4999
线程启动;5099
线程启动;5199
线程启动;5299
线程启动;5399
线程启动;5499
线程启动;5599。。。
* */
}
}
解决1:
对多个线程要访问|操作的变量枷锁,每次都重新获得主存中的新的变量
package com.iteima.MutiThread;
/**
* @Author: wxf
* @Date: 2024/6/1520:39
*/
public class demo5_lock {
public static void main(String[] args) {
//
ThreadTarget2 target=new ThreadTarget2();
for(int i=0;i<100;i++)
{
new Thread(target,"第"+i+"个线程").start();
}
}
}
//线程任务
class ThreadTarget2 implements Runnable{
private int j=0;
@Override
public void run() {
/*触发修改共享成员变量*/
/*加了锁,只能有一个线程进入*/
synchronized (ThreadTarget2.class)//选择一个唯一对象,非原子操作增加锁
{
for(int i=0;i<100;i++)
{
j++;
}
System.out.println("线程启动"+";"+j);
}
}
}
解决2:原子类
package com.iteima.MutiThread;
import java.util.concurrent.atomic.AtomicInteger;
/**
* @Author: wxf
* @Date: 2024/6/1520:51
* 原子类实现变量原子性操作
*/
public class demo6_automic {
public static void main(String[] args) {
ThreadTarget02 target=new ThreadTarget02();
for(int i=0;i<100;i++)
{
new Thread(target,"第"+i+"个线程").start();
}
}
}
//线程任务
class ThreadTarget02 implements Runnable{
/*提供原子类*/
private AtomicInteger atomicInteger=new AtomicInteger(0);
@Override
public void run() {
for(int i=0;i<100;i++)
{
System.out.println("线程启动"+";"+atomicInteger.incrementAndGet());
}
/* *//*触发修改共享成员变量*//*
*//*加了锁,只能有一个线程进入*//*
synchronized (ThreadTarget02.class)//选择一个唯一对象,非原子操作增加锁
{
for(int i=0;i<100;i++)
{
System.out.println("线程启动"+";"+atomicInteger.incrementAndGet());
}
}*/
}
}
原子类;
【Unsafe 提供了原子操作】
Java 中
java.util.concurrent.atomic
包下的一个原子类,用于提供无锁的线程安全编程。
/*
* ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
* Written by Doug Lea with assistance from members of JCP JSR-166
* Expert Group and released to the public domain, as explained at
* http://creativecommons.org/publicdomain/zero/1.0/
*/
package java.util.concurrent.atomic;
import java.util.function.IntUnaryOperator;
import java.util.function.IntBinaryOperator;
import sun.misc.Unsafe;
/**
* An {@code int} value that may be updated atomically. See the
* {@link java.util.concurrent.atomic} package specification for
* description of the properties of atomic variables. An
* {@code AtomicInteger} is used in applications such as atomically
* incremented counters, and cannot be used as a replacement for an
* {@link java.lang.Integer}. However, this class does extend
* {@code Number} to allow uniform access by tools and utilities that
* deal with numerically-based classes.
*
* @since 1.5
* @author Doug Lea
*/
public class AtomicInteger extends Number implements java.io.Serializable {
private static final long serialVersionUID = 6214790243416807050L;
// setup to use Unsafe.compareAndSwapInt for updates
private static final Unsafe unsafe = Unsafe.getUnsafe();//Unsafe 是 Java 提供的一个用于执行低级别、不安全【这些操作通常不受 Java 内存模型的约束】操作的类,它可以用来直接操作内存【Unsafe 的方法操作的是 Java 对象的内存,而不是线程的内存。
它允许开发者绕过 Java 的一些安全检查(Unsafe 类的方法操作允许绕过 Java 的一些安全检查,主要包括:
类型检查:Java 虚拟机通常在执行期间对数据类型进行检查,以确保类型的正确性。使用 Unsafe 可以绕过这些检查,直接在内存中操作任意类型的数据。
可见性:Java 的内存模型确保变量的更改对所有线程可见。【Unsafe 允许直接在内存中修改变量】,而不经过这些保证可见性的机制。
原子性:虽然 【Unsafe 提供了原子操作】,但它也允许执行非原子的读写操作,这需要开发者自己确保操作的原子性。
同步:Java 的 synchronized 关键字和其他同步机制确保了在同一时刻只有一个线程可以访问特定的代码段。Unsafe 允许直接访问内存,绕过了这些同步机制。
访问控制:Java 的访问控制机制(如 private、protected)限制了对类成员的访问。Unsafe 可以无视这些访问控制,直接访问和修改对象的字段),直接在内存中读写数据。】和执行原子操作。
AtomicInteger 通过 Unsafe 来实现无锁的原子操作。
//compareAndSwapInt、getAndSetInt、getAndAddInt 等方法是 Unsafe 类提供的方法,它们利用了底层硬件的原子指令来保证操作的原子性。例如,compareAndSwapInt 方法在硬件层面上执行一个比较并交换的操作,这是原子的。【意味着它不需要使用 synchronized 或其他锁机制来保证线程安全,从而减少了线程争用时的上下文切换和调度开销。】
private static final long valueOffset;//valueOffset 是 value 成员变量在 AtomicInteger 对象内存中的偏移量。这是通过 Unsafe 的 objectFieldOffset 方法获得的。使用这个偏移量,Unsafe 可以直接访问和修改 value 变量的值。【每个字段在 Java 对象内存中都有一个特定的位置,valueOffset 就是 value 字段的起始位置偏移。通过这个偏移量加上对象的内存地址,Unsafe 可以定位到 value 字段的实际内存位置,并进行操作。】
static {
try {
valueOffset = unsafe.objectFieldOffset
(AtomicInteger.class.getDeclaredField("value"));
} catch (Exception ex) { throw new Error(ex); }
}
private volatile int value;//通过 volatile 关键字确保 value 变量的读写对所有线程立即可见。
/**
* 【!!】Creates a new AtomicInteger with the given initial value.
*
* @param initialValue the initial value
*/
public AtomicInteger(int initialValue) {
value = initialValue;
}
/**
* Creates a new AtomicInteger with initial value {@code 0}.
*/
public AtomicInteger() {
}
/**
* Gets the current value.
*
* @return the current value
*/
public final int get() {
return value;
}
/**
* Sets to the given value.
*
* @param newValue the new value
*/
public final void set(int newValue) {
value = newValue;
}
/**
* Eventually sets to the given value.
*
* @param newValue the new value
* @since 1.6
*/
public final void lazySet(int newValue) {
unsafe.putOrderedInt(this, valueOffset, newValue);
}
/**
* Atomically sets to the given value and returns the old value.
*
* @param newValue the new value
* @return the previous value
*/
public final int getAndSet(int newValue) {
return unsafe.getAndSetInt(this, valueOffset, newValue);
}
/**
* Atomically sets the value to the given updated value
* if the current value {@code ==} the expected value.
*
* @param expect the expected value
* @param update the new value
* @return {@code true} if successful. False return indicates that
* the actual value was not equal to the expected value.
*/
/**
*原子性保证:尽管 Unsafe 的操作本身是原子的,但 compareAndSet 方法提供了一个更高级的抽象,它不仅执行原子比较和交换,还返回操作是否成功的布尔值(开发者可以根据这个返回值来判断操作是否成功,并决定是否需要重试操作。这种重试机制通常通过循环实现,直到操作成功为止,这种模式有时被称为“自旋”(spinning)。自旋适用于锁争用不高的情况,以【避免线程阻塞和上下文切换的开销】。)。这使得开发者可以在更高层次上控制线程*安全,而不必担心底层的原子性实现细节。【提供了一个更易于使用的接口,它隐藏了直接使用 Unsafe 所需的复杂性,并在必要时可以进行额外的检查或处理。】
【Unsafe 类的 CAS 操作【并不能避免】其他线程同时对同一个变量进行操作。CAS(Compare-And-Swap)操作的原子性是基于一个前提的:在执行 CAS 操作期间,没有其他线程修改了变量的值。如果另一个线程在 CAS 操作的检查和交换步骤之间修改了变量的值,CAS 操作就会发现预期值与当前值不匹配,从而导致更新失败。
CAS 操作的执行步骤如下:
检查当前值:CAS 操作首先检查给定的内存位置的当前值是否与预期值相同。
交换新值:如果当前值与预期值相同,CAS 操作将该内存位置的值更新为新值。
返回结果:CAS 操作返回一个布尔值,指示操作是否成功(即,预期值是否匹配当前值)。
如果在步骤1和步骤2之间,其他线程更改了内存位置的值,那么CAS操作将执行以下操作:
失败情况:如果另一个线程更改了值,当 CAS 操作执行时,它会发现当前值与预期值不匹配,因此认为更新失败,并返回 false。【volatile修饰value,这里主要应用他的可见性!!】
重试机制:在这种情况下,执行 CAS 的线程可以选择重试操作,再次尝试更新变量。】
*/
public final boolean compareAndSet(int expect, int update) {
return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}
/**
* Atomically sets the value to the given updated value
* if the current value {@code ==} the expected value.
*
* <p><a href="package-summary.html#weakCompareAndSet">May fail
* spuriously and does not provide ordering guarantees</a>, so is
* only rarely an appropriate alternative to {@code compareAndSet}.
*
* @param expect the expected value
* @param update the new value
* @return {@code true} if successful
*/
public final boolean weak
CompareAndSet(int expect, int update) {
return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}
/**
* Atomically increments by one the current value.
*
* @return the previous value
*/
public final int getAndIncrement() {
return unsafe.getAndAddInt(this, valueOffset, 1);
}
/**
* Atomically decrements by one the current value.
*
* @return the previous value
*/
public final int getAndDecrement() {
return unsafe.getAndAddInt(this, valueOffset, -1);
}
/**
* Atomically adds the given value to the current value.
*
* @param delta the value to add
* @return the previous value
*/
public final int getAndAdd(int delta) {
return unsafe.getAndAddInt(this, valueOffset, delta);
}
/**
* Atomically increments by one the current value.
*
* @return the updated value
*/
public final int incrementAndGet() {
return unsafe.getAndAddInt(this, valueOffset, 1) + 1;
}
/**
* Atomically decrements by one the current value.
*
* @return the updated value
*/
public final int decrementAndGet() {
return unsafe.getAndAddInt(this, valueOffset, -1) - 1;
}
/**
* Atomically adds the given value to the current value.
*
* @param delta the value to add
* @return the updated value
*/
public final int addAndGet(int delta) {
return unsafe.getAndAddInt(this, valueOffset, delta) + delta;
}
/**
* Atomically updates the current value with the results of
* applying the given function, returning the previous value. The
* function should be side-effect-free, since it may be re-applied
* when attempted updates fail due to contention among threads.
*
* @param updateFunction a side-effect-free function
* @return the previous value
* @since 1.8
*/
public final int getAndUpdate(IntUnaryOperator updateFunction) {
int prev, next;
//循环结合 CAS 操作
//如果更新失败(通常是由于其他线程已经更改了值),则会重试操作,而不是进入阻塞状态。
即比较并交换(Compare-And-Swap)操作,是一种原子操作,用于在多线程环境中安全地更新变量。CAS 操作需要三个参数:预期值(expect)、新值(update)和要检查的变量地址。确保即使在其他线程同时修改变量的情况下,更新也能原子地完成。
do {
prev = get();
next = updateFunction.applyAsInt(prev);
} while (!compareAndSet(prev, next));
return prev;
}
/**
* Atomically updates the current value with the results of
* applying the given function, returning the updated value. The
* function should be side-effect-free, since it may be re-applied
* when attempted updates fail due to contention among threads.
*
* @param updateFunction a side-effect-free function
* @return the updated value
* @since 1.8
*/
public final int updateAndGet(IntUnaryOperator updateFunction) {
int prev, next;
do {
prev = get();
next = updateFunction.applyAsInt(prev);
} while (!compareAndSet(prev, next));
return next;
}
/**
* Atomically updates the current value with the results of
* applying the given function to the current and given values,
* returning the previous value. The function should be
* side-effect-free, since it may be re-applied when attempted
* updates fail due to contention among threads. The function
* is applied with the current value as its first argument,
* and the given update as the second argument.
*
* @param x the update value
* @param accumulatorFunction a side-effect-free function of two arguments
* @return the previous value
* @since 1.8
*/
public final int getAndAccumulate(int x,
IntBinaryOperator accumulatorFunction) {
int prev, next;
do {
prev = get();
next = accumulatorFunction.applyAsInt(prev, x);
} while (!compareAndSet(prev, next));
return prev;
}
/**
* Atomically updates the current value with the results of
* applying the given function to the current and given values,
* returning the updated value. The function should be
* side-effect-free, since it may be re-applied when attempted
* updates fail due to contention among threads. The function
* is applied with the current value as its first argument,
* and the given update as the second argument.
*
* @param x the update value
* @param accumulatorFunction a side-effect-free function of two arguments
* @return the updated value
* @since 1.8
*/
public final int accumulateAndGet(int x,
IntBinaryOperator accumulatorFunction) {
int prev, next;
do {
prev = get();
next = accumulatorFunction.applyAsInt(prev, x);
} while (!compareAndSet(prev, next));
return next;
}
/**
* Returns the String representation of the current value.
* @return the String representation of the current value
*/
public String toString() {
return Integer.toString(get());
}
/**
* Returns the value of this {@code AtomicInteger} as an {@code int}.
*/
public int intValue() {
return get();
}
/**
* Returns the value of this {@code AtomicInteger} as a {@code long}
* after a widening primitive conversion.
* @jls 5.1.2 Widening Primitive Conversions
*/
public long longValue() {
return (long)get();
}
/**
* Returns the value of this {@code AtomicInteger} as a {@code float}
* after a widening primitive conversion.
* @jls 5.1.2 Widening Primitive Conversions
*/
public float floatValue() {
return (float)get();
}
/**
* Returns the value of this {@code AtomicInteger} as a {@code double}
* after a widening primitive conversion.
* @jls 5.1.2 Widening Primitive Conversions
*/
public double doubleValue() {
return (double)get();
}
}