死锁ThreadLocal

news/2024/5/20 11:19:44 标签: java, 开发语言, 死锁, ThreadLocal

一、死锁是什么?

死锁是多线程环境中遇到的最严重的问题之一。

一个线程如果重复获取锁,获取了两次以上,如果锁是可重入锁,可以利用多次解锁方式释放锁资源,反之就会出现多个线程同时被阻塞,它们中的一个或多个线程都在等待锁资源的释放。由于线程被无限期阻塞,因此这种现象被称为“死锁”。

举个栗子:

鸡哥出门买鸡蛋,但是他把钥匙忘记带在身上,当他买完鸡蛋想要回家时,发现备用钥匙也被老妈拿走了,两个钥匙都获取不到,进不了门,就造成了死锁现象。

代码示例:

java">
import java.util.concurrent.TimeUnit;

/**
 * 模拟死锁
 */
public class Exe_02 {
    public static void main(String[] args) {
        //创建两个锁
        Object locker1=new Object();
        Object locker2=new Object();
        //创建两个线程
        Thread thread1=new Thread(() ->{
            System.out.println("t1申请locker1");
            synchronized(locker1){
                System.out.println(Thread.currentThread().getName()+"获取到了locker1");
                try {
                    TimeUnit.SECONDS.sleep(2);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized(locker2){
                    System.out.println(Thread.currentThread().getName()+"获取到了locker2");
                }
            }
        },"t1");
        Thread thread2=new Thread(() ->{
            System.out.println("t2申请locker1");
            synchronized(locker2){
                System.out.println(Thread.currentThread().getName()+"获取到了locker2");
                try {
                    TimeUnit.SECONDS.sleep(2);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized(locker1){
                    System.out.println(Thread.currentThread().getName()+"获取到了locker1");
                }
            }
        },"t2");
        //启动线程
        thread1.start();
        thread2.start();
    }
}

 

 进一步理解死锁,那么谈论最多就是”哲学家就餐问题“。

  • 有个桌子, 围着一圈哲学家, 桌子中间放着一盘意大利面. 每个哲学家两两之间, 放着一根筷子.

 

  • 每个哲学家 只做两件事: 思考人生 或者 吃面条. 思考人生的时候就会放下筷子. 吃面条就会拿起左右两边的筷子(先拿起左边, 再拿起右边).

 

  • 如果哲家发现筷子拿不起来了(被别人占用了), 就会阻塞等待. 
  •  [关键点在这]假设同一时刻,五个哲家 同时拿起左手边的筷子, 然后再尝试拿右手的筷子, 就会
    发现右手的筷子都被占用了. 由于哲家 们互不相让, 这个时候就形成了死锁.

 

死锁是一种严重的BUG!!,导致一个程序的线程”卡死“,无法正常工作。 

二、如何避免死锁

2.1、死锁产生的原因

  1. 互斥访问:线程1拿到了锁A,其它线程就不能得到锁A。
  2. 不可抢占:获取到锁的线程,除非自己释放锁,别的线程不能把锁从别的线程手中抢过来占有。
  3. 保持和请求:线程1已经获取了锁A,还要在这个基础上再去获取锁B。
  4.  循环等待:线程1等待线程2释放锁,线程2等待线程3释放锁,线程3等待线程1释放锁,形成了一个等待回路。

当上述四个条件都成立(必要条件)的时候,便会形成死锁。当然,上述四个条件打破一个使其不成立,那么死锁就会消失。 

2.2、如何解决死锁问题 

  1. 互斥访问:锁的基本特性,不能打破。
  2. 不可抢占:锁的基本特性,不能打破。
  3. 保持和请求:从代码实现或是设计的角度来讲,可以改变保持和请求的顺序,也就是获取锁的顺序。
  4.  循环等待:最有可能也是最常见的解决死锁的策略——打破循环等待

2.2.1、破除循环等待 

 

策略 :

  1. 每一个筷子都编一个号。
  2. 让每个哲学家都先拿编号最小的那根筷子,然后再去拿编号最大的那根筷子。
  3. 吃一口然后放下筷子,让别的哲学家再去吃获取筷子。

过程: 

  1. 从1号到四号哲学家都拿到了编号最小的那根筷子。
  2. 5号哲学家再拿一号筷子的时候发现已经被1号哲学家拿走了,那么他就要原地等待
  3. 随着4号哲学家吃完后把两双筷子都放下之后,随即3~1号哲学家都拿到筷子吃到了‘’ 
  4. 1号哲学家在吃完面之后,把两只筷子都放下之后,5号哲学家就可以拿到1号筷子,再拿到5号筷子,最终吃到‘’。

三、ThreadLocal 

synchronized是能够通过加锁,可以保证多线程环境下的共享变量的可见性(让每一个线程访问的变量都是一致的),原子性。 

ThreadLocal正好相反,对于同一个变量让每一个线程都保持自己线程处理任务过程中不同的值。

场景:

  1. 统计每个班的学生人数。
  2. 根据每个班的学生人数去定制校服。

实现方式: 

每个班创建一个线程,在线程中去统计每个班的人数,再调用定做校服的方法。

对于count来说,每一个线程都维护着这一个变量,对于线程来说是局部变量。

这时就需要使用ThreadLocal来区分每一个线程的count值(可以理解为一个Map集类)

ThreadLocal里面维护了一个ConcurrentHashMap,线程安全的,set()方法传进value值,线程对象作为Key值。

java">
/**
 * ThreadLocal里面维护了一个ConcurrentHashMap,线程安全的,set()方法传进value值,线程对象作为key值
 */
public class Exe_03 {
    //初始化ThreadLocal对象
    private static ThreadLocal<Integer> threadLocal=new ThreadLocal<>();
    public static void main(String[] args) {
        //多个线程分别去统计人数
        Thread t1=new Thread(() ->{
            //统计人数
            int count=35;
            threadLocal.set(count);
            Integer value=threadLocal.get();
            System.out.println(Thread.currentThread().getName()+"人数:"+value);
            //定制校服
            print();
        },"一班");
        Thread t2=new Thread(() ->{
            //统计人数
            int count=56;
            threadLocal.set(count);
            Integer value=threadLocal.get();
            System.out.println(Thread.currentThread().getName()+"人数:"+value);
            //定制校服
            print();
        },"二班");
        Thread t3=new Thread(() ->{
            //统计人数
            int count=48;
            threadLocal.set(count);
            Integer value=threadLocal.get();
            System.out.println(Thread.currentThread().getName()+"人数:"+value);
            //定制校服
            print();
        },"三班");
        //启动线程
        t1.start();
        t2.start();
        t3.start();
    }

    private static void print() {
        Integer value=threadLocal.get();
        System.out.println(Thread.currentThread().getName()+": 需要定制 "+value+"校服");
    }
}

 

 


http://www.niftyadmin.cn/n/1552476.html

相关文章

数据库管理常用sql语句

一、creating a database1、以系统管理员用户登录。$ sqlplus / as sysdba 2、启动数据库导nomount状态。SQL> startup nomount; 3、执行创建数据库语句。SQL> create database invrep controlfile reuse maxlogfiles 16 maxlogmembers 4 maxdatafiles 1024 maxinstances…

解决热块冲突的主要思路

参考《oracle性能优化实务》 打散数据分布使用较小的块大小使用HASH簇表使用HASH分区表反转键索引较大的SEQUENCE CACHE&#xff08;RAC下&#xff09; 优化表存储参数PCTFREEINITRANSFREELISTS/FREELIST GROUPS 提高DB CACHE命中率 优化IO 使用物化视图 KEEP热表 优化应用&…

使用SQL生成SQL

set echo off heading off feedback off spool sql.txt select grant connect,resource to ||username||; from dba_users; spool off sql.txt

手工配置Oracle 10G Enterprise Manager

最近在学习安装数据库的时候&#xff0c;用了多种方法创建&#xff0c;如dbca创建&#xff0c;手工创建&#xff0c;用模版创建等等&#xff0c;当用dbca创建数据库的时候&#xff0c;没有选择“使用Enterprise Manager配置数据库”&#xff0c;造成后来想用em不能使用&#xf…

查找热块脚本

参考《oracle性能优化实务》 &#xff08;最好还是看AWR报告的Segments by Buffer Busy Waits&#xff09; select event,p1,p2,p3 from v$session_wait where event like buffer busy wait%; select owner,segment_name from dba_extents where file_id&p1 and block_id&l…

kill -0

kill -0 pid 用来检测进程是否存在&#xff0c;当进程不存在时&#xff0c;kill -0会报错 kill -0 $pid > /dev/null 2>&1

LOG FILE SYNC

参考《oracle性能优化实务》 等待日志缓冲写入日志文件 该等待较多说明REDO LOG整个机制中存在问题 如何排查和解决如果同时Redo log space requests较大&#xff0c;需要加大LOG_BUFFER如果log file parallel write指标较差&#xff0c;需要优化REDO LOG的IO应用中大量小事务也…

vcs双机基础之一

1.VCS双机的基本结构 VCS 使用的心跳协议叫做LLT(低延迟传输协议&#xff0c;Low Latency Transport)&#xff0c;LLT运行在IP之下。这个协议比IP更快且更可靠。这能确保集群成员保持同步和在集群中能马上联系对方&#xff1b; 相关的配置项为&#xff1a; /etc/llttab文件&a…