原子性

本文详细探讨了Java中的Volatile关键字及其在解决线程间同步问题中的作用。通过代码示例展示了Volatile如何确保线程可见性但无法保证原子性。进一步解释了原子性操作的重要性,并通过AtomicInteger类展示了如何使用原子类来实现线程安全的计数。文章还提到了并发工具类如Semaphore在控制并发访问资源数量中的应用。
摘要由CSDN通过智能技术生成

Volatile关键字

1.出现原因:
代码演示:
public class Money {
public static int money = 100000;
}

public class MyThread1 extends Thread{
@Override
public void run() {
while (Money.money == 100000){

    }
    System.out.println("结婚基金已经不是十万了");
}

}

public class MyThread2 extends Thread {
@Override
public void run() {
try {
Thread.sleep(100);
Money.money = 90000;
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}

public static void main(String[] args) {
MyThread1 t1 = new MyThread1();
t1.setName(“老王”);
t1.start();

   MyThread2 t2 = new MyThread2();
   t2.setName("隔壁");
   t2.start();
}
程序问题 :**  线程1虽然知道钱是十万,但是当钱的余额发生变化的时候,线程1无法知道最新的值。

2.volatile
上边代码出现的问题:
当线程Thread2修改了共享数据时,线程Thread1没有及时获取到最新的值,如果还在使用原先的值,就会出现问题;
堆内存是唯一的,每一个线程都有自己的线程栈;
每一个线程在使用堆里面变量的时候,都会先拷贝一份到变量的副本中;
在线程中,每一次使用是从变量的副本中获取的.
Volatile关键字:强制线程每次在使用的时候,都会看一下共享区域最新的值;
代码演示:(使用volatile关键字解决)
public class Money {
public static volatile int money = 100000;
}

public class MyThread1 extends Thread{
@Override
public void run() {
while (Money.money == 100000){

    }
    System.out.println("结婚基金已经不是十万了");
}

}

public class MyThread2 extends Thread {
@Override
public void run() {
try {
Thread.sleep(100);
Money.money = 90000;
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}

public static void main(String[] args) {
MyThread1 t1 = new MyThread1();
t1.setName(“老王”);
t1.start();

   MyThread2 t2 = new MyThread2();
   t2.setName("隔壁");
   t2.start();
}
  1. synchronized(锁)解决
    synchronized解决: 线程获得锁; 清空变量副本; 拷贝共享变量最新的值到变量副本中; 执行代码; 将修改后变量副本中的值赋
    值给共享数据; 释放锁.
    代码演示:
    public class Money {
    public static Object lock = new Object();
    public static int money = 100000;
    }

public class MyThread1 extends Thread{
@Override
public void run() {
while (true){
synchronized (Money.lock){
if (Money.money != 100000){
System.out.println(“结婚基金已经不是十万了”);
break;
}
}
}
}
}

public class MyThread2 extends Thread {
@Override
public void run() {
synchronized (Money.lock){
try {
Thread.sleep(100);
Money.money = 90000;
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}

public static void main(String[] args) {
MyThread1 t1 = new MyThread1();
t1.setName(“老王”);
t1.start();

   MyThread2 t2 = new MyThread2();
   t2.setName("隔壁");
   t2.start();
}

原子性

1.原子性:所谓的原子性 是指在一次操作或者多次操作中, 要么所有的操作全部都得到了执行并且不会受到任何因素的干扰而中断,要么所有的操作都不执行,多个操作是一个不可分割的整体.
代码演示:
public class MyAtomThread implements Runnable {
private volatile int count = 0;//送花的数量
@Override
public void run() {
for (int i = 0; i < 100; i++) {
//1,从共享数据中读取数据到本线程栈中.
//2,修改本线程栈中变量副本的值
//3,会把本线程栈中变量副本的值赋值给共享数据.
count++;
System.out.println(“已经送了” + count + “朵花”);
}
}
}

public static void main(String[] args) {
MyAtomThread atom = new MyAtomThread();
for (int i = 0; i < 100; i++) {
new Thread(atom).start();
}
}
代码多运行几次: count++ 不是一个原子性操作,在执行的过程中,有可能被其他线程打断.
2.volatile关键字不能保证原子性:
要想达到预期效果: 可以给count++ 操作添加锁,那么count++ 操作就是临界区中的代码,临界区中的代码一次只能被一个线程
去执行,这个时候count++就变成了原子操作.
代码演示:
public class MyAtomThread implements Runnable {
private volatile int count = 0;//送花的数量
private Object lock = new Object();
@Override
public void run() {
for (int i = 0; i < 100; i++) {
//1,从共享数据中读取数据到本线程栈中.
//2,修改本线程栈中变量副本的值
//3,会把本线程栈中变量副本的值赋值给共享数据.
synchronized (lock) {
count++;
System.out.println(“已经送了” + count + “朵花”);
}
}
}
}

public static void main(String[] args) {
MyAtomThread atom = new MyAtomThread();
for (int i = 0; i < 100; i++) {
new Thread(atom).start();
}
}
多运行几次看结果.
3.原子性–Atomiclnteger
Java从JDK1.5开始提供了java.util.concurrent.atomic包(简称Atomic包),这个包中的原子操作类提供了一种用法简单,性能高效,线程安全地更新一个变量的方式.因为变量的类型有很多种,所以在Atomic包里一共提供了13个类,属于4种类型的原子更新方式,分别是原子更新基本类型、原子更新数组、原子更新引用和原子更新属性(字段)。
使用原子的方式更新基本类型,使用原子的方式更新基本类型Atomic包提供了以下3个类:
AtomicBoolean: 原子更新布尔类型
AtomicInteger: 原子更新整型
AtomicLong: 原子更新长整型
常用方法:
public AtomicInteger(): 初始化一个默认值为0的原子型Integer
public AtomicInteger(int initialValue): 初始化一个指定值的原子型Integer

int get(): 获取值
int getAndIncrement(): 以原子方式将当前值加1,注意,这里返回的是自增前的值。
int incrementAndGet(): 以原子方式将当前值加1,注意,这里返回的是自增后的值。
int addAndGet(int data): 以原子方式将输入的数值与实例中的值(AtomicInteger里的value)相加,并返回结果。
int getAndSet(int value): 以原子方式设置为newValue的值,并返回旧值。
代码演示:
public class Test11 {
// public AtomicInteger(); 初始化一个默认值为0的原子型Integer
// public AtomicInteger(int initialvalue): 初始化一个指定值的原子性Integer
public static void main(String[] args) {
AtomicInteger ac = new AtomicInteger();
System.out.println(ac);
AtomicInteger ac1 = new AtomicInteger(10);
System.out.println(ac1);
}
}

public class Test11 {
// int get(): 获取值
// int getAndIncrement(): 以原子方式将当前值加1,注意,这里返回的是自增前的值。
// int incrementAndGet(): 以原子方式将当前值加1,注意,这里返回的是自增后的值。
// int addAndGet(int data): 以原子方式将参数与对象中的值相加,并返回结果。
// int getAndSet(int value): 以原子方式设置为newValue的值,并返回旧值。
public static void main(String[] args) {
// AtomicInteger ac1 = new AtomicInteger(10);
// System.out.println(ac1);
//
// AtomicInteger ac2 = new AtomicInteger(10);
// int andIncrement = ac2.getAndIncrement();
// System.out.println(andIncrement);
// System.out.println(ac2.get());

// AtomicInteger ac3 = new AtomicInteger(10);
// int i = ac3.incrementAndGet();
// System.out.println(i);
// System.out.println(ac3.get());

// AtomicInteger ac4 = new AtomicInteger(10);
// int i1 = ac4.addAndGet(20);
// System.out.println(i1);
// System.out.println(ac4.get());

    AtomicInteger ac5 = new AtomicInteger(10);
    int andSet = ac5.getAndSet(20);
    System.out.println(andSet);
    System.out.println(ac5.get());
}

}
4.AtomicInteger-内存解析
AtomicInteger原理: 自旋锁+CAS算法
CAS算法:​ 有3个操作数(内存值V, 旧的预期值A,要修改的值B)
​ 当旧的预期值A == 内存值 此时修改成功,将V改为B
​ 当旧的预期值A!=内存值 此时修改失败,不做任何操作
​ 并重新获取现在的最新值(这个重新获取的动作就是自旋)
5.​ AtomicInteger-源码解析
public class AtomDemo {
public static void main(String[] args) {
MyAtomThread atom = new MyAtomThread();
for (int i = 0; i < 100; i++) {
new Thread(atom).start();
}
}
}

public class MyAtomThread implements Runnable {
//private volatile int count = 0; //送花的数量
//private Object lock = new Object();
AtomicInteger ac = new AtomicInteger(0);

@Override
public void run() {
    for (int i = 0; i < 100; i++) {
        //1,从共享数据中读取数据到本线程栈中.
        //2,修改本线程栈中变量副本的值
        //3,会把本线程栈中变量副本的值赋值给共享数据.
        //synchronized (lock) {

// count++;
// ac++;
int count = ac.incrementAndGet();
System.out.println(“已经送了” + count + “朵花”);
// }
}
}
}

解析:
//先自增,然后获取自增后的结果
public final int incrementAndGet() {
//+ 1 自增后的结果
//this 就表示当前的atomicInteger(值)
//1 自增一次
return U.getAndAddInt(this, VALUE, 1) + 1;
}

public final int getAndAddInt(Object o, long offset, int delta) {
//v 旧值
int v;
//自旋的过程
do {
//不断的获取旧值
v = getIntVolatile(o, offset);
//如果这个方法的返回值为false,那么继续自旋
//如果这个方法的返回值为true,那么自旋结束
//o 表示的就是内存值
//v 旧值
//v + delta 修改后的值
} while (!weakCompareAndSetInt(o, offset, v, v + delta));
//作用:比较内存中的值,旧值是否相等,如果相等就把修改后的值写到内存中,返回true。表示修改成功。
// 如果不相等,无法把修改后的值写到内存中,返回false。表示修改失败。
//如果修改失败,那么继续自旋。
return v;
}
6.悲观锁和乐观锁
synchronized和CAS的区别 :
**相同点:**在多线程情况下,都可以保证共享数据的安全性。
**不同点:**synchronized总是从最坏的角度出发,认为每次获取数据的时候,别人都有可能修改。所以在每 次操作共享数据之前,都会上锁。(悲观锁)
​ cas是从乐观的角度出发,假设每次获取数据别人都不会修改,所以不会上锁。只不过在修改共享数据的时候,会检查一下,别人有没有修改过这个数据。如果别人修改过,那么我再次获取现在最新的值. 如果别人没有修改过,那么我现在直接修改共享数据的值.(乐观锁)

并发工具类

1.并发工具类–Hashtable
Hashtable出现的原因:在集合类钟HashMap是比较常用的集合对象,但是HashMap是线程不安全的(多线程下可能会存在问题).为了保证数据的安全性,可以使用Hashtable,但是Hashtable的效率低下.
代码演示:
public static void main(String[] args) {
Hashtable<String,String> hm = new Hashtable<>();
Thread t1 = new Thread(()->{
for (int i = 0; i < 25; i++) {
hm.put(i + " ", i + " ");
}
});
Thread t2 = new Thread(()->{
for (int i = 25; i < 51; i++) {
hm.put(i + " ", i + " ");
}
});
t1.start();
t2.start();

    try {
        Thread.sleep(1000);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    for (int i = 0; i < 51; i++) {
        System.out.println(hm.get(i + " "));
    }
}

2.并发工具类-ConcurrentHashMap基本使用
ConcurrentHashMap出现的原因 : 在集合类中HashMap是比较常用的集合对象,但是HashMap是线程不安全的(多线程环境下可能会存在问题)。为了保证数据的安全性我们可以使用Hashtable,但是Hashtable的效率低下。
基于以上两个原因我们可以使用JDK1.5以后所提供的ConcurrentHashMap。
Map接口: HashMap ; Hashtable ; TreeMap ; ConcurrenHashMap
1 ,HashMap是线程不安全的。多线程环境下会有数据安全问题
​ 2 ,Hashtable是线程安全的,但是会将整张表锁起来,效率低下
​ 3,ConcurrentHashMap也是线程安全的,效率较高。 在JDK7和JDK8中,底层原理不一样。
代码演示:
public static void main(String[] args) {
ConcurrentHashMap<String,String> hm = new ConcurrentHashMap<>();
Thread t1 = new Thread(()->{
for (int i = 0; i < 25; i++) {
hm.put(i + " ", i + " ");
}
});
Thread t2 = new Thread(()->{
for (int i = 25; i < 51; i++) {
hm.put(i + " ", i + " ");
}
});
t1.start();
t2.start();

    try {
        Thread.sleep(1000);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    for (int i = 0; i < 51; i++) {
        System.out.println(hm.get(i + " "));
    }
}

3.并发工具类-ConcurrentHashMap1.7原理和1.8原理
如果使用空参构造创建ConcurrentHashMap对象,则什么事情都不做。 在第一次添加元素的时候创建哈希表
计算当前元素应存入的索引。
如果该索引位置为null,则利用cas算法,将本结点添加到数组中。
如果该索引位置不为null,则利用volatile关键字获得当前位置最新的结点地址,挂在他下面,变成链表。
当 链表的长度大于等于8时,自动转换成红黑树6,以链表或者红黑树头结点为锁对象,配合悲观锁保证多线程操作集合
时数据的安全性
4.并发工具类-CountDownLatch
方法 解释
public CountDownLatch(int count) 参数传递线程数,表示等待线程数量
public void await() 让线程等待
public void countDown() 当前线程执行完毕
使用场景: 让某一条线程等待其他线程执行完毕之后再执行
代码演示:
public class Children1 extends Thread {
private CountDownLatch countDownLatch;

public Children1(CountDownLatch countDownLatch) {
    this.countDownLatch = countDownLatch;
}

@Override
public void run() {
    for (int i = 0; i < 10; i++) {
    System.out.println(getName() + "吃第" + i + "个饺子");
    }
    countDownLatch.countDown();
}

}

public class Children2 extends Thread {
private CountDownLatch countDownLatch;

public Children2(CountDownLatch countDownLatch) {
    this.countDownLatch = countDownLatch;
}

@Override
public void run() {
    for (int i = 0; i < 15; i++) {
        System.out.println(getName() + "在吃第" + i + "个饺子");
    }
    countDownLatch.countDown();
}

}

public class Children3 extends Thread {
private CountDownLatch countDownLatch;

public Children3(CountDownLatch countDownLatch) {
    this.countDownLatch = countDownLatch;
}

@Override
public void run() {
    for (int i = 0; i < 20; i++) {
        System.out.println(getName() + "在吃第" + i +"个饺子");
    }
    countDownLatch.countDown();
}

}

public class Mother extends Thread {
private CountDownLatch countDownLatch;

public Mother(CountDownLatch countDownLatch) {
    this.countDownLatch = countDownLatch;
}

@Override
public void run() {
    try {
        countDownLatch.await();
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    System.out.println("妈妈收拾碗筷");
}

}

public static void main(String[] args) {
CountDownLatch countDownLatch = new CountDownLatch(3);

    Mother m = new Mother(countDownLatch);
    m.start();

    Children1 c1 = new Children1(countDownLatch);
    c1.setName("阿里");
    Children2 c2 = new Children2(countDownLatch);
    c2.setName("小云");
    Children3 c3 = new Children3(countDownLatch);
    c3.setName("波波");

    c1.start();
    c2.start();
    c3.start();
}

5.并发工具类-Semaphore
使用场景 :可以控制访问特定资源的线程数量。
实现步骤 :需要有人管理这个通道;
当有车进来了,发通行许可证
当车出去了,收回通行许可证
如果通行许可证发完了,那么其他车辆只能等着
代码演示:
public class MyRunnable implements Runnable {
private Semaphore semaphore = new Semaphore(2);
@Override
public void run() {
try {
semaphore.acquire();
System.out.println(Thread.currentThread().getName()+“获得通行证开始通行” );
Thread.sleep(300);
System.out.println(Thread.currentThread().getName()+“归还通行证” );
semaphore.release();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}

public static void main(String[] args) {
MyRunnable mr = new MyRunnable();
for (int i = 0; i < 100; i++) {
new Thread(mr).start();
}
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值