并发编程-实战篇(各类工具的使用)

目录

1、分而治之(Fork/join)

2、CountDownLatch

3.CyclicBarrirer的使用

4、 信号量 Semaphore   

5、Exchange的使用

6.Runnable ,future ,futuretask,和callable关系

7、原子操作

2.原子操作可以通过锁机制和CAS来实现

3.CAS和synchronized的区别

4.CAS的基本实现思想

5.CAS的缺点

6.原子操作相关java类

7.常用原子操作实战

8、显示锁


1、分而治之(Fork/join)

1.规模为N的问题,N<阈值,直接解决,N>阈值,将N分解为K个小规模子问题,子问题互相对立,与原问题形式相同,将子问题的解合并得到原问题的解。

Fork/Join框架是Java 7提供的一个用于并行执行任务的框架,是一个把大任务分割成若干个小任务,最终汇总每个小任务结果后得到大任务结果的框架。Fork/Join框架要完成两件事情:

  (1)任务分割:首先Fork/Join框架需要把大的任务分割成足够小的子任务,如果子任务比较大的话还要对子任务进行继续分割

  (2)执行任务并合并结果:分割的子任务分别放到双端队列里,然后几个启动线程分别从双端队列里获取任务执行。子任务执行完的结果都放在另外一个队列里,启动一个线程从队列里取数据,然后合并这些数据。

2.fork/join使用的标准范式

标准范式的意思,就是按照这个模板样子来用forkjoin就是了。

ForkJoinTask有两个子类,RecursiveAction和RecursiveTask。他们之间的区别是,RecursiveAction没有返回值,RecursiveTask有返回值。

(1)使用 RecursiveTask拿到返回值

首先按照范式,新建我们的Mytask,来做求和功能 命名为MySumtask,这里我们继承RecursiveTask来拿到返回值。将求和分成一个个更小的求和计算,然后通过汇总其结果达到求和的目的。

public class MySumTask extends RecursiveTask<Integer> {
	private final int THRESHOLD = MakeArray.ArrayLength / 10;// 门槛值,也就是分而治之的最小单位
	private int[] src;
	private int fromIndex;
	private int toIndex;

	MySumTask(int fromIndex, int toIndex, int[] src) {
		this.src = src;
		this.fromIndex = fromIndex;
		this.toIndex = toIndex;
	}

	@Override
	protected Integer compute() {
		if (toIndex - fromIndex < THRESHOLD) {
			int count = 0;
			for (int i = fromIndex; i<=toIndex; i++) {
				count += src[i];
			}
			return count;
		} else {
			int mid = (toIndex + fromIndex) / 2;
			MySumTask sumtaskLeft = new MySumTask(fromIndex, mid, src);
			MySumTask sumtaskRight = new MySumTask(mid + 1, toIndex, src);
			invokeAll(sumtaskLeft, sumtaskRight);
			return sumtaskLeft.join() + sumtaskRight.join();
		}
	}

	public static void main(String[] args) {
		ForkJoinPool pool= new ForkJoinPool();
		int []src =MakeArray.makeArray();
		MySumTask mytask=new MySumTask(0,src.length-1,src);
		pool.invoke(mytask);
		int count =mytask.join();
		System.out.println("计算结果是:"+count);
	}
}

工具类

public class MakeArray {
	public static final int ArrayLength = 10000;

	public static int[] makeArray() {
		int[] arr = new int[ArrayLength];
		for (int i = 0; i < ArrayLength; i++) {
			arr[i] = i;
		}
		return arr;
	}
}

测试结果如下:

计算结果是:49995000;

(2)使用RecursiveAction来遍历磁盘目录

public class FindTxt extends RecursiveAction {
	private File path;// 当前任务需要搜寻的目录

	FindTxt(File path) {
		this.path = path;
	}

