多线程并发的情况下可能存在的安全问题

a.可见性问题

主内存的变量更新之后,工作内存中副本没有立刻得到最新的值,不可见性

b.有序性问题

public static int a = 0;
public static boolean b = false;
单线程中代码重排是没有影响的,但是多线程中代码重排对结果有影响的,代码的无序性

c.原子性问题

a++操作,分成三步: a.获取a b.增加a c更新a,这三步操作可能被其他线程打断,不能保证原子性

第一章 volatile关键字【理解】
volatile是什么

是一个关键字,用于修饰成员变量,主要用于解决可见性和有序性问题,但是无法解决原子性问题!!

volatile解决可见性

代码示例:

public class MyThread extends Thread {
    //保证所有线程每次使用a时都会强制更新
    public volatile static  int a = 0;
    @Override
    public void run() {
        System.out.println("线程启动,休息2秒...");
        try {
            Thread.sleep(1000 * 2);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("将a的值改为1");
        a = 1;
        System.out.println("线程结束...");
    }
}

public class TestDemo {
    public static void main(String[] args) {
        //1.启动线程
        MyThread t = new MyThread();
        t.start();
        //2.主线程继续
        while (true) {
            if (MyThread.a == 1) {
                System.out.println("主线程读到了a = 1");
            }
        }
    }
}
volatile解决有序性

a和b由于使用了volatile关键字修饰,那么编译器就不会进行优化,即对a和b进行赋值的代码不会进行"重排"!
在这里插入图片描述

volatile不能解决原子性
public class MyThread extends Thread {
    //添加了volatile关键字,也不能保证a++的操作不被其他线程打断
    public volatile static int a = 0;
    @Override
    public void run() {
        for (int i = 0; i < 10000; i++) {
            a++;//a.获取a的值 b.增加1 c.更新a的值
        }
        System.out.println("修改完毕!");
    }
}

public class AutomicDemo {
    public static void main(String[] args) throws InterruptedException {
        MyThread t1 = new MyThread();
        t1.start();

        MyThread t2 = new MyThread();
        t2.start();


        Thread.sleep(2000);
        System.out.println(MyThread.a);//20000

    }
}
输出结果:
	修改完毕!
    修改完毕!
    13752(还是小于20000)
volatile的作用:a.解决可见性 b.解决有序性 c."不能"解决原子性  

第二章 原子类【理解】

a.什么是原子类?
在java.util.concurrent.atomic包下定义的类,基本都是原子类
b.原子类的作用?
保证变量的操作是原子性操作(也能解决变量操作的有序性和可见性)
c.原子类有哪些?
AtomicInteger 对int基本类型的原子类
AtomicLong 对long基本类型的原子类
AtomicBoolean 对boolean基本类型的原子类

AtomicInteger类示例【重点】

a.AtomicInteger是什么?

为基本类型int提供的原子类

b.AtomicInteger的构造方法

public AtomicInteger(int num);

c.AtomicInteger的成员方法

public int getAndIncrement();//相当于,变量++
public int incrementAndget();//相当于,++变量
也有相当于,变量–或者–变量的方法

d.使用AtomicInteger改写案例

public class MyThread extends Thread {
//AtomicInteger就能保证变量++或者++变量的原子性
public static AtomicInteger a = new AtomicInteger(0);
@Override
public void run() {
for (int i = 0; i < 10000; i++) {
a.getAndIncrement();//相当于a++
}
System.out.println(“修改完毕!”);
}
}
运行结果:
修改完毕!
修改完毕!
20000(每次都是20000)

AtomicInteger类的工作原理-CAS机制【了解】

在这里插入图片描述

AtomicIntegerArray类示例【了解】

非原子类数组在多线程并发时会有问题

public class MyThread extends Thread {
    public static int[] arr = new int[1000];//不直接使用数组
    @Override
    public void run() {
        for (int i = 0; i < arr.length; i++) {
            arr[i]++;
        }
    }
}

public class TestDemo {
    public static void main(String[] args) throws InterruptedException {
        for (int i = 0; i < 1000; i++) {
            new MyThread().start();//创建1000个线程,每个线程为数组的每个元素+1
        }
        //结果理论上是什么:1000个1000啊!!
        Thread.sleep(1000 * 5);//让所有线程执行完毕
        System.out.println("主线程休息5秒醒来");
        for (int i = 0; i < MyThread.arr.length; i++) {
            System.out.println(MyThread.arr[i]);
        }
        //结果实际上是小于1000的(多尝试几次)
    }
}
运行结果:
	有可能出现小于1000的元素

使用原子类数组,保证原子性,解决问题

使用AtomicIntegerArray解决非原子类数组的原子性问题
public class MyThread extends Thread {
//    public static int[] arr = new int[1000];//不直接使用数组
    public static AtomicIntegerArray arr = new AtomicIntegerArray(1000);//直接使用原子类数组
    @Override
    public void run() {
        for (int i = 0; i < arr.length(); i++) {
            arr.getAndIncrement(i);//相当于数组操作中的arr[i]++
//            arr.incrementAndGet(i);//相当于数组操作中的++arr[i]
//            arr.getAndAdd(i,1);
//            arr.addAndGet(i,1);
            //以上四种方式在本题中均可
        }
    }
} 

public class TestDemo {
    public static void main(String[] args) throws InterruptedException {
        for (int i = 0; i < 1000; i++) {
            new MyThread().start();//创建1000个线程,每个线程为数组的每个元素+1
        }
        //结果理论上是什么:1000个1000啊!!
        Thread.sleep(1000 * 5);//让所有线程执行完毕
        System.out.println("主线程休息5秒醒来");
        //结果实际上是小于1000的(多尝试几次)
        System.out.println(MyThread.arr);
    }
}
输出结果:
	[1000,1000,.....都是1000]

AtomicInteger能解决什么问题?
主要解决的变量操作的原子性问题,也能解决可见性和有序性问题
但是: 原子类无法解决多句代码的原子性问题

synchronized关键字【重点】
多行代码的原子性安全问题–卖票案例【非常重点】
/**
 * 卖票任务
 */
public class MyRunnable implements Runnable {
    /**
     * 票数
     */
    public int count = 100;
    @Override
    public void run() {
        while (true){
            if (count > 0) {
                try {
                    Thread.sleep(30);//模拟卖票掏钱消耗的时间
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName()+"卖出第"+count+"张票");
                count--;
            }
        }
    }
}

public class TestDemo {
    public static void main(String[] args) {
        //0.创建任务
        MyRunnable mr = new MyRunnable();
        //1.创建窗口123
        Thread t1 = new Thread(mr);
        Thread t2 = new Thread(mr);
        Thread t3 = new Thread(mr);
        t1.start();
        t2.start();
        t3.start();
        //出现多线程安全问题:
        //a.出现了重复数据
        //b.出现了0,-1非法数据
    }
}

重复数据出现的原因:
当某个线程卖出某张后,还没来得及对票数减1,被其他线程抢走CPU,导致其他线程也卖出同张票!!
非法数据出现的原因:
当只剩下最后一张票时,由于多线程的随机切换可能多个线程都会通过大于0的判断, 最终导致卖出的票是0,-1,-2…这些张数!!!
在这里插入图片描述

synchronized关键字介绍

synchronized是什么??

一个可以用于让多行代码保证原子性的关键字

synchronized的作用??

让多行代码"同步",当某个线程进入这多行代码执行时,其他线程是无法进入的,直到多行代码都运行完毕了,其他线程才能进入

解决方案1_同步代码块
格式:
	synchronized(任意对象){ //也叫做锁对象
        需要同步的代码(需要保证原子性操作的那些代码)
    }

/**
 * 卖票任务
 */
public class MyRunnable implements Runnable {
    /**
     * 票数
     */
    public int count = 100;
    /**
     * 创建一个对象
     */
    public Object obj = new Object();
    @Override
    public void run() {
        while (true){
            //同步代码块
            synchronized (obj) {//任意对象都可以作为锁对象
                if (count > 0) {
                    System.out.println(Thread.currentThread().getName() + "卖出第" + count + "张票");
                    count--;
                }
            }
        }
    }
}

public class TestDemo {
    public static void main(String[] args) {
        //0.创建任务
        MyRunnable mr = new MyRunnable();
        //1.创建窗口123
        Thread t1 = new Thread(mr,"窗口1");
        Thread t2 = new Thread(mr,"窗口2");
        Thread t3 = new Thread(mr,"窗口3");
        t1.start();
        t2.start();
        t3.start();
        //出现多线程安全问题:
        //a.出现了重复数据
        //b.出现了0,-1非法数据
    }
}

注意:
	a.synchronized()中的锁对象,可以是任意对象,但是必须保证多个线程使用的是同一个锁对象   
解决方案2_同步方法
格式:
	public synchronized void 方法名(){
     	需要同步的代码(需要保证原子性的代码)   
    }

/**
 * 卖票任务
 */
public class MyRunnable implements Runnable {
    /**
     * 票数
     */
    public int count = 100;
    /**
     * 创建一个对象
     */
    public Object obj = new Object();

