多线程系列之线程同步

多线程系列之线程同步

一 线程同步机制和锁概述

  1. 线程同步机制简介
    线程同步机制是一套用于协调线程之间的数据访问的机制.该机制可以保障线程安全.
    Java 平台提供的线程同步机制包括: 锁, volatile 关键字, final 关键字,static 关键字,以及相关的 API,如Object.wait()/Object.notify()等
  2. 锁概述
    (1)线程安全问题的产生前提是多个线程并发访问共享数据.
    (2)将多个线程对共享数据的并发访问转换为串行访问,即一个共享数据一次只能被一个线程访问.锁就是复用这种思路来保障线程安全的
    (3)(Lock)可以理解为对共享数据进行保护的一个许可证. 对于同一个许可证保护的共享数据来说**,任何线程想要访问这些共享数据必须先持有该许可证**. 一个线程只有在持有许可证的情况下才能对这些共享数据进行访问; 并且一个许可证一次只能被一个线程持有; 许可证线程在结束对共享数据的访问后必须释放其持有的许可证
    (4)一线程在访问共享数据前必须先获得锁; 获得锁的线程称为锁的持有线程; 一个锁一次只能被一个线程持有. 锁的持有线程在获得锁之后 和释放锁之前这段时间所执行的代码称为临界区
    (5)锁具有排他性(Exclusive), 即一个锁一次只能被一个线程持有.这种锁称为排它锁或互斥锁
    (6)JVM 把锁分为内部锁和显示锁两种. 内部锁通过 synchronized关键字实现; 显示锁通过 java.concurrent.locks.Lock 接口的实现类实现的
    在这里插入图片描述

二 锁的作用和相关概念

  1. 锁的作用
    (1)锁可以实现对共享数据的安全访问. 保障线程的原子性,可见性与有序性.
    (2)锁是通过互斥保障原子性. 一个锁只能被一个线程持有, 这就保证临界区的代码一次只能被一个线程执行.使得临界区代码所执行的操作自然而然的具有不可分割的特性,即具备了原子性.
    (3)可见性的保障是通过写线程冲刷处理器的缓存和读线程刷新处理器缓存这两个动作实现的. 在 java 平台中,锁的获得隐含着刷新处理器缓存的动作, 锁的释放隐含着冲刷处理器缓存的动作
    (4)锁能够保障有序性.写线程在临界区所执行的在读线程所执行的临界区看来像是完全按照源码顺序执行的
    注意:使用锁保障线程的安全性,必须满足以下条件:
    这些线程在访问共享数据时必须使用同一个锁;读取共享数据的线程也需要使用同步锁。
  2. 锁相关的概念
    (1)可重入性:一个线程持有该锁的时候能再次(多次)申请该锁
    如果一个线程持有一个锁的时候还能够继续成功申请该锁,称该锁是可重入的, 否则就称该锁为不可重入的
void methodA(){
	申请 a 锁
	methodB();
	释放 a 锁
}
void methodB(){
	申请 a 锁
	.... 
	释放 a 锁
}

(2)锁的争用与调度
Java 平台中内部锁属于非公平锁, 显示 Lock 锁既支持公平锁又支持非公平锁
(3)锁的粒度
一个锁可以保护的共享数据的数量大小称为锁的粒度锁,保护共享数据量大,称该锁的粒度粗, 否则就称该锁的粒度细。锁的粒度过粗会导致线程在申请锁时会进行不必要的等待.锁的粒度过细会增加锁调度的开销

三 内部锁:synchronized关键字

  1. 内部锁说明
    Java 中的每个对象都有一个与之关联的内部锁(Intrinsic lock). 这种锁也称为监视器(Monitor), 这种内部锁是一种排他锁,可以保障原子性,可见性与有序性.
    内部锁是通过 synchronized 关键字实现的.synchronized 关键字修饰代码块,修饰该方法
    修饰代码块的语法:
    synchronized( 对象锁 ) {
    同步代码块,可以在同步代码块中访问共享数据
    }
    修饰实例方法就称为同步实例方法
    修饰静态方法称称为同步静态方法
  2. synchronized同步代码块
