并发4-线程安全

1.线程安全问题:

    在多线程中,有可能出现多个线程同时使用同一个资源的情况,这个资源可以是变量,数据表,txt文件等。这个资源称作"临界资源"

    举个例子:取钱这个线程分为两个步骤:

    1.读取金额

    2.取款

    3.更新金额


    有个典型的线程安全的例子,倘若A,B两人使用同一个账户(1000元)取款,A执行

    1.读取金额    2.取款,取出300元,并未更新金额。

    此时,

    B读取金额,显示为1000(应该为700),B取出1000元。

    最后,A执行3.更新金额,B执行3.更新金额。

    如前面所说,多个线程访问临界变量,就会产生线程安全问题。不过,当多个线程执行同一个方法时,方法内部的变量不是临界资源,因为每个线程都有自己独立的内存区域(PC,方法栈,线程栈)   



2.解决线程安全问题:

    基本所有的并发方案,都采用“序列化访问资源”,也就是在同一时间,只有一个线程能访问临界资源,也称作同步互斥访问

    方案1:synchronized

    在Java中,每个对象都有一个锁标记,称为monitor(监视器),多个线程访问这个对象的临界资源时,只有获取了该对象的锁才能访问

    在Java中,可以使用synchronized关键字来标记一个方法或者代码块,当某个线程调用该对象的synchronized方法或者访问synchronized代码块时,这个线程便获得了该对象的锁,其他线程暂时无法访问这个方法,只有等待这个方法执行完毕或者代码块执行完毕,这个线程才会释放该对象的锁,其他线程才能执行这个方法或者代码块。

    

 
 
  1. public class ThreadLock2{

  2.    public static void main(String[] agrs) {

  3.        final InsertData insertData = new InsertData();

  4.        new Thread("线程1"){

  5.            @Override

  6.            public void run() {

  7.                insertData.insert(Thread.currentThread());

  8.            }

  9.        }.start();

  10.        new Thread("线程2"){

  11.            @Override

  12.            public void run() {

  13.                insertData.insert(Thread.currentThread());

  14.            }

  15.        }.start();

  16.    }

  17. }

  18. class InsertData extends Thread {

  19.    private List<Integer> list = new ArrayList<>();

  20.    //在这其实是锁住了InsertData对象的monitor锁

  21.    public synchronized void insert(Thread thread) {

  22.        for (int i = 0; i < 5; i++) {

  23.            System.out.println(thread.getName() + "插入: " + i);

  24.            list.add(i);

  25.        }

  26.    }

  27. }

    输出结果:    

 
 
  1. 线程1插入: 0

  2. 线程1插入: 1

  3. 线程1插入: 2

  4. 线程1插入: 3

  5. 线程1插入: 4

  6. 线程2插入: 0

  7. 线程2插入: 1

  8. 线程2插入: 2

  9. 线程2插入: 3

  10. 线程2插入: 4

    试着把  public synchronized void insert(Thread thread)  中的 synchronized去掉,再看看结果

    此时,线程1和线程2可能会交叉运行,因为insert这个方法不再是临界资源了(允许多个线程同时进入)

    

    insert方法还可以改成以下两种方式:

    

 
 
  1. class InsertData extends Thread {

  2.    private List<Integer> list = new ArrayList<>();

  3.    public void insert(Thread thread) {

  4.        synchronized (this) {

  5.            for (int i = 0; i < 5; i++) {

  6.                System.out.println(thread.getName() + "插入: " + i);

  7.                list.add(i);

  8.            }

  9.        }

  10.    }

  11. }

    

 
 
  1. class InsertData extends Thread {

  2.    private List<Integer> list = new ArrayList<>();

  3.    private Object object = new Object();

  4.    

  5.    public synchronized void insert(Thread thread) {

  6.        synchronized (object) {

  7.            for (int i = 0; i < 5; i++) {

  8.                System.out.println(thread.getName() + "插入: " + i);

  9.                list.add(i);

  10.            }

  11.        }

  12.    }

  13. }


    说明:

        1)当一个线程正在访问一个对象的synchronized方法,那么其他线程不能访问该对象的其他synchronized方法。这个原因很简单,因为一个对象只有一把锁,当一个线程获取了该对象的锁之后,其他线程无法获取该对象的锁,所以无法访问该对象的其他synchronized方法。

        2)当一个线程正在访问一个对象的synchronized方法,那么其他线程能访问该对象的非synchronized方法。这个原因很简单,访问非synchronized方法不需要获得该对象的锁,假如一个方法没用synchronized关键字修饰,说明它不会使用到临界资源,那么其他线程是可以访问这个方法的,

        3)如果一个线程A需要访问对象object1的synchronized方法fun1,另外一个线程B需要访问对象object2的synchronized方法fun1,即使object1和object2是同一类型的对象,也不会产生线程安全问题,因为他们访问的是不同的对象,所以不存在互斥问题。


    synchronized的另外一种使用方式:

 
 
  1. public class MyThread{

  2. private Object lock = new Object();

  3. private ArrayList<Integer> list = new ArrayList<>();

  4. public static void main(String[] args) {

  5. MyThread mh = new MyThread();

  6. ThreadTest tt = mh.new ThreadTest();

  7. ThreadTest tt2 = mh.new ThreadTest();

  8. tt.start();

  9. tt2.start();

  10. }

  11. class ThreadTest extends Thread{

  12. @Override

  13. public void run() {

  14. synchronized (lock) {

  15. for(int i=0;i<5;i++){

  16. list.add(i);

  17. System.out.println(Thread.currentThread().getName()

  18. + "insert:" +i);

  19. }

  20. }

  21. }

  22. }

  23. }

    输出结果:

 
 
  1. Thread-0insert:0

  2. Thread-0insert:1

  3. Thread-0insert:2

  4. Thread-0insert:3

  5. Thread-0insert:4

  6. Thread-1insert:0

  7. Thread-1insert:1

  8. Thread-1insert:2

  9. Thread-1insert:3

  10. Thread-1insert:4



注意事项:

    static方法,使用的是类锁(多个对象使用同一个类的monitor)

    和非static方法,使用的是对象锁(每个对象维护一个monitor)



synchronized的缺陷:

    1.使用synchronized包住的代码块,只可能有两种状态:顺利执行完毕释放锁,执行发生异常释放锁,不会由于异常导致出现死锁现象

    2.如果synchronized包住的代码块中有sleep等操作,比如I/O阻塞,但是其他线程还是需要等待,这样程序的效率就比较低了

    3.等待synchronized释放锁的线程会一直等待下去(死心塌地,不到黄河心不死)


    

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值