Java基础(12):多线程(四):线程并发和同步锁机制:

本文探讨了Java并发编程中的线程并发问题,如数据不安全的情况,以及如何通过同步锁机制来解决。举例说明了在多线程环境下,未使用同步锁导致的问题,并对比了`synchronized`关键字和`Lock`锁的使用。同时,文章还分析了死锁的概念及其避免方法,强调了正确使用同步锁以防止性能问题和死锁的重要性。
摘要由CSDN通过智能技术生成

线程并发和同步锁机制:

并发:

并发指的是当多个线程对同一个对象的数据操作。但是并发会带来数据混乱,数据不安全问题。例如:

/**
 1. 2.多个线程访问同一个对象,会引起并发问题,这里通过synchronized同步锁把实现同步化解决问题。synchronized的同步锁有两种实现方式,同步方法或者同步对象代码块。
 */
class ConcurrencyRunnable implements Runnable {
    static int i = 1000;
    private static boolean flag = true;
    @Override
    public void run() {
        while (flag) {
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            if (i <= 0) {
                flag = false;
                break;
            }
            System.out.println(Thread.currentThread().getName() + "拿走了" + i--);
        }
    }
}
    public static void main(String[] args) {
        ConcurrencyRunnable concurrencyRunnable = new ConcurrencyRunnable();
        new Thread(concurrencyRunnable, "小明").start();
        new Thread(concurrencyRunnable, "小红").start();
        new Thread(concurrencyRunnable, "小刚").start();
    }

结果:
在这里插入图片描述
会发现小明和小刚拿走的"数字"重复。并发因此的数据不安全的两种情况:

  1. 多个线程对同一个对象数据进行操作且没有使用同步锁机制。
  2. 在线程中使用List添加元素。

(记录第一个问题:为什么List是不安全的?,后续深入学习后单独开一篇记录)

同步锁机制:

所谓的同步锁,指的每个对象都有自己的锁,是当多个线程访问同一对象的时候,会拿到对象的排它锁。当一个线程拿到对象的排它锁时候,然后才操作对象的数据,而其他的线程只能等待,直到释放了对象的排它锁后,其他线程才可以对该对象进行操作。本质上来说,就是让没拿到对象的排它锁的线程进入对象的等待池中等待。

同步锁实现的方式:
  1. 使用synchronized关键字修饰隐式加锁。
  2. 使用Lock锁进行显式加锁。

(记录第二个问题:synchronized的底层代码原理?,后续深入学习后单独开一篇记录)
synchronized:
synchronized(Object) 代码块:
在这里插入图片描述
synchronized 修饰特定方法:
在这里插入图片描述

使用Lock锁():
实例化实现Lock接口的子类ReentrantLock类对象,显示定义加锁和解锁。
在这里插入图片描述
补充:虽然通过同步锁机制能够很好的解决了并发所带来的问题,但如果使用不当也会造成影响,例如:

  1. 一个线程拿到对象的排它锁使得其他等待该对象的线程必须挂起。
  2. 多线程的竞争下,频繁的加锁和解锁会引起Cpu过渡调度等性能问题。
  3. 当一个优先级高的线程在等待优先级低的线程的对象排它锁时候,会引起优先级倒置所带来的性能问题。

严重的情况下,会导致"死锁"现象。

死锁:

举个例子来说:小明和小刚两个人都有各自的玩具,但彼此都想获得对方的。小明不让自己玩具给小刚,小刚也不让自己玩具给小明,结果两个人都僵持在一起,一直都没有结果。多线程的死锁现象就和这例子一样,双方线程彼此都想访问对方的资源,但同时也没有对使用完的资源进行释放。

例如:

public class Demo10 {
    public static void main(String[] args) {
        new Thread(new SwapToy("小明")).start();
        new Thread(new SwapToy("小刚")).start();
    }
}

class xiaoMing {
    protected String toyCar = "玩具车";
}

class xiaoGang {
    protected String toyHouse = "玩具房子";
}

class SwapToy implements Runnable {
    private String name;
    private static xiaoMing xiaoMing;
    private static xiaoGang xiaoGang;
    public SwapToy(String name) {
        this.name = name;
        xiaoGang = new xiaoGang();
        xiaoMing = new xiaoMing();
    }
    @Override
    public void run() {
        if (name.equals("小明")) {
            synchronized (xiaoMing) {
                System.out.println(name + "拿到了" + xiaoMing.toyCar);
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (xiaoGang) {
                    System.out.println(name + "拿到了" + xiaoGang.toyHouse);
                }
            }
        } else {
            synchronized (xiaoGang) {
                System.out.println(name + "拿到了" + xiaoGang.toyHouse);
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (xiaoMing) {
                    System.out.println(name + "拿到了" + xiaoMing.toyCar);
                }
            }
        }
    }
}

结果死锁了(小明和小刚为了玩具打起来了…):
在这里插入图片描述
死锁的必要条件:

  1. 互斥条件:即线程访问的资源只允许线程一个一个访问。
  2. 请求和保持条件:一个线程因请求资源而进入阻塞状态;线程对以获得的资源不释放。
  3. 不剥夺条件:不强制释放线程获得的资源。
  4. 循环条件:线程之间对资源的获取形成了头尾相连的循环。

因此根据必要这四个必要条件任意一条进行处理便可以避免死锁的发生。

因此对代码修改一下:

public class Demo10 {
    public static void main(String[] args) {
        new Thread(new SwapToy("小明")).start();
        new Thread(new SwapToy("小刚")).start();
    }
}

class xiaoMing {
    protected String toyCar = "玩具车";
}

class xiaoGang {
    protected String toyHouse = "玩具房子";
}

class SwapToy implements Runnable {
    private String name;
    private static xiaoMing xiaoMing;
    private static xiaoGang xiaoGang;
    public SwapToy(String name) {
        this.name = name;
        xiaoGang = new xiaoGang();
        xiaoMing = new xiaoMing();
    }
    @Override
    public void run() {
        if (name.equals("小明")) {
            synchronized (xiaoMing) {
                System.out.println(name + "拿到了" + xiaoMing.toyCar);
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            synchronized (xiaoGang) {
                System.out.println(name + "拿到了" + xiaoGang.toyHouse);
            }
        } else {
            synchronized (xiaoGang) {
                System.out.println(name + "拿到了" + xiaoGang.toyHouse);
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            synchronized (xiaoMing) {
                System.out.println(name + "拿到了" + xiaoMing.toyCar);
            }
        }
    }
}

结果(小刚和小明和好了…):
在这里插入图片描述

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值