/**
* synchronized 同步代码块
* this 锁对象
* 如果线程的锁不同, 不能实现同步
* 想要同步必须使用同一个锁对象
*/
public class Test01 {
	public static void main(String[] args) {
		//创建两个线程,分别调用 mm()方法
		//先创建 Test01 对象,通过对象名调用 mm()方法
		Test01 obj = new Test01();
		Test01 obj2 = new Test01();
		new Thread(new Runnable() {
			@Override
			public void run() {
				obj.mm(); //使用的锁对象this就是obj对象
			}
		}).start();
		new Thread(new Runnable() {
			@Override
			public void run() {
				obj.mm(); //使用的锁对象this也是obj对象
			}
		}).start();
		new Thread(new Runnable() {
			@Override
			public void run() {
				obj2.mm(); //使用的锁对象this就是obj2对象
			}
		}).start();
	}
	//定义方法,打印 100 行字符串
	public void mm(){
		synchronized ( this ) { //经常使用this当前对象作为锁对象
			for (int i = 1; i <= 100; i++) {
					System.out.println(Thread.currentThread().getName() + " --> " + i);
			}
		}
	}
}


/**
* synchronized 同步代码块
* 使用一个常量对象作为锁对象
*/
public class Test03 {
	public static void main(String[] args) {
		//创建两个线程,分别调用 mm()方法
		//先创建 Test03 对象,通过对象名调用 mm()方法
		Test03 obj = new Test03();
		Test03 obj2 = new Test03();
		new Thread(new Runnable() {
			@Override
			public void run() {
				obj.mm(); //使用的锁对象 OBJ 常量
			}
		}).start();
		new Thread(new Runnable() {
			@Override
			public void run() {
				obj2.mm(); //使用的锁对象 OBJ 常量
			}
		}).start();
}
	public static final Object OBJ = new Object(); //定义一个常量, 
	//定义方法,打印 100 行字符串
	public void mm(){
		synchronized ( OBJ ) { //使用一个常量对象作为锁对象
			for (int i = 1; i <= 100; i++) {
				System.out.println(Thread.currentThread().getName() + " --> " + i);
			}
		}
	}
}
/**
* synchronized 同步代码块
* 使用一个常量对象作为锁对象,不同方法中 的同步代码块也可以同步
*/
public class Test04 {
	public static void main(String[] args) {
		//创建两个线程,分别调用 mm()方法
		//先创建 Test01 对象,通过对象名调用 mm()方法
		Test04 obj = new Test04();
		Test04 obj2 = new Test04();
		new Thread(new Runnable() {
			@Override
			public void run() {
				obj.mm(); //使用的锁对象 OBJ 常量
			}
		}).start();
		new Thread(new Runnable() {
			@Override
			public void run() {
				obj2.mm(); //使用的锁对象 OBJ 常量
			}
		}).start();
		//第三个线程调用静态方法
		new Thread(new Runnable() {
			@Override
			public void run() {
				sm(); //使用的锁对象 OBJ 常量
			}
		}).start();
	}
	public static final Object OBJ = new Object(); //定义一个常量,
	 //定义方法,打印 100 行字符串
	public void mm(){
		synchronized ( OBJ ) { //使用一个常量对象作为锁对象
			for (int i = 1; i <= 100; i++) {
			System.out.println(Thread.currentThread().getName() + " --> " + i);
			}
		}
	}
	//定义方法,打印 100 行字符串
	public static void sm(){
		synchronized ( OBJ ) { //使用一个常量对象作为锁对象
			for (int i = 1; i <= 100; i++) {
				System.out.println(Thread.currentThread().getName() + " --> " + i);
			}
		}
	}
}
  1. 同步方法
    同步实例方法锁为this对象
    同步静态方法锁为类对象
/**
* synchronized 同步实例方法
* 把整个方法体作为同步代码块
* 默认的锁对象是 this 对象
*/
public class Test05 {
	public static void main(String[] args) {
		//先创建 Test01 对象,通过对象名调用 mm()方法
		Test05 obj = new Test05();
		//一个线程调用 mm()方法
		new Thread(new Runnable() {
			@Override
			public void run() {
				obj.mm(); //使用的锁对象this就是obj对象
			}
		}).start();
		//另一个线程调用 mm22()方法
		new Thread(new Runnable() {
			@Override
			public void run() {
				obj.mm22(); //使用的锁对象 this 也是 obj对象, 可以同步
				// new Test05().mm22(); //使用的锁对象 this是刚刚 new 创建的一个新对象,不是同一个锁对象不能同步
			}
		}).start();
	}
	//定义方法,打印 100 行字符串
	public void mm(){
		synchronized ( this ) { //经常使用this当前对象作为锁对象
			for (int i = 1; i <= 100; i++) {
				System.out.println(Thread.currentThread().getName() + " --> " + i);
			}
		}
	}
	//使用 synchronized 修饰实例方法,同步实例方法, 默认 this 作为锁对象
	public synchronized void mm22(){
		for (int i = 1; i <= 100; i++) {
			System.out.println(Thread.currentThread().getName() + " --> " + i);
		}
	}
}