	@Override
	protected void compute() {
		File[] files = path.listFiles();
		List<FindTxt> subTasks = new ArrayList<>();
		if (files != null) {
			for (File file : files) {
				if (file.isDirectory()) {
					subTasks.add(new FindTxt(file));
				} else {
					// 遇到文件,检查
					if (file.getAbsolutePath().endsWith("txt")) {
						System.out.println("文件:" + file.getAbsolutePath());
					}
				}
			}
		}
		invokeAll(subTasks);
		for (FindTxt fd : subTasks) {
			fd.join();
		}
	}

	public static void main(String[] args) throws InterruptedException {
		ForkJoinPool pool = new ForkJoinPool();
		FindTxt fdf = new FindTxt(new File("D:/work/aboutWorkSoft"));
		pool.invoke(fdf);//pool.execute(fdf)是异步调用
		System.out.println("你看他是不是同步调用");
		fdf.join();
	}
}

测试结果

文件:D:\work\aboutWorkSoft\localization\draw.io\war\resources\dia_zh-tw.txt
文件:D:\work\aboutWorkSoft\com.polarion.alm.ui.diagrams.mxgraph_3.17.2\draw.io\war\resources\dia_zh.txt
你看他是不是同步调用

pool.invoke是同步调用,pool.execute是异步调用,这里的同步和异步是什么 意思呢

同步就是pool相关逻辑执行完,main线程后面的逻辑才执行。

异步就是pool相关逻辑和main后面的逻辑一起执行。

把pool.invoke改成pool.execute可以得到执行结果如下:

你看他是不是同步调用

文件:D:\work\aboutWorkSoft\localization\draw.io\war\resources\dia_zh-tw.txt
文件:D:\work\aboutWorkSoft\com.polarion.alm.ui.diagrams.mxgraph_3.17.2\draw.io\war\resources\dia_zh.txt


2、CountDownLatch

  可以理解成并发编程中的顺序计数器,闭锁,可以用来确保某些活动直到其他活动都完成后才继续执行,例如:

  • 确保某个计算在其需要的所有资源都被初始化之后才继续执行。
  • 确保某个服务在其依赖的所有其他服务都已经启动之后才启动。
  • 等到某个操作的所有参与者都就绪再继续执行。

先看看实例

public class CountDownLatchTest {
	static CountDownLatch latch = new CountDownLatch(3);

	static class Thread1 extends Thread {
		@Override
		public void run() {
			try {
                System.out.println("Thread1.......is....prepare");
				latch.await();
				System.out.println("Thread1.......is....running");
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
	}

	static class Thread2 extends Thread {
		@Override
		public void run() {
			System.out.println("Thread2 is running");
			latch.countDown();
		}
	}

	static class Thread3 extends Thread {
		@Override
		public void run() {
			System.out.println("Thread3 is running");
			latch.countDown();
		}
	}

	public static void main(String[] args) {
		System.out.println("main Thread is running");
		latch.countDown();
		Thread thread1 = new Thread1();
		Thread thread2 = new Thread2();
		Thread thread3 = new Thread3();
		thread1.start();
		thread2.start();
		thread3.start();
	}
}

测试结果如下:

main Thread is running
Thread1.......is....prepare
Thread2 is running
Thread3 is running
Thread1.......is....running

所有具有latch.await()的线程中的后续逻辑,必须在latch的countDown执行了规定次数3次的时候,才会执行。latch的countDown可以在一个线程中执行多次扣减。

使用CountDownLatch可以通过特定的使用,来达到调整线程执行的先后顺序

3.CyclicBarrirer的使用

 栅栏类似于闭锁,它能阻塞一组线程直到某个事件发生。栅栏与闭锁的关键区别在于,所有线程必须同时到达栅栏位置,才能继续执行。闭锁用于等待事件,而栅栏用于等待其他线程。

