zoukankan      html  css  js  c++  java
  • 并发编程(五)线程同步

    一、引言

      多线程的开发过程中,也许会遇到这么一个场景:多个线程同时操作一个变量时,线程之间会有时间差,而在时间差内,该共享数据的值也许已经发生了改变,那么我们要怎么才能保证在多线程的环境下,每个线程读取到的数据值都是最新的呢?线程同步机制了解一下~

    二、线程同步的“锁”

      前面了解了多线程场景下,需要保证每个线程读取数据的值都要是最新的,那么我们就需要一个“锁”,来保证当前线程操作此数据时,别的线程无法使用,相当于当前线程“锁”住了此时数据的读写权限,下面我们来学习下如何实现这一场景。

      首先我们先了解几大神器,以及其大致的作用:

    • synchronized关键字,可以修饰方法,也可以修饰代码块
    • volatile特殊域变量
    • ReentrantLock重入锁
    • Atomic :原子变量

    三、线程同步详解

    synchronized同步方法

      定义:有synchronized关键字修饰的方法。

    • 每个java对象都有一个内置锁,使用synchronized关键字修饰方法时, 内置锁就会“锁住”整个方法。
    • 每个线程在调用该方法前,都需要获得内置锁,否则就处于阻塞状态。
    • PS:若用synchronized关键字修饰静态方法,此时如果调用该静态方法,将锁住整个类
    /**
     * synchronized同步方法
     */
    public class MySynchronizedMethod {
    
        private static int count = 0;
    
        public static void main(final String[] arguments) throws InterruptedException {
            //创建TestThread对象
            TestThread threadRunable = new TestThread();
            //增加10个Thread线程对象
            Thread thread1 = new Thread(threadRunable);
            Thread thread2 = new Thread(threadRunable);
            Thread thread3 = new Thread(threadRunable);
            Thread thread4 = new Thread(threadRunable);
            Thread thread5 = new Thread(threadRunable);
            Thread thread6 = new Thread(threadRunable);
            Thread thread7 = new Thread(threadRunable);
            Thread thread8 = new Thread(threadRunable);
            Thread thread9 = new Thread(threadRunable);
            Thread thread10 = new Thread(threadRunable);
            //10个线程同时启动
            thread1.start();
            thread2.start();
            thread3.start();
            thread4.start();
            thread5.start();
            thread6.start();
            thread7.start();
            thread8.start();
            thread9.start();
            thread10.start();
        }
    
        /**
         * 同步方法
         */
        public static synchronized void countNum() {
            for (int i = 0; i < 100000; i++) {
                count++;
                System.out.println(count);
            }
        }
    
        /**
         * 线程实体(实现Runnable接口)
         */
        static class TestThread implements Runnable {
    
            public void run() {
                //调用同步方法
                countNum();
            }
        }
    
    }

    synchronized同步代码块

       与同步方法类似,但是同步是一种高开销的操作,通常没必要同步整个方法,所以同步块相对同步方法来说更优一些。

    代码与上面类似,只贴出不一样的地方:

        /**
         * 同步方法
         */
        public static void countNum() {
            for (int i = 0; i < 100000; i++) {
                //PS:由于这里是个静态方法,要同步语句块时,就需要到整个类的内置锁(synchronized的入参代表要获取内置锁的实体)
                synchronized (MySynchronizedMethod.class) {
                    count++;
                }
                System.out.println(count);
            }
        }

    特殊域变量(volatile)

    • volatile关键字为域变量的访问提供了一种免锁机制
    • 使用volatile修饰域相当于告诉虚拟机该域可能会被其他线程更新
    • 因此每次使用该域就要重新计算,而不是使用寄存器中的值
    • volatile不会提供任何原子操作,它也不能用来修饰final类型的变量
    //使用volatile关键字来修饰变量
    private
    static volatile int count = 0;

    PS:volatile只有在原子操作(如:n=m+1)的时候才有效,若操作的值与自身有关时(如:n++;n=n+1;)就不起作用了,不熟悉的话不建议用这个关键字实现同步。

    ReenreantLock重入锁

    ReenreantLock类的常用方法有:

    • ReentrantLock() : 创建一个ReentrantLock实例
    • lock() : 获得锁
    • unlock() : 释放锁
    //声明一个重入锁
    private static Lock lock = new ReentrantLock();
    
    
        /**
         * 同步方法
         */
        public static void countNum() {
            for (int i = 0; i < 100000; i++) {
                lock.lock();//开始加锁
                count++;
                System.out.println(count);
                lock.unlock();//解锁
            }
        }

    Atomic原子变量

      从上面的例子可以看出,类似count++;的方法,实际上并不是一个原子操作,而是经过了读取、修改、写入三个步骤。

      实现原理:CAS原理(比较并交换),每个线程操作前会用旧的预期值内存值进行比较,相同的时候把内存值修改为新值,当一个线程在执行此操作时,其他线程都失败,并可以再次尝试,或者什么都不做,此时线程并不会被挂起。

      存在问题:ABA问题   详解参考这里~

    /**
     * 原子变量Atomic实现Synchronized锁的同步功能
     */
    public class MyAtomic {
    
        // 多个线程共享的变量(使用AtomicInteger来替代Synchronized锁)
        private static AtomicInteger count = new AtomicInteger(0);
    
        //获取共享变量的值
        public static Integer getCount() {
            return count.get();
        }
    
        //自增方法
        public static void increase() {
            count.incrementAndGet();
        }
    
        public static void main(final String[] arguments) throws InterruptedException {
            //创建50个线程的线程池
            ExecutorService executor = Executors.newFixedThreadPool(50);
            //放入50个线程对象并执行
            for (int i = 0; i < 50; i++) {
                executor.submit(new TestThread());
            }
        }
    
        /**
         * 同步方法
         */
        public static void countNum() {
            for (int i = 0; i < 10000; i++) {
                increase();
                System.out.println(getCount());
            }
        }
    
        /**
         * 线程实体(实现Runnable接口)
         */
        static class TestThread implements Runnable {
    
            public void run() {
                //调用同步方法
                countNum();
            }
        }
    
    }

    操作结果:

  • 相关阅读:
    Linux 常用工具openssh之ssh-copy-id
    Linux 常用工具openssh之ssh-agent
    SpringMVC视图机制详解[附带源码分析]
    Spring中Ordered接口简介
    SpringMVC拦截器详解[附带源码分析]
    SpringMVC类型转换、数据绑定详解[附带源码分析]
    详解SpringMVC请求的时候是如何找到正确的Controller[附带源码分析]
    详解SpringMVC中Controller的方法中参数的工作原理[附带源码分析]
    SpringMVC关于json、xml自动转换的原理研究[附带源码分析]
    Servlet容器Tomcat中web.xml中url-pattern的配置详解[附带源码分析]
  • 原文地址:https://www.cnblogs.com/riches/p/11990386.html
Copyright © 2011-2022 走看看