/**
* synchronized 同步静态方法
* 把整个方法体作为同步代码块
* 默认的锁对象是当前类的运行时类对象, Test06.class, 有人
称它为类锁
*/
public class Test06 {
	public static void main(String[] args) {
		//先创建 Test01 对象,通过对象名调用 mm()方法
		Test06 obj = new Test06();
		//一个线程调用 m1()方法
		new Thread(new Runnable() {
			@Override
			public void run() {
				obj.m1(); //使用的锁对象是 Test06.class
			}
		}).start();
		//另一个线程调用 sm2()方法
		new Thread(new Runnable() {
			@Override
			public void run() {
				Test06.sm2(); //使用的锁对象是 Test06.class
			}
		}).start();
	}
	//定义方法,打印 100 行字符串
	public void m1(){
		//使用当前类的运行时类对象作为锁对象,可以简单的理解为把 Test06 类的字节码文件作为锁对象
		synchronized ( Test06.class ) {
			for (int i = 1; i <= 100; i++) {
				System.out.println(Thread.currentThread().getName() + " --> " + i);
			}
		}
	}
	//使用 synchronized 修饰静态方法,同步静态方法, 默认运行时类Test06.class 作为锁对象
	public synchronized static void sm2(){
		for (int i = 1; i <= 100; i++) {
		System.out.println(Thread.currentThread().getName() + " --> " + i);
		}
	}
}
  1. 同步方法与同步代码块相比,同步方法锁的粒度粗,执行效率低,同步代码块执行效率高
  2. 脏读
    在读取属性值出现了一些意外, 读取的是中间值,而不是修改之后 的值。出现脏读的原因是 对共享数据的修改 与对共享数据的读取不同步。
    解决方法:
    不仅对修改数据的代码块进行同步,还要对读取数据的代码块同步
public class Test08 {
	public static void main(String[] args) throws InterruptedException {
		//开启子线程设置用户名和密码
		PublicValue publicValue = new PublicValue();
		SubThread t1 = new SubThread(publicValue);
		t1.start();
		//为了确定设置成功
		Thread.sleep(100);
		//在 main 线程中读取用户名,密码
		publicValue.getValue();
}
//定义线程,设置用户名和密码
static class SubThread extends Thread{
	private PublicValue publicValue;
	public SubThread( PublicValue publicValue){
		this.publicValue = publicValue;
	}
	@Override
	public void run() {
		publicValue.setValue("bjpowernode", "123");
	}
}
static class PublicValue{
	private String name = "wkcto";
	private String pwd = "666";
	public synchronized void getValue(){
		System.out.println(Thread.currentThread().getName() + ", getter -- name: " + name + ",--pwd: " + pwd);
	}
	public synchronized void setValue(String name, String pwd){
		this.name = name;
		try {
			Thread.sleep(1000); //模拟操作name 属性需要一定时间
		} catch (InterruptedException e) {
			e.printStackTrace();
	}
	this.pwd = pwd;
	System.out.println(Thread.currentThread().getName() + ", setter --name:" + name + ", --pwd: " + pwd );
	}
	}
}
  1. 程序出现异常自动释放锁
public class Test09 {
	public static void main(String[] args) {
		//先创建 Test01 对象,通过对象名调用 mm()方法
		Test09 obj = new Test09();
		//一个线程调用 m1()方法
		new Thread(new Runnable() {
			@Override
			public void run() {
				obj.m1(); //使用的锁对象是 Test06.class
			}
		}).start();
		//另一个线程调用 sm2()方法
		new Thread(new Runnable() {
			@Override
			public void run() {
				Test09.sm2(); //使用的锁对象是 Test06.class
			}
		}).start();
	}
	//定义方法,打印 100 行字符串
	public void m1(){
		//使用当前类的运行时类对象作为锁对象,可以简单的理解为把 Test06 类的字节码文件作为锁对象
		synchronized ( Test09.class ) {
			for (int i = 1; i <= 100; i++) {
				System.out.println(Thread.currentThread().getName() + " --> " + i);
				if ( i == 50){
					Integer.parseInt("abc"); //把字符串转换为int 类型时,如果字符串不符合 数字格式会产生异常
				}
			}
		}
	}
	//使用 synchronized 修饰静态方法,同步静态方法, 默认运行时类Test06.class 作为锁对象
	public synchronized static void sm2(){
		for (int i = 1; i <= 100; i++) {
			System.out.println(Thread.currentThread().getName() + " --> " + i);
		}
	}
}
  1. 死锁
