并发编程(五)线程同步

一、引言

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

二、线程同步的“锁”

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

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

  • 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();
        }
    }

}

操作结果:

posted @ 2019-12-10 17:07  有梦想的肥宅  阅读(246)  评论(0编辑  收藏  举报