Loading

线程同步(锁的概念)

首先先提供线程生命周期图,方便理解

 

线程同步

线程同步机制简介

  1. 线程同步机制是一套用于协调线程之间的数据访问的机制.该机制可以保障线程安全.
  2. Java 平台提供的线程同步机制包括: , volatile 关键字, final 关键字,static 关键字,以及相关的 API,Object.wait()/Object.notify()

 

 锁

锁概述 

  1. 线程安全问题的产生前提是多个线程并发访问共享数据.将多个线程对共享数据的并发访问转换为串行访问,即一个共享数据一次只能被一个线程访问.锁就是复用这种思路来保障线程安全的
  2. (Lock)可以理解为对共享数据进行保护的一个许可证. 对于同一个许可证保护的共享数据来说,任何线程想要访问这些共享数据必须先持有该许可证. 一个线程只有在持有许可证的情况下才能对这些共享数据进行访问; 并且一个许可证一次只能被一个线程持有; 持有许可证的线程在结束对共享数据的访问后必须释放其持有的许可证
  3. 一线程在访问共享数据前必须先获得锁; 获得锁的线程称为锁的持有线程; 一个锁一次只能被一个线程持有. 锁的持有线程在获得锁之后 和释放锁之前这段时间所执行的代码称为临界区(Critical Section),锁具有排他性(Exclusive), 即一个锁一次只能被一个线程持有.这种锁称为排它锁或互斥锁(Mutex)


锁的分类

JVM 把锁分为内部锁和显示锁两种. 内部锁通过 synchronized关键字实现; 显示锁通过 java.concurrent.locks.Lock 接口的实现类实现的

锁的作用

  1. 锁可以实现对共享数据的安全访问. 保障线程的原子性,可见性与有序性
  2. 锁是通过互斥保障原子性. 一个锁只能被一个线程持有, 这就保证临界区的代码一次只能被一个线程执行.使得临界区代码所执行的操作自然而然的具有不可分割的特性,即具备了原子性.
  3. 可见性的保障是通过写线程冲刷处理器的缓存和读线程刷新处理器缓存这两个 动作实现的. java 平台中,锁的获得隐含着刷新处理器缓存的动作, 锁的释放隐含着冲刷处理器缓存的动作.
  4. 锁能够保障有序性.写线程在临界区所执行的在读线程所执行的临界区看来像是完全按照源码顺序执行的

注意:使用锁保障线程的安全性,必须满足以下条件:

  1. 这些线程在访问共享数据时必须使用同一个锁
  2. 即使是读取共享数据的线程也需要使用同步锁

锁的相关概念

可重入性

可重入性(Reentrancy)描述这样一个问题: 一个线程持有该锁的时候能再次(多次)申请该锁 .如果一个线程持有一个锁的时候还能够继续成功申请该锁,称该锁是可重入的, 否则就称该锁为不可重入的


锁的争用与调度

Java 平台中内部锁属于非公平锁, 显示 Lock 锁既支持公平锁又支持非公平锁 

 

锁的粒度

一个锁可以保护的共享数据的数量大小称为锁的粒度.锁保护共享数据量大,称该锁的粒度粗, 否则就称该锁的粒度细.锁的粒度过粗会导致线程在申请锁时会进行不必要的等待.锁的粒度过细会增加锁调度的开销.

 内部锁:synchronized 关键字

Java 中的每个对象都有一个与之关联的内部锁(Intrinsic lock). 这种锁也称为监视器(Monitor), 这种内部锁是一种排他锁,可以保障原子性,可见性与有序性.内部锁是通过 synchronized 关键字实现的.synchronized 关键字修
饰代码块,修饰该方法.

语法

修饰代码块的语法:
synchronized( 对象锁 ) {
    同步代码块,可以在同步代码块中访问共享数据
}
修饰实例方法就称为同步实例方法
修饰静态方法称称为同步静态方法 
把整个方法体作为同步代码块,默认的锁对象是 this 对象
经常使用this或者常量来当做锁对象

 

具体使用推荐动力节点的多线程教程

 轻量级同步机制:volative 关键字

volatile 的作用

volatile 关键的作用使变量在多个线程之间可见【保证了线程的可见性,无法保证原子性】. (volatile 的作用可以强制线程从公共内存中读取变量的值,而不是从工作内
存中读取

语法

volatile public static int count;//在共享变量前面加一个volatile关键字即可

具体使用推荐动力节点的多线程教程

CAS

CAS概念

  1. CAS(Compare And Swap)是由硬件实现的.
  2. CAS 可以将 read- modify - write 这类的操作转换为原子操作.
    i++自增操作包括三个子操作:从主内存读取 i 变量值——》对 i 的值加 1——》再把加 1 之后 的值保存到主内存

CAS 原理

在把数据更新到主内存时,再次读取主内存变量的值,如果现在变量的值与期望的值(操作起始时读取的值)一样就更新.如图

 

 

 

使用 CAS 实现线程安全的计数器

package com.edu.volatilekw;

class CASCounter {
    //使用 volatile 修饰 value 值,使线程可见
    volatile private long value;

    public long getValue() {
        return value;
    }

    //定义 comare and swap 方法
    private boolean compareAndSwap(long expectedValue, long newValue) {
        //如果当前 value 的值与期望的 expectedVAlue 值一样,就把当前的 Value 字段替换为newValue 值
        synchronized (this) {
            if (value == expectedValue) {
                value = newValue;
                return true;
            } else {
                return false;
            }
        }
    }

    //定义自增的方法
    public long incrementAndGet() {
        long oldvalue;
        long newValue;
        do {
            oldvalue = value;
            newValue = oldvalue + 1;
        } while (!compareAndSwap(oldvalue, newValue));
        return newValue;
    }
}

一些常见的原子变量类

原理:

原子变量类基于CAS实现的, 当对共享变量进行read-modify-write更新操作时,通过原子变量类可以保障操作的原子性与可见性.对变量的 read-modify-write 更新操作是指当前操作不是一个简单的赋值,而是变量的新值依赖变量的旧值,如自增操作i++. 由于volatile只能保证
可见性,无法保障原子性, 原子变量类内部就是借助一个 Volatile 变量,并且保障了该变量的 read-modify-write 操作的原子性, 有时把原子变量类看作增强的 volatile 变量.

原子变量类分类

分组
原子变量类
基础数据型
AtomicInteger, AtomicLong, AtomicBoolean
数组型
 
AtomicIntegerArray,
AtomicLongArray,AtomicReferenceArray
字段更新器
 

AtomicIntegerFieldUpdater,AtomicLongFieldUpdater,
AtomicReferenceFieldUpdater
引用型
 
AtomicReference, AtomicStampedReference,
AtomicMarkableReference

 

具体用法

下载jdk1.8.chm文件(提取码:5vby),进行查看




posted @ 2020-09-19 11:41  揸火箭  阅读(353)  评论(0编辑  收藏  举报

Loading