/**
* 死锁
* 在多线程程序中,同步时可能需要使用多个锁,如果获得锁的顺序
不一致,可能会导致死锁
* 如何避免死锁?
* 当需要获得多个锁时,所有线程获得锁的顺序保持一致即可
*/
public class Test10 {
	public static void main(String[] args) {
		SubThread t1 = new SubThread();
		t1.setName("a");
		t1.start();
		SubThread t2 = new SubThread();
		t2.setName("b");
		t2.start();
}
static class SubThread extends Thread{
	private static final Object lock1 = new Object();
	private static final Object lock2 = new Object();
	@Override
	public void run() {
		if ("a".equals(Thread.currentThread().getName())){
			synchronized (lock1){
				System.out.println("a 线程获得了 lock1 锁,还需要获得 lock2 锁");
				synchronized (lock2){
					System.out.println("a 线程获得 lock1 后又获得了 lock2,可以想干任何想干的事");
				}
			}
		}
		if ("b".equals(Thread.currentThread().getName())){
			synchronized (lock2){
				System.out.println("b 线程获得了 lock2 锁,还需要获得 lock1 锁");
				synchronized (lock1){
					System.out.println("b 线程获得lock2后又获得了 lock1,可以想干任何想干的事");
				}
			}
		}
	}
}
}

四 轻量级同步机制:volatile关键字

  1. volatile的作用:使变量在多个线程之间可见