        CyclicBarrier可以使一定数量参与方反复地在栅栏位置汇集,它在并行迭代算法中非常有用:这种算法通常将一个问题拆分成一系列相互独立的子问题。当线程到达栅栏位置时,将调用await方法,这个方法将阻塞直到所有线程都到达栅栏位置。如果所有线程都到达了栅栏位置,那么栅栏将打开,此时所有线程都被释放,而栅栏将被重置以便下次使用。如果对await的调用超时,或者await阻塞的线程被中断,那么栅栏就被认为是打破了,所有的阻塞的await调用都将终止并抛出BrokenBarrierException,那么await将为每个线程返回一个唯一到达的索引号,我们可以利用这些索引选举一个领导线程,并在下一次迭代中由该领导线程执行一些特殊工作。

     例如某个步骤中的计算可以并行执行,但是必须等到该步骤的所有计算都执行完毕才能进入下一个步骤。


public class CyclicBarrierTest {
	static CyclicBarrier barrier = new CyclicBarrier(2, new Thread1());

	static class Thread1 extends Thread {
		@Override
		public void run() {
			System.out.println("Thread1.......is....running");
		}
	}

	static class Thread2 extends Thread {
		@Override
		public void run() {
			try {
				System.out.println("Thread2 is wait");
				barrier.await();
				System.out.println("Thread2 is continue Running");
			} catch (InterruptedException | BrokenBarrierException e) {
				e.printStackTrace();
			}

		}
	}

	static class Thread3 extends Thread {
		@Override
		public void run() {
			try {
				System.out.println("Thread3 is wait");
				barrier.await();
			} catch (InterruptedException | BrokenBarrierException e) {
				e.printStackTrace();
			}
			System.out.println("Thread3 is continue Running");
		}
	}

	public static void main(String[] args) {
		Thread thread2 = new Thread2();
		Thread thread3 = new Thread3();
		thread2.start();
		thread3.start();
	}
}

测试结果如下:

Thread2 is wait
Thread3 is wait
Thread1.......is....running
Thread3 is continue Running
Thread2 is continue Running

从CyclicBarrirer的英文来看,是周期屏障的意思。在CyclicBarrirer实例化的时候,传入两个参数,一个是int 值2,一个线程Thread1。其中2是表示在CyclicBarrirer在2个线程Thread2,Thread3中调用await后,才会放开屏障,让其继续执行。并且是在Thread1执行完成之后,才执行Thread3,Thread2的后续逻辑。

CyclicBarrirer和CountDownLatch区别就是,CyclicBarrier的调用await线程数量必须和初始化定义的int值,一一对应,并且一个线程只能调用一次CyclicBarrirer.await(),否则就会一直等待的结果。

4、 信号量 Semaphore   

Semaphore可以将任何一中容器变成有界阻塞容器。acquire将阻塞知道有许可。release将返回一个许可给信号量。

这里我们通过实现一个简单的连接池来使用Semaphore(读音:生 me four),
这里semaphore作为一种信号量,来实现获取数据库连接数的计数,
实现原理就是 
定义两个Semaphore,一个useful(10)表示可用的数据库连接,useless(0)表示已用的数据库连接。
通过useful.getQueueLength(),得到当前请求获得连接的线程的数量(实际请求信号量)
useful.availablePermits()得到实际可用连接数(剩余信号量)。

其实最主要思想就是通过Semaphore的信号量来判断请求信号等待数,请求信号成功数,和剩余信号量。

    useful.acquire():阻塞获取信号(代表线程请求获得数据库连接,useful的信号量-1)

    useless.release():释放一个信号(代表线程请求获得数据库连接成功,useless信号量+1)

    useless.acquire()释放一个信号(代表线程使用数据库连接完成,useless信号量-1)。

    useful.release()返回信号量(代表线程使用数据库连接完成,useful的信号量+1)。

Semaphore的主要方法:

  void acquire():从此信号量获取一个许可,在提供一个许可前一直将线程阻塞,否则线程被中断。

  void release():释放一个许可,将其返回给信号量。

  int availablePermits():返回此信号量中当前可用的许可数。

  boolean hasQueuedThreads():查询是否有线程正在等待获取。

