Java并发编程(二)线程安全问题

导图
线程安全

一、基础概念

1、在并发编程中为什么会有线程安全的问题?

首先由于Java的内存模型,线程并不是直接对共享变量进行操纵,而是拷贝了一个副本,到当前线程本身的栈内存当中;对副本修改完成后再刷新到主存当中;
出自Java并发编程之美

以计数count为例:
线程A拿到共享变量count(初始值为1)后加1;线程B也要拿到这个共享变量并进行累加操作;那么就有可能出现,线程A将count拷贝到自己的工作内存空间后进行处理并加1,但此时还未将count刷新到主存,线程B从主存拿到的count依然为1,就会导致计数不准确,造成一定的脏数据。

2、线程安全问题的解决思路

线程安全问题只在多线程环境下才会出现;并且如果所有的线程都对数据仅有读操作,没有写操作的话,也不会有线程安全的问题;因此保证高并发场景下的线程安全,可以从以下四个维度考量;
数据单线程内可见
单线程总是线程安全的。通过限制单线程仅在单线程内部可见,可以避免被其他线程锁篡改;比如ThreadLocal局部变量,它存储在独立虚拟机栈帧的局部变量表中,与其他线程并无瓜葛;

只读对象
如果对象的属性是不可写入修改的,那么这个对象也是线程安全的;比如用final修饰的关键字。

线程安全类
在Java当中提供了很多线程安全的操作类;在类的内部实现了非常明确的安全机制;比如StringBuffer 内部的操作都是synchronized;或者Atomic相关的原子操作类等。

同步与锁机制
使用线程同步或者加锁机制;比如JUC并发包中的内容。

二、死锁

1、什么是死锁

死锁是指俩两个或者两个以上的线程在执行过程中,因争夺资源而造成的互相等待的情况;若无外力作用的情况,这些线程会一直相互等待无法继续执行下去。

2、死锁产生的条件

死锁的产生必须具备以下4个条件:
互斥条件
指的是线程对已经获得的资源具有排它属性;即该资源同时只由一个线程占用;如果此时其他线程想要获得该资源,就只能等待。

请求并保持条件
指一个线程已经获得至少一个资源后,又提出了新的资源请求;而新资源已被其他线程占有,所以当前线程会被阻塞;但阻塞的同时不会释放已经获得的资源。

不可剥夺条件
指线程获取到的资源在自己使用完成之前不能被其他线程抢占,只有在自己使用完毕后才由自己释放该资源。

循环等待条件
指在发生死锁时,必然存在一个线程——资源的环形链,例如T1等待T2占用的资源…Tn等待T0占用的资源。

一个死锁的示例:

public class ThreadTest {
    private Object lockA = new Object();
    private Object lockB = new Object();

    public void run() {
        new Thread(new TestRunnable()).start();
        new Thread(new Test2Runnable()).start();
    }

    private class TestRunnable implements Runnable {

        @Override
        public void run() {
            synchronized (lockA) {
                System.out.println("Thread1 get resource A");
                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("Thread1 waiting resource B");
                synchronized (lockB) {
                    System.out.println("Thread1 get resource B");
                }

            }
        }
    }

    private class Test2Runnable implements Runnable {

        @Override
        public void run() {
            synchronized (lockB) {
                System.out.println("Thread2 get resource B");
                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("Thread2 waiting resource A");
                synchronized (lockA) {
                    System.out.println("Thread2 get resource A");
                }
            }

        }
    }
}

这个例子当中,线程1拿到lockA之后还需要拿到lockB,而此时lockB被线程2所占用,并且线程B除了持有lockB还等待线程1释放lockA;因此形成死锁,这两个线程会永远等待下去。

3、如何避免死锁?

如果要避免死锁的发生,只需要将死锁的4个条件之一打破即可;但目前只有请求和保持以及循环等待这两个条件是可以被打破的。
造成死锁的原因和申请资源的顺序是有很大关系的;使用资源申请的有序性原则就可以避免死锁,例如上面死锁的例子,将线程1,2申请资源(锁)的顺序改为一样,每当一个线程获取到资源后,另外一个线程阻塞不会去获取另外的资源,通过这种方式可以避免死锁的发生:

public class ThreadTest {
    private Object lockA = new Object();
    private Object lockB = new Object();

    public void run() {
        new Thread(new TestRunnable()).start();
        new Thread(new Test2Runnable()).start();
    }

    private class TestRunnable implements Runnable {

        @Override
        public void run() {
            synchronized (lockA) {
                System.out.println("Thread1 get resource A");
                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("Thread1 waiting resource B");
                synchronized (lockB) {
                    System.out.println("Thread1 get resource B");
                }

            }
        }
    }

    private class Test2Runnable implements Runnable {

        @Override
        public void run() {
            synchronized (lockA) {
                System.out.println("Thread2 get resource A");
                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("Thread2 waiting resource B");
                synchronized (lockB) {
                    System.out.println("Thread2 get resource B");
                }
            }

        }
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值