/**
* volatile 的作用可以强制线程从公共内存中读取变量的值,而不是从工作内存中读取
*/
public class Test02 {
	public static void main(String[] args) {
		//创建 PrintString 对象
		PrintString printString = new PrintString();
		//开启子线程,让子线程执行 printString 对象的 printStringMethod()方法
		new Thread(new Runnable() {
			@Override
			public void run() {
				printString.printStringMethod();
			}
		}).start();
		//main 线程睡眠 1000 毫秒
		try {
			Thread.sleep(1000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		System.out.println("在 main 线程中修改打印标志");
		printString.setContinuePrint(false);
		//程序运行,查看在 main 线程中修改了打印标志之后 ,子线程打印是否可以结束打印
		//程序运行后, 可能会出现死循环情况
		//分析原因: main 线程修改了 printString 对象的打印标志后, 子线程读不到
		//解决办法: 使用 volatile 关键字修饰 printString 对象的打印标志. 
		// volatile 的作用可以强制线程从公共内存中读取变量的值,而不是从工作内存中读取
	}	
	//定义类打印字符串
	static class PrintString{
		private volatile boolean continuePrint = true;
		public PrintString setContinuePrint(boolean continuePrint) {
			this.continuePrint = continuePrint;
			return this;
		}
		public void printStringMethod(){
			System.out.println(Thread.currentThread().getName() + "开始....");
			while ( continuePrint ){
				//...
			}
			System.out.println(Thread.currentThread().getName() + "结束++++++++++++++");
		}
	}
}
  1. volatile 与 synchronized 比较
    (1)volatile 关键字是线程同步的轻量级实现,所以volatile性能肯定比 synchronized 要好; volatile 只能修饰变量,而 synchronized 可以修饰方法,代码块. 随着 JDK 新版本的发布,synchronized 的执行效率也有较大的提升,在开发中使用 sychronized 的比率还是很大的.
    (2)多线程访问 volatile 变量不会发生阻塞,而 synchronized 可能会阻塞
    (3)volatile 能保证数据的可见性,但是不能保证原子性; 而synchronized 可以保证原子性,也可以保证可见性
    **
    (4)关键字 volatile 解决的是
    变量在多个线程之间的可见性 ,synchronized 关键字解决多个线程之间访问公共资源的同步性**
  2. volatile非原子特性
    volatile 关键字增加了实例变量在多个 线程之间的可见性,但是不具备原子性
public class Test03 {
	public static void main(String[] args) {
		//在 main 线程中创建 10 个子线程
		for (int i = 0; i < 100; i++) {
			new MyThread().start();
		}
	}
	static class MyThread extends Thread{
		//volatile 关键仅仅是表示所有线程从主内存读取 count 变量的值
		public static int count;
		/* //这段代码运行后不是线程安全的,想要线程安全,需要使用 synchronized 进行同
步,如果使用 synchronized 同时,也就不需要 volatile 关键了
		public static void addCount(){
		for (int i = 0; i < 1000; i++) {
			//count++不是原子操作
			count++;
		}
		System.out.println(Thread.currentThread().getName() + " count=" + count);
		*/
		public synchronized static void addCount(){
			for (int i = 0; i < 1000; i++) {
				//count++不是原子操作
				count++;
			}
		System.out.println(Thread.currentThread().getName() + " count=" + count);
		}
		@Override
		public void run() {
			addCount();
		}
	}
}
  1. 常用原子类进行自增自减操作
    我们知道 i++操作不是原子操作, 除了使用 Synchronized 进行同步外,也可以使用 AtomicInteger/AtomicLong 原子类进行实现
/**
* 使用原子类进行自增
*/
public class Test04 {
	public static void main(String[] args) throws InterruptedException {
		//在 main 线程中创建 10 个子线程
		for (int i = 0; i < 1000; i++) {
			new MyThread().start();
		}
		Thread.sleep(1000);
		System.out.println( MyThread.count.get());
	}
	static class MyThread extends Thread{
		//使用 AtomicInteger 对象
		private static AtomicInteger count = new AtomicInteger();
		public static void addCount(){
			for (int i = 0; i < 10000; i++) {
				//自增的后缀形式
				count.getAndIncrement();
			}
			System.out.println(Thread.currentThread().getName() + " count=" + count.get());
		}
		@Override
		public void run() {
			addCount();
		}
	}
}

五 CAS原理

  1. 基本介绍
    CAS(Compare And Swap)是由硬件实现的. CAS 可以将 read- modify - write 这类的操作转换为原子操作.
    i++自增操作包括三个子操作:
    从主内存读取 i 变量值
    对 i 的值加 1
    再把加 1 之后 的值保存到主内存
  2. CAS原理
    在把数据更新到主内存时,再次读取主内存变量的值,如果现在变量的值与期望的值(操作起始时读取的值)一样就更新
    在这里插入图片描述3. 应用案例
    使用CAS实现一个线程安全的计数器
public class CASTest {
	public static void main(String[] args) {
		CASCounter casCounter = new CASCounter();
		for (int i = 0; i < 100000; i++) {
			new Thread(new Runnable() {
				@Override
				public void run() {
					System.out.println(casCounter.incrementAndGet());
				}
			}).start();
		}
	}
}
class CASCounter{
	//使用 volatile 修饰 value 值,使线程可见	
	volatile private long value;
	public long getValue() {
		return value;
	}
	//定义 comare and swap 方法
	private boolean compareAndSwap(long expectedValue, long newValue){
		//如果当前 value 的值与期望的 expectedValue 值一样,就把当前的 Value 字段替换为newValue 值
		synchronized (this){
			if ( value == expectedValue ){
				value = newValue;
				return true;
			}else {
				return false;
			}
		}
	}
	//定义自增的方法
	public long incrementAndGet(){
		long oldvalue ;
		long newValue;
		do {
			oldvalue = value;
			newValue = oldvalue+1;
		}while (!compareAndSwap(oldvalue, newValue) );
		return newValue;
	}
}

CAS 实现原子操作背后有一个假设: 共享变量的当前值与当前线程提供的期望值相同, 就认为这个变量没有被其他线程修改过
实际上这种假设不一定总是成立.如有共享变量 count = 0
A 线程对 count 值修改为 10
B 线程对 count 值修改为 20
C 线程对 count 值修改为 0
这就是 CAS 中的 ABA 问题,即共享变量经历了 A->B->A 的更新.如果想要规避 ABA 问题,可以为共享变量引入一个修订号(时间戳), 每次修改共享变量时,相应的修订号就会增加 1. ABA 变量更新过程变量: [A,0] ->[B,1]->[A,2], 每次对共享变量的修改都会导致修订号的增加,通过修订号依然可以准确判断变量是否被其他线程修改过. AtomicStampedReference 类就是基于这种思想产生的。

六 原子变量类

1. 基本介绍
原子变量类基于CAS实现的, 当对共享变量进行read-modify-write更新操作时,通过原子变量类可以保障操作的原子性与可见性.对变量的 read-modify-write 更新操作是指当前操作不是一个简单的赋值,而是变量的新值依赖变量的旧值,如自增操作i++. 由于volatile只能保证可见性,无法保障原子性, 原子变量类内部就是借助一个 Volatile 变量, 并且保障了该变量的 read-modify-write 操作的原子性, 有时把原子变量类看作增强的 volatile 变量. 原子变量类有 12 个,
基础数据型 AtomicInteger, AtomicLong, AtomicBoolean
数组型 AtomicIntegerArray,AtomicLongArray,AtomicReferenceArray
字段更新器 AtomicIntegerFieldUpdater,AtomicLongFieldUpdater, AtomicReferenceFieldUpdater
引用型AtomicReference, AtomicStampedReference, AtomicMarkableReference

2. AtomicLong原子变量实现计数器

/**
* 使用原子变量类定义一个计数器
* 该计数器,在整个程序中都能使用,并且所有的地方都使用这一个计数器,这个计数器可以
设计为单例
*/
public class Indicator {
	//构造方法私有化
	private Indicator(){}
	//定义一个私有的本类静态的对象
	private static final Indicator INSTANCE = new Indicator();
	//3)提供一个公共静态方法返回该类唯一实例
	public static Indicator getInstance(){
		return INSTANCE;
	}
	//使用原子变量类保存请求总数,成功数,失败数
	private final AtomicLong requestCount = new AtomicLong(0); //记录请求总数
	private final AtomicLong successCount = new AtomicLong(0); //处理成功总数
	private final AtomicLong fialureCount = new AtomicLong(0); //处理失败总数
	//有新的请求
	public void newRequestReceive(){
		requestCount.incrementAndGet();
	}
	//处理成功
	public void requestProcessSuccess(){
		successCount.incrementAndGet();
	}
	//处理失败
	public void requestProcessFailure(){
		fialureCount.incrementAndGet();
	}
	//查看总数,成功数,失败数
	public long getRequestCount(){
		return requestCount.get();
	}
	public long getSuccessCount(){
		return successCount.get();
	}
	public long getFailureCount(){
		return fialureCount.get();
	}
}
/**
* 模拟服务器的请求总数, 处理成功数,处理失败数
*/
public class Test {
	public static void main(String[] args) {
		//通过线程模拟请求,在实际应用中可以在 ServletFilter 中调用 Indicator 计数器的相关方法
		for (int i = 0; i < 10000; i++) {
new Thread(new Runnable() {
			@Override
			public void run() {
				//每个线程就是一个请求,请求总数要加 1
				Indicator.getInstance().newRequestReceive();
				int num = new Random().nextInt();
				if ( num % 2 == 0 ){ //偶数模拟成功
					Indicator.getInstance().requestProcessSuccess();
				}else { //处理失败
				Indicator.getInstance().requestProcessFailure();
				}
			}
		}).start();
	}
	try {
		Thread.sleep(1000);
	} catch (InterruptedException e) {
		e.printStackTrace();
	}
	//打印结果
	System.out.println( Indicator.getInstance().getRequestCount()); //总的请求数
	System.out.println( Indicator.getInstance().getSuccessCount()); //成功数
	System.out.println( Indicator.getInstance().getFailureCount()); //失败数
	}
}

3. AtomicIntegerArray原子变量更新数组

public class Test {
	public static void main(String[] args) {
		//1)创建一个指定长度的原子数组
		AtomicIntegerArray atomicIntegerArray = new AtomicIntegerArray(10);
		System.out.println( atomicIntegerArray ); //[0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
		//2)返回指定位置的元素
		System.out.println( atomicIntegerArray.get(0)); //0
		System.out.println( atomicIntegerArray.get(1)); //0
		//3)设置指定位置的元素
		atomicIntegerArray.set(0, 10);
		//在设置数组元素的新值时, 同时返回数组元素的旧值
		System.out.println( atomicIntegerArray.getAndSet(1, 11) ); //0
		System.out.println( atomicIntegerArray ); //[10, 11, 0, 0, 0, 0, 0, 0, 0, 0]
		//4)修改数组元素的值,把数组元素加上某个值
		System.out.println( atomicIntegerArray.addAndGet(0, 22) ); //32
		System.out.println( atomicIntegerArray.getAndAdd(1, 33)); //11
		System.out.println( atomicIntegerArray ); //[32, 44, 0, 0, 0, 0, 0, 0, 0, 0]
		//5)CAS 操作
		//如果数组中索引值为 0 的元素的值是 32 , 就修改为 222
		System.out.println( atomicIntegerArray.compareAndSet(0, 32, 222)); //true
		System.out.println( atomicIntegerArray ); //[222, 44, 0, 0, 0, 0, 0, 0, 0, 0]
		System.out.println( atomicIntegerArray.compareAndSet(1, 11, 333)); //false
		System.out.println(atomicIntegerArray);
		//6)自增/自减
		System.out.println( atomicIntegerArray.incrementAndGet(0) ); //223, 相当于前缀
		System.out.println( atomicIntegerArray.getAndIncrement(1)); //44, 相当于后缀
		System.out.println( atomicIntegerArray ); //[223, 45, 0, 0, 0, 0, 0, 0, 0, 0]
		System.out.println( atomicIntegerArray.decrementAndGet(2)); //-1
		System.out.println( atomicIntegerArray); //[223, 45, -1, 0, 0, 0, 0, 0, 0, 0]
		System.out.println( atomicIntegerArray.getAndDecrement(3)); //0
		System.out.println( atomicIntegerArray ); //[223, 45, -1, -1, 0, 0, 0, 0, 0, 0]
	}
}

public class Test02 {
	//定义原子数组
	static AtomicIntegerArray atomicIntegerArray = new AtomicIntegerArray(10);
	public static void main(String[] args) {
	//定义线程数组
	Thread[] threads = new Thread[10];
	//给线程数组元素赋值
	for (int i = 0; i < threads.length; i++) {
		threads[i] = new AddThread();
	}
	//开启子线程
	for (Thread thread : threads) {
		thread.start();
	}
	//在主线程中查看自增完以后原子数组中的各个元素的值,在主线程中需要在所有子线程都执行完后再查看
	//把所有的子线程合并到当前主线程中
	for (Thread thread : threads) {
		try {
			thread.join();
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}
	System.out.println( atomicIntegerArray );
}
	//定义一个线程类,在线程类中修改原子数组
	static class AddThread extends Thread{
		@Override
		public void run() {
			//把原子数组的每个元素自增 1000 次
			for (int j = 0; j < 100000; j++) {
				for (int i = 0; i < atomicIntegerArray.length(); i++) {
					atomicIntegerArray.getAndIncrement(i % atomicIntegerArray.length());
			}
		}
	/* for (int i = 0; i < 10000; i++) {
		atomicIntegerArray.getAndIncrement(i % atomicIntegerArray.length());
	}*/
		}
	}
}
  1. AtomicIntegerFieldUpdater对原子整数字段更新
    (1) 字符必须使用 volatile 修饰,使线程之间可见
    (2) 只能是实例变量,不能是静态变量,也不能使用 final 修饰
public class User {
	int id;	
	volatile int age;
	public User(int id, int age) {
		this.id = id;
		this.age = age;
	}
	@Override
	public String toString() {
		return "User{" +
						"id=" + id 
						+", age=" + age 
						+'}';
		}
}

public class SubThread extends Thread {
	private User user; //要更新的 User 对象
	//创建 AtomicIntegerFieldUpdater 更新器
	private AtomicIntegerFieldUpdater<User> updater = AtomicIntegerFieldUpdater.newUpdater(User.class, "age");
	public SubThread(User user) {
		this.user = user;
	}
	@Override
	public void run() {
		//在子线程中对 user 对象的 age 字段自增 10 次
		for (int i = 0; i < 10; i++) {
			System.out.println( updater.getAndIncrement(user));
		}
	}
}

public class Test {
	public static void main(String[] args) {
		User user = new User(1234, 10);
		//开启 10 个线程
		for (int i = 0; i < 10; i++) {
			new SubThread(user).start();
		}
		try {
			Thread.sleep(1000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		System.out.println( user );
	}
}

5. AtomicReference原子读写一个对象

public class Test01 {
	//创建一个 AtomicReference 对象
	static AtomicReference<String> atomicReference = new AtomicReference<>("abc");
	public static void main(String[] args) throws InterruptedException {
		//创建 100 个线程修改字符串
		for (int i = 0; i < 100; i++) {
			new Thread(new Runnable() {
				@Override
				public void run() {
					try {
						Thread.sleep(new Random().nextInt(20));
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
					if (atomicReference.compareAndSet("abc","def")){
						System.out.println(Thread.currentThread().getName() + "把字符串abc 更改为 def");
					}
				}
			}).start();
		}
		//再创建 100 个线程
		for (int i = 0; i < 100; i++) {	
			new Thread(new Runnable() {
				@Override
				public void run() {
					try {
						Thread.sleep(new Random().nextInt(20));
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
					if (atomicReference.compareAndSet("def","abc")){
						System.out.println(Thread.currentThread().getName() + "把字符串还原为 abc");
					}
				}
			}).start();
		}
		Thread.sleep(1000);
		System.out.println(atomicReference.get());
	}
}

/**
* 演示 AtomicReference 可能会出现 CAS 的 ABA 问题
*/
public class Test02 {
	private static AtomicReference<String> atomicReference = new AtomicReference<>("abc");
	public static void main(String[] args) throws InterruptedException {
		//创建第一个线程,先把 abc 字符串改为"def",再把字符串还原为 abc
		Thread t1 = new Thread(new Runnable() {
			@Override
			public void run() {
				atomicReference.compareAndSet("abc", "def");
				System.out.println(Thread.currentThread().getName() + "--" +atomicReference.get());
				atomicReference.compareAndSet("def", "abc");
			}
		});
		Thread t2 = new Thread(new Runnable() {
			@Override
			public void run() {
			try {
				TimeUnit.SECONDS.sleep(1);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			System.out.println( atomicReference.compareAndSet("abc", "ghg"));
		}
	});
	t1.start();
	t2.start();
	t1.join();
	t2.join();
	System.out.println( atomicReference.get());
}
}
/**
* AtomicStampedReference 原子类可以解决 CAS 中的 ABA 问题
* 在 AtomicStampedReference 原子类中有一个整数标记值 stamp, 每次执行 CAS 操作时,需
要对比它的版本,即比较 stamp 的值
*/
public class Test03 {
	// private static AtomicReference<String> atomicReference = new AtomicReference<>("abc");
	//定义 AtomicStampedReference 引用操作"abc"字符串,指定初始化版本号为 0
	private static AtomicStampedReference<String> stampedReference = new
AtomicStampedReference<>("abc", 0);
	public static void main(String[] args) throws InterruptedException {
	Thread t1 = new Thread(new Runnable() {
		@Override
		public void run() {
			stampedReference.compareAndSet("abc", "def", stampedReference.getStamp(),stampedReference.getStamp()+1);
			System.out.println(Thread.currentThread().getName() + "--" +stampedReference.getReference());
			stampedReference.compareAndSet("def", "abc", stampedReference.getStamp(),stampedReference.getStamp()+1);
			}
		});
		Thread t2 = new Thread(new Runnable() {
			@Override
			public void run() {
				int stamp = stampedReference.getStamp(); //获得版本号
				try {
					TimeUnit.SECONDS.sleep(1);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
				System.out.println( stampedReference.compareAndSet("abc", "ggg", stamp, stamp+1));
			}
		});
		t1.start();
		t2.start();
		t1.join();
		t2.join();
		System.out.println( stampedReference.getReference() );
	}
}

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
提供的源码资源涵盖了Java应用等多个领域,每个领域都包含了丰富的实例和项目。这些源码都是基于各自平台的最新技术和标准编写,确保了在对应环境下能够无缝运行。同时,源码中配备了详细的注释和文档,帮助用户快速理解代码结构和实现逻辑。 适用人群: 适合毕业设计、课程设计作业。这些源码资源特别适合大学生群体。无论你是计算机相关专业的学生,还是对其他领域编程感兴趣的学生,这些资源都能为你提供宝贵的学习和实践机。通过学习和运行这些源码,你可以掌握各平台开发的基础知识,提升编程能力和项目实战经验。 使用场景及目标: 在学习阶段,你可以利用这些源码资源进行课程实践、课外项目或毕业设计。通过分析和运行源码,你将深入了解各平台开发的技术细节和最佳实践,逐步培养起自己的项目开发和问题解决能力。此外,在求职或创业过程中,具备跨平台开发能力的大学生将更具竞争力。 其他说明: 为了确保源码资源的可运行性和易用性,特别注意了以下几点:首先,每份源码都提供了详细的运行环境和依赖说明,确保用户能够轻松搭建起开发环境;其次,源码中的注释和文档都非常完善,方便用户快速上手和理解代码;最后,我定期更新这些源码资源,以适应各平台技术的最新发展和市场需求。 所有源码均经过严格测试,可以直接运行,可以放心下载使用。有任何使用问题欢迎随时与博主沟通,第一时间进行解答!

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值