    int getQueueLength :得到等待数量

1、创建数据库连接实体类(这里模拟实现,所以并不涉及到连接信息,并且并不重写任何接口方法,只添加静态获取连接方法  fetchConnection())

public class SqlConnectImpl implements Connection{
	
	/*拿一个数据库连接*/
    public static final Connection fetchConnection(){
        return new SqlConnectImpl();
    }

	@Override
	public <T> T unwrap(Class<T> iface) throws SQLException {
		// TODO Auto-generated method stub
		return null;
	}

	@Override
	public boolean isWrapperFor(Class<?> iface) throws SQLException {
		// TODO Auto-generated method stub
		return false;
	}

.......................

}

2、新建一个类DBPoolSemaphore,通过静态代码块实现连接池的初始化。

public class DBPoolSemaphore {
	
	    private final static int POOL_SIZE = 10;
	private final Semaphore useful,useless;//useful表示可用的数据库连接,useless表示已用的数据库连接
	
	public DBPoolSemaphore() {
		this. useful = new Semaphore(POOL_SIZE);
		this.useless = new Semaphore(0);
	}
	
	//存放数据库连接的容器
	private static LinkedList<Connection> pool = new LinkedList<Connection>();
	//初始化池
	static {
        for (int i = 0; i < POOL_SIZE; i++) {
            pool.addLast(SqlConnectImpl.fetchConnection());
        }
	}

	/*归还连接*/
	public void returnConnect(Connection connection) throws InterruptedException {
		if(connection!=null) {
			System.out.println("当前有"+useful.getQueueLength()+"个线程等待数据库连接!!"
					+"可用连接数:"+useful.availablePermits()+",当前实际已用数据库连接"+useless.availablePermits()+"个");
			
			useless.acquire();
			synchronized (pool) {
				pool.addLast(connection);
			}	
			useful.release();
		}
	}
	
	/*从池子拿连接*/
	public Connection takeConnect() throws InterruptedException {
		useful.acquire();
		Connection conn;
		synchronized (pool) {
			conn = pool.removeFirst();
		}
		useless.release();
		return conn;
	}
	
}

3、新建测试类

public class AppTest {

	private static DBPoolSemaphore dbPool = new DBPoolSemaphore();
	
	//业务线程
	private static class BusiThread extends Thread{
		@Override
		public void run() {
			Random r = new Random();//让每个线程持有连接的时间不一样
			long start = System.currentTimeMillis();
			try {
				Connection connect = dbPool.takeConnect();
				System.out.println("Thread_"+Thread.currentThread().getId()
						+"_获取数据库连接共耗时【"+(System.currentTimeMillis()-start)+"】ms.");
				SleepTools.ms(100+r.nextInt(100));//模拟业务操作,线程持有连接查询数据
				System.out.println("查询数据完成,归还连接!");
				dbPool.returnConnect(connect);
			} catch (InterruptedException e) {
			}
		}
	}
	
	public static void main(String[] args) {
        for (int i = 0; i < 50; i++) {
            Thread thread = new BusiThread();
            thread.start();
        }
	}
	
}

4、测试结果如下:

Thread_11_获取数据库连接共耗时【0】ms.
Thread_15_获取数据库连接共耗时【0】ms.
Thread_16_获取数据库连接共耗时【0】ms.
Thread_13_获取数据库连接共耗时【0】ms.
Thread_12_获取数据库连接共耗时【0】ms.
Thread_10_获取数据库连接共耗时【0】ms.
Thread_20_获取数据库连接共耗时【0】ms.
Thread_17_获取数据库连接共耗时【0】ms.
Thread_14_获取数据库连接共耗时【0】ms.
Thread_18_获取数据库连接共耗时【1】ms.
查询数据完成,归还连接!
当前有40个线程等待数据库连接!!可用连接数:0,当前实际已用数据库连接10个
Thread_19_获取数据库连接共耗时【102】ms.
查询数据完成,归还连接!
当前有39个线程等待数据库连接!!可用连接数:0,当前实际已用数据库连接10个
Thread_21_获取数据库连接共耗时【118】ms.

..............................................................

5、Exchange的使用

该类用于处理,偶数个线程之间的数据交换。比如有两个线程A和B,当A开始运行并准备就绪数据后,它就会等待B开始运行,直到B准备好数据,两个线程马上开始交互数据。如果有三个线程将导致死锁,    另外一种形式的栅栏是Exchanger,它是一种两方栅栏,各方在栅栏位置上交换数据。当两方执行不对称的操作时,Exchanger会非常有用。例如当一个线程向缓冲区写入数据,而另外一个线程从缓冲区读取数据。通过Exchanger,达到满的缓冲区与空的缓冲区交换,当两个线程通过Exchanger较好对象时,这种交换就把两个对象安全地发布给另一方。

测试类

public class UseExchange {
    private static final Exchanger<Set<String>> exchange 
    	= new Exchanger<Set<String>>();

