Java多线程 volatile及synchronized还有原子类你还不会用吗

java基础(jdk1.8)

**

多线程—volatile、原子类及synchronized使用

**

此篇博客主要讲volatile及synchronized关键字的使用

本篇链接


情景

我们先要知道这么一个概念,那就是什么是线程安全。简单来说,线程安全就是在多线程状态下程序执行结果和单线程执行结果相同。

先看这个代码块

	public class A{
		int count = 0;
		public static void main(String []args)
		{
			A a = new A();
			for (int i = 0 ; i < 10 ; i++)
			{
				for (int j = 0 ; j < 1000 ; j++){
					a.count++;
				}
			}
			System.out.println(a.count);
		}
	}
	/*
	这个小代码块,则是10个对a进行1000次++操作
	结果自然是10000
	 */

接下来使用多线程试试

	public class A{
		int count = 0;
		public static void main(String []args)
		{
			A a = new A();
			for (int i = 0 ; i < 10 ; i++)
			{
				new Thread(()->{
                    for (int j = 0 ; j < 1000 ; j++){
                        a.count++;
                    }
            	}).start();
			}
			try {
            	TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
			System.out.println(a.count);
		}
	}
	/*
	这个小代码块,则是10个线程对a进行1000次++操作
	结果则加不到10000
	这就是线程的安全性问题
	 */

这里就有一个问题则是这个++操作,因为其分为三步,
第一步:取值
第二步:+1
第三步:赋值

而且,取值与赋值是相对于缓存操作的,具体看volatile

volatile关键字

  • 功能

保证内存可见性&&禁止指令重排序

  1. 使一个变量在多个线程中可见

  2. 一个线程在更改变量后,会通知其他线程变量改变,使其他线程更改内容:

	高并发过程中,线程对一个变量的访问可能在缓存中,而主存的内容改变,没有 volatile关键字的情况下,线程一直访问缓存,导致程序不能及时刷新而出现问题。加了volatile之后,变量改变后,会通知所有线程,使其各自的缓冲更新一次,达到精准的效果。
  1. 但volatile不能保证多个线程对变量共同修改导致的不一致问题,只有可见性,而 synchronized既有原子性也保证可见性,但效率相对低一些。
  • 使用方式
	class A{
		int a;
		//volatile关键字直接加到字段上即可保证这个字段有上面两个功能
		volatile int b;
	}
  • 注意

这个volatile并不能保证上面的代码可以加到10000,但是可以保证读取数据时的一个读取正常
相对来说,加上volatile比不加会稍微高一些,当还是有问题

volatile详解

synchronized关键字

  • 功能

锁,互斥锁

任何线程想执行以下代码的时候,必须先申请(堆上对象,而不是引用)的锁
synchronized是互斥锁(只要有人拿到锁,其他就拿不到锁)

  1. 从程序角度考虑,synchronized相当于一个原子性操作,此代码块为一个原子操作,不可被打断

  2. synchronized是对对象加锁,同一个对象执行到synchronized时,必须获取对象锁才能继续执行

  3. 在一个线程访问synchronized的过程中,其他线程是可以访问该对象的非synchronized方法(读写问题,写加锁,读不加锁,产生脏读现象 )

  4. 在一个线程获取到对象锁的时候,再次申请该对象的锁时还是可以获取到该对象的锁,所以synchronized获得的锁是可重入的

  5. 子类同步方法可以调用父类同步方法

  6. synchronized 锁遇到异常时会释放,并发编程中必须对异常处理。

  7. synchronized 加在static方法上,锁的是(类.class),非静态方法,则锁的是(this)

  • 使用
	public class A{
		int count = 0;
		public static void main(String []args)
		{
			A a = new A();
			for (int i = 0 ; i < 10 ; i++)
			{
				new Thread(()->{
                    synchronized (a)
                    {
                    	for (int j = 0 ; j < 1000 ; j++){
                        	a.count++;
                    	}
                    }
            	}).start();
			}
			//使用睡眠保证在所有的线程执行完然后主线程输出count的值
			try {
            	TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
			System.out.println(a.count);
		}
	}

synchronized使用两种方式:
1.直接加在方法上

2.使用以上代码块的方式

3.其他位置不行

  • 注意

加到普通方法即和代码块上(this)相同,加到静态方法和代码块(A.class)

synchronized详解

原子操作类

AtomicInteger类的incrementAndGet()方法代替++,(不用synchronized)可以使效率更高。AtomicXXX的几个原子性方法并用,则破坏其原子性,也会被其他线程插入

原子操作类在操作时使用了CAS

上代码

	
	import java.util.ArrayList;
	import java.util.List;
	import java.util.concurrent.atomic.AtomicInteger;

	public class A{
		//volatile只保证可见性,所以并不能使count到100000
    	/*volatile*/ int count = 0;

        //原子性操作的数据,效率比synchronized和count++更高
        AtomicInteger acount = new AtomicInteger(0);

        /*synchronized*/ void m(){
            for (int i = 0;i<10000;i++)
            {
                count++;
                acount.incrementAndGet();//原子性的 count++
            }
        }

        public static void main(String[] args) {
            List list = new ArrayList();
            list.add("");
            A v = new A();
            List <Thread> threads = new ArrayList<>();
            for (int i = 0;i<10;i++)
            {
                threads.add(new Thread(v::m,"Thread:"+i));
            }
            threads.forEach((o)->o.start());

            threads.forEach((o)->{
                try{
                    o.join();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            });
            System.out.println(v.count);
            System.out.println(v.acount);
        }
	}

常见问题

  1. synchronized代码优化,代码块中代码尽量少写

  2. synchronized锁定的是new 堆的对象,而不是引用内存,如果对象的引用改变,则也会改变
    :Object o = new Object();
    o = new Object();//此时的锁也会相应改变。

  3. synchronized不要将String作为锁定对象,因为字符串内容一样时,其锁是同一个字符串常量

  4. 高并发过程中

    (1)在并发量较大的情况下increment 自增

    (2)synchronized加锁和原子操作类相比,原子操作类还是速度快一些

    (3)LongAdder 在并发量相当高的时候效率会非常快
    使用数组将其分开,平均线程去操作数组的每个值,最后合成

上一篇 》锁的类型及概念


下一篇 》ReentrantLock使用

展开阅读全文

Windows版YOLOv4目标检测实战:训练自己的数据集

04-26
©️2020 CSDN 皮肤主题: 技术黑板 设计师: CSDN官方博客 返回首页
实付0元
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值