线程基础知识(二)

发布时间:2023年12月28日

线程基本知识

同步

线程的同步是为了防止对个线程访问同一个对象对数据造成损坏。

1. 锁的概述

线程安全问题的前提是多个线程并发访问同一个共享变量和资源,那么就能得到让一个共享变量在一个时间只能被一个线程访问,结束后才能被其他的线程访问。
Lock锁就是利用这种机制来实现的保护线程安全的线同步机制。
锁分为两种

  • 内部锁:通过synchronized关键字来实现。
  • 显式锁: 是通过java.councurrent.locks.Lock接口的实现类(如java.concurrent.locks.ReentrantLock类)实现的。

2.锁作用

保护共享变量资源的数据安全实现线程安全,作用是保障数据的原子性,可见性,有序性。

  • 原子性:锁式通过互斥性保障原子性的,互斥是指锁一次只能被一个线程拥有,synchronized就是通过互斥保障了临界区的代码一次只能被一个线程持有。因此线程执行一个临界区没有其他的线程访问共享变量数据,使得临界区代码所执行的操作自然的具有了不可分割线,就具备了原子性。
  • 可见性: 可见性保障是通过写线程 冲刷处理器缓存读线程 刷新处理器缓存这两个动作实现的.
    • 再java中锁的获得隐含了刷新处理器缓存这个动作,这使得读线程再获得锁之后可以将写线程所对共享变量的更新操作同步到该线程的高速缓存中(可更新同步,读线程刷新处理器
    • 锁的释放:隐含冲刷处理器缓存动作,这使得写线程对共享变量所做的更新能够被推送到该线程执行的高速缓存中,从而可以对读线程同步。
      因此锁能够保障可见性,获得锁同步更新缓存,释放推送缓存。
      锁的互斥性及其对可见性的保障合在一起。可保证临界区内的代码能够读取到共享数据的最新值。
      因此,线程在临界区中所读取到共享数据的相对新值(锁对保障可见性的结果)同时也是最新值。
  • 有序性:这是锁对原子性和可见性保障的结果
    • 由于锁对可见性的保障,写线程在临界区中对上述任何一个共享变量锁做的更新都对读线程可见。
    • 由于锁具有互斥性,临界区内的操作具有原子性,因此写线程对上述共享变量的更新会同时对读线程可见,即在读线程看来这些变量就像是同一时刻被更新的。
b = a + 1;
c = 2;
flag = true;

由于锁能够保障有序性,因此对于上面例子,可几种情况(读线程无法区分更新顺序):
如果一个读线程在临界区中读取到c =2,flag =ture, b = a+1.
如果一个读线程在临界区中读取到flag = true,c=2, b = a+1
因此:尽管锁能够保障有序性,但是这并不意味着临界区内的内存操作不能够被重排序。
临界区内的任意两个操作依然可以在临界区之内被重排序(即不会重排序到临界区之外)。
由于临界区内的操作具有原子性,写线程在临界区内对各个共享数据的更新同时对读线程可见,因此这种重排序并不会对其他线程产生影响。

  1. 原子性,可见性和有序性是有条件的
    需要注意的是同时需要满足以下两点:
  2. 这些线程在访问同一组共享变量时候,必须使用同一个锁。
  3. 这些线程中的任意一个线程,即使仅仅是读取这组共享变量,也必须要持有锁。

3.锁的相关概念

   1. 锁的可重入性:一个线程在持有一个锁的时候,能否在次或者多吃申请该锁,如果一个线程在持有一个锁的时候,还能继续申请该锁,那么我们就称呼为这种锁为可重入的锁。否则锁就是非可重入的锁
//伪代码
void  A(){
    cquireLock(Lock); // 申请锁lock
	 //省略....
	 methodB();   // 再次调用methodB ,申请锁lock
	 releaseLock(lock); // 释放锁
}
void B(){
acquireLock(lock); // 申请锁Lock
	//...
	releaseLock(lock); // 释放锁Lock
	}

方法A使用了锁lock, 该锁引导的临界区代码又调用了另外 一个方法B, 而方法B也使用lock。

那么这就产生了一个问题:方法A 的执行线程持有锁lock 的时候调用了 方法B, 而方法B执行的时候又去申请锁lock , 而lock 此时正被当前线程持有(未被释放)。那么,此时方法B究竟能否获得(申请成功)lock 呢?可重入性就描述了这样一个问题。

  • 可重入实现锁原理
    可重入锁可以理解为一个对象,它拥有一个计数器,初始值为0,表示没有被任何线程持有;每一个线程持有锁的时候该计数器就会加1,释放锁就会减一。一个可重入锁初次获得该锁的开销相对大,这是因为该锁的线程必须和其他线程竞争来获取锁,可重入锁的持有线程后续获得锁的开销就会小,因为java虚拟机只需要咋响应的计数器上加1就行,即可获得锁。

    • 锁的争度和调用
      因为锁也可以被看做是多线程访问共享数据的一种排他性资源,java中资源调度策略可以分为公平策略和非公平性策略就引申出公平锁和非公平锁。内部锁是非公平锁,显示锁是及支持公平也支持非公平。
  • 3、锁的粒度
    一个锁可以保护一个或者多个共享数据,一个锁实例保护的的共享数据的数量大小就是锁粒度

  • 4.锁的开销和及其可能得问题

    • 锁的不正确使用导致死锁,锁泄露
    • 锁会使用会导致上下文切换(上下文切换:使用多线程是为了充分利用cpu,并发就是一个cpu来应对多个线程。为了让用户感觉多个线程是同时在执行,CPU资源的分配是采用了时间片轮转,也就是每个线程分配一个时间片,线程在时间片里面占用cpu执行任务,当线程使用完时间片后,就会处于就绪状态,并让出Cpu让其他线程占用,这就是上下文切换。)
  • 4.锁的适用场景
    如果其中有线程操作可以使用锁

    • check-then-act操作
    • read-modify-writer操作
    • 多个线程对多个共享数据进行更新

参考链接锁相关介绍

文章来源:https://blog.csdn.net/weixin_45003206/article/details/135255345
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。