    public static void main(String[] args) {

    	//第一个线程
        new Thread(new Runnable() {
            @Override
            public void run() {
            	Set<String> setA = new HashSet<String>();//存放数据的容器
                try {
                	/*添加数据
                	 * set.add(.....)
                	 * */
                	setA.add("A的数据");
                	setA = exchange.exchange(setA);//交换set
                	System.out.println(setA.toArray()[0]+",这是A");
                	/*处理交换后的数据*/
                } catch (InterruptedException e) {
                }
            }
        }).start();

      //第二个线程
        new Thread(new Runnable() {
            @Override
            public void run() {
            	Set<String> setB = new HashSet<String>();//存放数据的容器
                try {
                	/*添加数据
                	 * set.add(.....)
                	 * set.add(.....)
                	 * */
                	setB.add("B的数据");
                	setB = exchange.exchange(setB);//交换set
                	System.out.println(setB.toArray()[0]+",这是B");
                	/*处理交换后的数据*/
                } catch (InterruptedException e) {
                }
            }
        }).start();

    }
}
测试结果:
B的数据,这是A
A的数据,这是B

6.Runnable ,future ,futuretask,和callable关系

FutureTask中的 outcome存储 callable中的返回值,并且可以通过FutureTask的对象示例.get()得到。

isDone,结束,正常还是异常结束,或者自己取消,返回true;

isCancelled 任务完成前被取消,返回true;

cancel(boolean):

  1. 任务还没开始,返回false
  2. 任务已经启动,cancel(true),中断正在运行的任务,中断成功,返回true,cancel(false),不会去中断已经运行的任务
  3. 任务已经结束,返回false

实际应用:

包含图片和文字的文档的处理:图片(云上),可以用future去取图片,主线程继续解析文字。

7、原子操作

 1.原子操作相关解释

    "原子操作(atomic operation)是不需要synchronized",这是Java多线程编程的老生常谈了。所谓原子操作是指不会被线程调度机制打断的操作;这种操作一旦开始,就一直运行到结束,中间不会有任何 context switch (切 换到另一个线程)。

2.原子操作可以通过锁机制和CAS来实现

3.CAS和synchronized的区别

synchronized基于阻塞的锁的机制,1、被阻塞的线程优先级很高,2、拿到锁的线程一直不释放锁怎么办?3、大量的竞争,消耗cpu,同时带来死锁或者其他安全。而原子操作则是通过指令级保证安全 

4.CAS的基本实现思想

三个运算符:一个内存地址V,一个期望的值A,一个新值B

基本思路:如果地址V上的值和期望的值A相等,就给地址V赋给新值B,如果不是,不做任何操作。

循环(死循环,自旋)里不断的进行CAS操作

5.CAS的缺点

   (1)ABA问题。因为CAS需要在操作值的时候,只检查值有没有发生变化,如果没有发生变化则更新,但是如果有一个值原来是A,变成了B,然后又变成了A,那么CAS进行检查时会发现它的值没有变化,但是实际上却变化了。

