线程同步:队列和锁,三大不安全案例;同步锁synchronized,死锁,lock锁

淩亂°似流年 2023-10-13 23:18 189阅读 0赞

线程同步

什么是线程同步

  • 线程同步就是并发;多个线程操作同一个资源
  • 线程同步实际就是一个等待机制,多个需要访问一个对象的线程进入这个对象的线程等待池形成队列,等待前一个线程使用完后在交由下一个线程使用。
  • 形成条件:队列+锁

  • 当存在多个线程访问同一存储空间时,在访问的同时会导致冲突,导致数据出现错误,为保证数据的准确性,在访问对象时为每个对象加入锁,使得一个线程在访问这个存储空间时独占资源,其他线程等待锁的释放后才能访问这一线程。
  • 使用锁存在的一些问题:

    1. 一个线程的锁可能导致所有需要这把锁的线程就此挂起。
    2. 在多线程中,锁的加入和锁的释放会引起性能问题
    3. 如果一个优先级高的线程需要等待一个优先级低的线程的锁的释放会导致性能问题。

三大不安全案例

不安全的买票

  • 由于每个线程都有自己的存储空间,所以在面对最后一张票时,所有线程都会将票拿到自己的存储空间中,所有导致最后的结果可能出现某个线程买了第-1张票的情况。
  • 代码解释一下:

    package com.XianCheng;
    //不安全的买票
    //使用多线程对同一资源进行操作没有添加同步锁。导致数据紊乱出现的不安全案例
    public class Unsafeticket {

    1. public static void main(String[] args) {
    2. //创建线程对象
    3. buy buy = new buy();
    4. new Thread(buy,"张一").start();
    5. new Thread(buy,"张二").start();
    6. new Thread(buy,"张三").start();
    7. }

    }
    //创建线程,买票
    class buy implements Runnable{

  1. //票
  2. private int ticket=10;
  3. //设置外部标志停止符
  4. Boolean flag=true;
  5. @Override
  6. public void run() {
  7. while (flag){
  8. try {
  9. Buy();
  10. } catch (InterruptedException e) {
  11. e.printStackTrace();
  12. }
  13. }
  14. }
  15. //创建买票方法
  16. public void Buy() throws InterruptedException {
  17. //判断是否有票
  18. if (ticket<0){
  19. flag=false;
  20. return;
  21. }
  22. //线程休眠,增加事件的可能性
  23. Thread.sleep(100);
  24. //买票
  25. System.out.println(Thread.currentThread().getName()+"买到了第"+ticket--);
  26. }
  27. }

不安全的银行取钱

  • 线程为同步,两个人都可在同一个账户中取钱,取钱的总金额可能大于账户余额,导致数据错误。
  • 代码又来了,不懂看代码:

    package com.XianCheng;
    //模拟多线程取钱,导致的数据错误,从而出现的线程不安全
    public class Unsafebank {

    1. public static void main(String[] args) {
    2. //创建一个账户类的对象,并且给其赋值
    3. Account account = new Account("账户",100);
    4. //创建两个线程对象用于取钱
    5. getMoney getMoney = new getMoney(account,20);
    6. getMoney getMoney1 = new getMoney(account,90);
    7. new Thread(getMoney,"老公").start();
    8. new Thread(getMoney1,"老婆").start();
    9. }

    }

    //创建一个账户类,用于保存账户的属性
    class Account{

    1. //账户名
    2. String name;
    3. //账户余额

    int money;

    1. public Account(String name, int money) {
    2. this.name = name;
    3. this.money = money;
    4. }

    }
    //创建一个线程:取钱
    class getMoney implements Runnable{

    1. public getMoney(Account account, int num) {
    2. this.account = account;
    3. this.num = num;

    // this.name = name;

    1. }
    2. //取钱的账户
    3. Account account;
    4. //取钱的数目
    5. private int num;
    6. //取钱人的名字
    7. private String name;
    8. //手里的前
    9. private int nowMonwy;
    10. @Override
    11. public void run() {
    12. getWay();
    13. }
    14. //创建一个取钱方法
    15. public void getWay(){
    16. //判断账户是否有钱
    17. if (account.money-num<0){
    18. System.out.println(Thread.currentThread().getName()+"账户钱不够");
    19. return;
    20. }
    21. //等待
    22. try {
    23. Thread.sleep(1000);
    24. } catch (InterruptedException e) {
    25. e.printStackTrace();
    26. }
    27. //有钱的情况
    28. //1.更改卡内余额
    29. account.money=account.money-num;
    30. //2.输出手里的前
    31. nowMonwy = nowMonwy+num;
    32. System.out.println(Thread.currentThread().getName()+"手里有"+nowMonwy);
    33. //输出余额
    34. System.out.println(account.name+"余额"+account.money);
    35. }

    }