    @Override
    public void run() {
        while (true){
            sell();
        }
    }
    //同步方法,原理和同步代码块基本是一致的,区别在于同步代码块的锁对象是我们指定的,而同步方法也需要锁对象,但是不需要我们指定
    //由编译器自己指定,指定当前对象this
    public synchronized void sell(){
        if (count > 0) {
            System.out.println(Thread.currentThread().getName() + "卖出第" + count + "张票");
            count--;
        }
    }
}

注意:
	a.同步方法需要不需要锁对象??? 需要,但是不需要我们去指定,默认使用当前对象this
    b.同步方法可以是静态方法呢?? 可以,但是此时默认锁对象使用当前类的字节码文件   

解决方案3_Lock锁

Lock实际上是一个接口,我们要使用它的实现类,ReentrantLock锁对象
    其中有两个方法:
		public void lock(); //获取锁
		public void unlock(); //释放锁

格式:
	Lock lock = new ReentrantLock();
	
	lock.lock();//加锁
		需要同步的代码,需要保证原子性的代码
    lock.unlock();//解锁  

/**
 * 卖票任务
 */
public class MyRunnable implements Runnable {
    /**
     * 票数
     */
    public int count = 100;
    /**
     * 创建一个Lock锁对象
     */
    public Lock lock = new ReentrantLock();

    @Override
    public void run() {
        while (true){
            //加锁
            lock.lock();
                if (count > 0) {
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                    System.out.println(Thread.currentThread().getName() + "卖出第" + count + "张票");
                    count--;
                }
            lock.unlock();
        }
    }
}
public class TestDemo {
    public static void main(String[] args) {
        //0.创建任务
        MyRunnable mr = new MyRunnable();
        //1.创建窗口123
        new Thread(mr ,"窗口1").start();
        new Thread(mr ,"窗口2").start();
        new Thread(mr ,"窗口3").start();
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

lemon20120331

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值