    (2)循环时间长开销大。自旋CAS如果长时间不成功,会给CPU带来非常大的执行开销

    (3)只能保证一个共享变量的原子操作。当对一个共享变量执行操作时,我们可以使用循环CAS的方式保证原子操作,但是对多个个共享变量操作时,循环CAS就无法保证操作的原子性,这个时候就可以用锁来保证原子操作

6.原子操作相关java类

更新基本类型类:AtomicBoolean,AtomicInteger,AtomicLong,AtomicReference

更新数组类:AtomicIntegerArray,AtomicLongArray,AtomicReferenceArray

更新引用类型:AtomicReference,AtomicMarkableReference,AtomicStampedReference

原子更新字段类: AtomicReferenceFieldUpdater,AtomicIntegerFieldUpdater,AtomicLongFieldUpdater

7.常用原子操作实战

 AtomicInteger

public class UseAtomicInt {
	
	static AtomicInteger ai = new AtomicInteger(10);
	
    public static void main(String[] args) {
    	System.out.println(ai.getAndIncrement());//10--->11
    	System.out.println(ai.incrementAndGet());//11--->12--->out
    	System.out.println(ai.get());
    }
}
测试结果:
10
12
12

AtomicArray  

public class AtomicArray {
    static int[] value = new int[] { 1, 2 };
    
    static AtomicIntegerArray ai = new AtomicIntegerArray(value);
    
    public static void main(String[] args) {
    	ai.getAndSet(0, 3);
    	System.out.println(ai.get(0));
    	System.out.println(value[0]);

    }
}

测试结果
3
1

引用类型

public class UseAtomicReference {
	
	static AtomicReference<UserInfo> userRef = new AtomicReference<UserInfo>();
	
    public static void main(String[] args) {
        UserInfo user = new UserInfo("erwan", 25);//
        userRef.set(user);
        
        UserInfo updateUser = new UserInfo("xuegao", 1);//我的宠物狗,叫雪糕
        userRef.compareAndSet(user, updateUser);
        System.out.println(userRef.get().getName()+":"+userRef.get().getAge());
        System.out.println(user.getName()+":"+user.getAge());
    }
    
    //定义一个实体类
    static class UserInfo {
        private String name;
        private int age;
        public UserInfo(String name, int age) {
            this.name = name;
            this.age = age;
        }
        public String getName() {
            return name;
        }
        public int getAge() {
            return age;
        }
    }

}
测试结果:
xuegao:1
erwan:25

通过版本号来解决原子操作的ABA问题的类

package com.erwan.chp2;

import java.util.concurrent.atomic.AtomicStampedReference;

public class AtomicStampedReferenceTest {
	static AtomicStampedReference<String> asr = new AtomicStampedReference<>("未更新", 0);

	static class Thread1 extends Thread {
		@Override
		public void run() {
			boolean flag =asr.compareAndSet("未更新", "第一次更新", asr.getStamp(), asr.getStamp()+1);
			System.out.println(asr.getReference() +",版本号:"+asr.getStamp()+",操作是否成功:"+flag);
		}
	}
	static class Thread2 extends Thread {
		@Override
		public void run() {
			boolean flag =asr.compareAndSet("第一次更新", "第二次更新", asr.getStamp(), asr.getStamp()+1);
			System.out.println(asr.getReference() +",版本号:"+asr.getStamp()+",操作是否成功:"+flag);
		}
	}
	static class Thread3 extends Thread {
		@Override
		public void run() {
		boolean flag =asr.compareAndSet("第二次更新", "第三次更新", asr.getStamp()+1, asr.getStamp());
		boolean flag2 =asr.compareAndSet("第一次更新", "第三次更新", asr.getStamp(), asr.getStamp());
		System.out.println("错误使用,版本号不对:"+asr.getReference() +",版本号:"+asr.getStamp()+",操作是否成功:"+flag);
		System.out.println("错误使用,原有值不对:"+asr.getReference() +",版本号:"+asr.getStamp()+",操作是否成功:"+flag2);
		}
	}