不安全的数组集合

  • 来咯来咯,直接来咯:

    package com.XianCheng;

    import java.util.ArrayList;

    //不安全的线程集合
    public class UnsafeThreadList {

    1. public static void main(String[] args) {
    2. //新建一个list数组
    3. ArrayList<String> list = new ArrayList<String>();
    4. //向数组中添加数据
    5. for (int i = 0; i < 10000; i++) {
    6. //新建线程对象,将线程名加入数组中
    7. new Thread(()->{
    8. list.add(Thread.currentThread().getName());
    9. }).start();
    10. }
    11. //输出数组大小=>应该是10000
    12. System.out.println(list.size());
    13. //实际结果小于10000=>因为线程为同步,所有有可能多个线程将数据放到了数组中的同一位置,导致数组大小为达到期待值
    14. }

    }

同步锁

  • synchronized可以用于方法,锁定这个方法体本身,让这个方法体运行时资源只被一个线程使用,synchronized默认锁定this,即本身这个方法或类。
  • synchronized(锁的对象){锁的方法体},锁定的是这个对象,方法体内是对对象的一些操作。

juc安全类型集合

  • 本质就是在不安全集合的基础上增加了synchronized锁,使得并发时,资源不会被多人占用,导致数据出错
  • 更改的安全类型集合的代码:

    package com;

    import java.util.concurrent.CopyOnWriteArrayList;

    //juc安全类型集合
    public class safeLIst {

    1. public static void main(String[] args) {
    2. //创建一个安全集合的对象
    3. CopyOnWriteArrayList<String> objects = new CopyOnWriteArrayList<String>();
    4. //添加数据
    5. for (int i = 0; i < 10000; i++) {
    6. objects.add(Thread.currentThread().getName());
    7. }
    8. System.out.println(objects.size());
    9. }

    }

死锁

  • 死锁就是多个线程对于对方锁占有的资源有需求,但是都不将资源释放给对仿,程序就一直处于等待获取资源的状态,造成死锁。
  • 给个例子,假设有两个资源:镜子,口红;同时有两个线程:男,女;此时男占有镜子不释放,同时想要获得口红,女占有口红不释放,同时想要获取镜子,这就会造成死锁。
  • 代码说话:

    package com.XianCheng;
    //死锁现象的解释
    public class DeadLocak {

    1. public static void main(String[] args) {
    2. //创建线程对象
    3. Makeup man = new Makeup(0,"男");
    4. Makeup woman = new Makeup(1,"女");
    5. man.start();
    6. woman.start();
    7. }

    }
    //创建资源:镜子
    class mirror{

  1. }
  2. //创建资源:口红
  3. class red{
  4. }
  5. //创建线程
  6. class Makeup extends Thread{
  7. //实例化类,同时使用static保证资源只有一份
  8. static mirror m=new mirror();
  9. static red r=new red();
  10. //选择
  11. int choice;
  12. //使用对东西的对象
  13. String name;
  14. //构造器
  15. Makeup(int choice,String name){
  16. this.choice=choice;
  17. this.name=name;
  18. }
  19. @Override
  20. public void run() {
  21. makeup();
  22. }
  23. //化妆这个方法
  24. public void makeup(){
  25. //判断谁获取了哪样资源
  26. if (choice==0){
  27. synchronized (m){
  28. //谁拥有镜子这个锁
  29. System.out.println(Thread.currentThread().getName()+"拥有镜子这个锁");
  30. synchronized (r){
  31. //争夺口红这个锁
  32. System.out.println(Thread.currentThread().getName()+"获得口红这个锁");
  33. }//将此处释放即可解除死锁
  34. }
  35. }
  36. else {
  37. synchronized (r){
  38. //谁拥有口红这个锁
  39. System.out.println(Thread.currentThread().getName()+"拥有口红这个锁");
  40. synchronized (m){
  41. //争夺镜子这个锁
  42. System.out.println(Thread.currentThread().getName()+"获得镜子这个锁");
  43. }
  44. }
  45. }
  46. }
  47. }

lock锁

  1. package com.XianCheng;
  2. import java.util.concurrent.locks.ReentrantLock;
  3. //测试lock锁的使用,以买票为例
  4. public class TestLock {
  5. public static void main(String[] args) {
  6. //创建线程对象
  7. Buy buy = new Buy();
  8. new Thread(buy,"1").start();
  9. new Thread(buy,"2").start();
  10. new Thread(buy,"3").start();
  11. }
  12. }
  13. //创建线程
  14. class Buy implements Runnable{
  15. //定义票
  16. int num=10;
  17. //创建lock锁对象
  18. ReentrantLock re=new ReentrantLock();
  19. @Override
  20. public void run() {
  21. while (true){
  22. try {
  23. re.lock();
  24. //判断是否有票
  25. if (num>0){
  26. try {
  27. Thread.sleep(100);
  28. } catch (InterruptedException e) {
  29. e.printStackTrace();
  30. }
  31. System.out.println(num--);
  32. }
  33. else {
  34. break;
  35. }
  36. }finally {
  37. //解锁
  38. re.unlock();
  39. }
  40. }
  41. }
  42. }

发表评论

表情:
评论列表 (有 0 条评论,189人围观)

还没有评论,来说两句吧...

相关阅读

    相关 线-同步 递归

    多线程 同步锁(互斥锁) 解决什么问题? 同步锁解决什么问题? 多个线程操作同一个数据,可能会发生数据错乱的问题,因为一个线程拿到数据后,还没来得及对数据进行操