VB.net 2010 视频教程 VB.net 2010 视频教程 python基础视频教程
SQL Server 2008 视频教程 c#入门经典教程 Visual Basic从门到精通视频教程
当前位置:
首页 > 编程开发 > Java教程 >
  • 浅谈volatile ,synchronized,CAS,锁升级

1.volatile

俩个功能:1.保证线程可见性 2.禁止指令重排序

前言

并发编程的3个条件
1.原子性:要实现原子性方式较多,可用synchronized、lock加锁,AtomicInteger等,但volatile关键字是无法保证原子性的;
2.可见性:要实现可见性,也可用synchronized、lock,volatile关键字可用来保证可见性;
3.有序性:要避免指令重排序,synchronized、lock作用的代码块自然是有序执行的,volatile关键字有效的禁止了指令重排序,实现了程序执行的有序性;

1.1保证线程可见性

假设A B线程都用到一个变量,java默认是A线程中保留一份copy,这样如果B线程修改了该变量,则A线程未必知道,使用volatile关键字,会让所有线程都会读到变量的修改值

在下面的代码中,running是存在于堆内存的t对象中,当线程t1开始运行的时候,会把running值从内存中读到t1线程的工作区,在运行过程中直接使用这个copy,并不会每次都去读取堆内存,这样当主线程修改running的值之后,t1线程感知不到,所以start会先运行一段时间才end,而在running前面加上volatile后,将会强制所有线程都去堆内存中读取running的值,所以很快会到end。但同时,volatile并不能保证多个线程共同修改running变量时所带来的不一致问题

复制代码
//没有volatile,start会过一段时间才停下来,因为线程t1不会每次都去读取堆内存
  private static volatile boolean running = true;
    private static void m() {
        System.out.println("m start");
        while (running) {
            System.out.println("hello");
        }
        System.out.println("m end!");
    }
    public static void main(String[] args) throws IOException {
        new Thread(T01_HelloVolatile::m, "t1").start();
        SleepHelper.sleepSeconds(1);
        running = false;
        System.in.read();
    }
复制代码

1.2.禁止指令重排序

对于非常经典的单例模式中双重检查,要不要加volatile呢?答案是肯定的,要加。
在说明为什么要加之前我们先来看下new一个对象的时候到底发生了什么。
从字节码可以看到创建一个对象实例我们需要三个步骤,例如Object a = new Object(3)
正常来说的话我们不会发生问题,但在超高并发的情况下,
假如线程1执行到了第二步,线程2此时执行到了第一步,这时候就会出错。
所以在单例模式中,我们在方法上使用 了synchronized,但是这么做就会导致每次方法
调用都需要获取与释放锁,开销很大。所以正确的双重检查锁定模式我们需要使用volatile。
这里主要由于volatile的第二个特性:禁止指令重排序
由于volatile禁止对象创建时指令之间重排序,所以其他线程不会访问到一个未初始化的
对象,从而保证安全性。
复制代码
//volatile虽然达到了按需初始化的目的,但也带来了线性不安全问题
//通过synchronized解决,但也带来了效率低下
public static M getInstance(){
    if(INSTANCE == null){
       //双重检查
       synchronized(M.class){
       try(){
       }catch{}
       //如果只有volatile,别的线程也会new,加上synchronized只有等当前线程已经初始化成功别的线程才会访问,此时已经new过了就不会在new了
       INSTANCE = new M();
       }
    }
}
复制代码

2.synchronized

简介:用于给某个对象上锁,保证在同一时刻,只有一个线程可以执行某个方法或某个代码块,

是Java中解决并发问题的一种最常用最简单的方法 ,他可以确保线程互斥的访问同步代码

2.1synchronized的目的

举栗子:

复制代码
public class Test {
        volatile int count = 0;//解决方法:synchronized void m()。保证每个线程结束后另一个线程再拿到count的值,此时可以加到10w
        void m() {
            for (int i = 0; i < 10000; i++) count++;
        }
        public static void main(String[] args) {
            Test t = new Test();
            List<Thread> threads = new ArrayList<Thread>();
            //10个线程,每个线程调用m方法
            for (int i = 0; i < 10; i++) {
                threads.add(new Thread(t::m, "thread-" + i));
            }
            threads.forEach((o) -> o.start());
            threads.forEach((o) -> {
                try {
                    o.join();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            });
            System.out.println(t.count);
        }
}
==========
结果:45854
复制代码

在上面的例子中,m方法将count加到10000,我们创建了十个线程,每个线程调用一次m方法,正常来说我们的结果应该是10w,

那为什么结果会是45854呢?我们需要先了解count++发生了什么


可以看到count++需要三个步骤,如果线程1的count加到1,线程2拿到的是1,线程3拿到的也是1,线程2返回2,线程3返回2,

实际结果应该为3,所以丢失了1次更新。

导致最后的结果少加了很多次。解决办法就是在m方法前面加锁,保证一个线程结束后第二个线程才可以执行。

也可以采用AtomicInteger 类,因为AtomicInteger类是原子性操作的

复制代码
//结果是10w
AtomicInteger count = new AtomicInteger(0);
       //不需要在加synchronized
        /* synchronized */void m() {
                for (int i = 0; i < 10000; i++)
                        //if count1.get() < 1000
                        count.incrementAndGet(); //count1++
        }
复制代码

注意点:

1.程序当中如果出现异常,默认情况锁被释放,需要catch,不然容易被别的程序乱入,导致数据不一致

2.锁定对象的时候不能用String常量,Integer,Long,

3.锁的是对象,不是代码

4.无论synchronized关键字加在方法上还是对象上,如果它作用的对象是非静态的,则它取得的锁是对象;如果synchronized作用的对象是一个静态方法或一个类,则它取得的锁是对类,该类所有的对象同一把锁。

5.每个对象只有一个锁(lock)与之相关联,谁拿到这个锁谁就可以运行它所控制的那段代码

6.同步代码块中的语句越少越好,因为上锁后性能会变低

7.锁定某对象o,如果o的属性发生改变,不影响锁的使用,但是如果o变成另外一个对象,则锁定的对象发生改变应该避免将锁定对象的引用变成另外的对象

2.2synchronized的优化

我们已经知道使用synchronized虽然可以保证线程的安全行,但同时也变得效率低下,每次只能通过一个线程,既然每次只能通过一个,这种形式不能改变的话,那么我们能不能让每次通过的速度变快一点了。打个比方,只有一个厕所,我们一群人在排队等着,由于使用了synchronized,导致我们每次只能一个人去上厕所,但如果想我们尽快都上完厕所,就只能增加上厕所的速度了。笑


相关教程