	public static void main(String[] args) throws InterruptedException {
		Thread thread1=new Thread1();
		thread1.start();
		thread1.join();
		Thread thread2=new Thread2();
		thread2.start();
		thread2.join();
		Thread thread3=new Thread3();
		thread3.start();
	}
}


测试结果如下:
第一次更新,版本号:1,操作是否成功:true
第二次更新,版本号:2,操作是否成功:true
错误使用,版本号不对:第二次更新,版本号:2,操作是否成功:false
错误使用,原有值不对:第二次更新,版本号:2,操作是否成功:false


解释:boolean flag =asr.compareAndSet("未更新", "第一次更新", asr.getStamp(), asr.getStamp()+1); 

         asr中的compareAndSet会进行比较和设值,如果设值成功,则返回true,设置失败则返回false

           四个参数依次是 oldValue,replaceValue,oldStamp,newStamp。

Thread1和Thread2为什么会用join。

join的用处,Thread1.join会让Thread1执行完成之后,才执行Thread2,同理,Thread3是最后执行。

如果不加join(),则Thread1和Thread2不知道谁先执行,如果Thread2先执行,则在oldValue为“未更新”和Thread2传入的oldValue “第一次更新”不匹配,那么Thread2的值就设值失败,返回false

结果分析:

从Thread3的分析结果来看,AtomicStampedReference 通过版本号来防止cas操作出现ABA的问题的,如果在compareAndSet的操作时候,oldValue不存在,则设值失败,返回false。如果旧版本号不存在,则设值错误。

通过AtomicMarkableReference解决ABA问题

package com.erwan.chp2;

import java.util.concurrent.atomic.AtomicMarkableReference;

public class AtomicMarkableReferenceTest {
	static AtomicMarkableReference<String> asr = new AtomicMarkableReference<>("未更新", false);

	static class Thread1 extends Thread {
		@Override
		public void run() {
			boolean [] booleans={true};
			System.out.println("第一次操作前:操作前的值:"+asr.get(booleans)+",标记为"+booleans[0]);
			boolean flag = asr.compareAndSet("未更新", "第一次更新", false, false);
			System.out.println("第一次操作后:操作是否成功" + flag+",操作后的值:"+asr.get(booleans)+",标记为"+booleans[0]);
		}
	}

	static class Thread2 extends Thread {
		@Override
		public void run() {
			boolean [] booleans={true};
			System.out.println("第二次操作前:操作前的值:"+asr.get(booleans)+",标记为"+booleans[0]);
			boolean flag = asr.compareAndSet("第一次更新", "第二次更新", false, false);
			
			System.out.println("第二次操作后:操作是否成功" + flag+",操作后的值:"+asr.get(booleans)+",标记为"+booleans[0]);
		}
	}


	public static void main(String[] args) throws InterruptedException {
		Thread thread1 = new Thread1();
		Thread thread2 = new Thread2();
		thread1.start();
		thread1.join();
		thread2.start();
		thread2.join();
	}
}
测试结果:
第一次操作前:操作前的值:未更新,标记为false
第一次操作后:操作是否成功true,操作后的值:第一次更新,标记为false
第二次操作前:操作前的值:第一次更新,标记为false
第二次操作后:操作是否成功true,操作后的值:第二次更新,标记为false

AtomicMarkableReference <V>方法介绍:

compareAndSet(V expectedReference, V newReference, boolean expectedMark, boolean newMark)

expectedReference 期望的值,newReference 更改后的值,expectedMark 期望的标记位,newMark 修改后的标记位。

V get(boolean []) 获得返回值,boolean[0],可以存储当前变量的标记位

AtomicMarkableReference,没有版本号,只是传递一个boolean,表示是否修改过值

AtomicStampedReference  通过版本号表示修改过几次值

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值