• 线程同步


    线程同步:

        现实生活中,我们会遇到“同一个资源,多个人都想使用”的问题,比如,食堂排队打饭,每个人都想吃饭,最天然的解决办法就是,排队,一个个来。

        处理多线程问题时,多个线程访问同一个资源对象,并且某些线程还想修改这个对象。这时候我们就需要线程同步。线程同步其实就是一种等待机制,多个需要同时访问此对象的线程进入这个对象的等待池形成队列,等待前面线程使用完毕,下一个线程再使用。

    • 由于同一个进程的多个线程共享同一块存储空间,在带来方便的同时,也带来了访问冲突问题,为了保证数据在方法中被访问时的正确性,在访问时加入锁机制synchronized,当一个线程获得对象的排它锁,独占资源,其他线程必须等待,使用后释放锁即可,存在以下问题:
      1. 一个线程持有锁会导致其他所有需要此锁的线程挂起;
      2. 在多线程竞争下,加锁,释放锁会导致比较多的上下文切换 和 调度延时,引起性能问题;
      3. 如果一个优先级高的线程等待一个优先级低的线程释放锁,会导致优先级导致,引起性能问题。

    多线程不安全案例一:

     1 package com.huolongluo.coindemo.morethread.sub3;
     2 
     3 /**
     4  * Created by 火龙裸 on 2019/11/9.
     5  * desc   : 不安全的买票
     6  * <p>
     7  * 线程不安全,有可能有的人拿到负数票
     8  * version: 1.0
     9  */
    10 public class UnsafeBuyTicket {
    11 
    12     public static void main(String[] args) {
    13         BuyTicket station = new BuyTicket();
    14 
    15         new Thread(station, "我").start();
    16         new Thread(station, "你").start();
    17         new Thread(station, "黄牛党").start();
    18     }
    19 }
    20 
    21 class BuyTicket implements Runnable {
    22 
    23     //
    24     private int ticketNums = 10;
    25     boolean flag = true;//外部停止方式
    26 
    27     @Override
    28     public void run() {
    29         //买票
    30         while (flag) {
    31             try {
    32                 buy();
    33             } catch (InterruptedException e) {
    34                 e.printStackTrace();
    35             }
    36         }
    37     }
    38 
    39     private void buy() throws InterruptedException {
    40         //判断是否有票
    41         if (ticketNums <= 0) {
    42             flag = false;
    43             return;
    44         }
    45         //模拟延时
    46         Thread.sleep(100);
    47         //买票
    48         System.out.println(Thread.currentThread().getName() + "拿到" + (ticketNums--));
    49     }
    50 }

    运行结果:

    多线程不安全案例二:

     1 package com.huolongluo.coindemo.morethread.sub3;
     2 
     3 /**
     4  * Created by 火龙裸 on 2019/11/9.
     5  * desc   : 不安全取钱
     6  * <p>
     7  * 两个人去银行取现,账户
     8  * version: 1.0
     9  */
    10 public class UnsafeBank {
    11     public static void main(String[] args) {
    12         //账户
    13         Account account = new Account(100, "结婚基金");
    14 
    15         Drawing you = new Drawing(account, 50, "你");
    16         Drawing girlFriend = new Drawing(account, 100, "girlFriend");
    17 
    18         you.start();
    19         girlFriend.start();
    20     }
    21 }
    22 
    23 //账户
    24 class Account {
    25     double money;//余额
    26     String name;//卡名
    27 
    28     public Account(double money, String name) {
    29         this.money = money;
    30         this.name = name;
    31     }
    32 }
    33 
    34 //银行:模拟取款
    35 class Drawing extends Thread {
    36     Account account;//账户
    37     //取了多少钱
    38     double drawingMoney;
    39     //现在手里有多少钱
    40     double nowMoney;
    41 
    42     public Drawing(Account account, double drawingMoney, String name) {
    43         super(name);
    44         this.account = account;
    45         this.drawingMoney = drawingMoney;
    46     }
    47 
    48     //取钱
    49     @Override
    50     public void run() {
    51         //判断有没有钱
    52         if (account.money - drawingMoney < 0) {
    53             System.out.println(Thread.currentThread().getName() + "钱不够,取不了");
    54             return;
    55         }
    56 
    57         //sleep可以放大问题的发生性
    58         try {
    59             Thread.sleep(1000);
    60         } catch (InterruptedException e) {
    61             e.printStackTrace();
    62         }
    63 
    64         //卡内余额 = 余额 - 你取的钱
    65         account.money = account.money - drawingMoney;
    66         //你手里的钱
    67         nowMoney = nowMoney + drawingMoney;
    68 
    69         System.out.println(account.name + "余额为:" + account.money);
    70         //Thread.currentThread().getName() 等价于 this.getName()
    71         System.out.println(this.getName() + "手里的钱:" + nowMoney);
    72     }
    73 }

    运行结果:

    多线程不安全案例三:

     1 package com.huolongluo.coindemo.morethread.sub3;
     2 
     3 import java.util.ArrayList;
     4 import java.util.List;
     5 
     6 /**
     7  * Created by 火龙裸 on 2019/11/9.
     8  * desc   : 线程不安全的集合
     9  * version: 1.0
    10  */
    11 public class UnsafeList {
    12     public static void main(String[] args) {
    13 
    14         final List<String> list = new ArrayList<>();
    15         for (int i = 0; i < 10000; i++) {
    16             new Thread(new Runnable() {
    17                 @Override
    18                 public void run() {
    19                     list.add(Thread.currentThread().getName());
    20                 }
    21             }).start();
    22         }
    23 
    24         System.out.println(list.size());
    25     }
    26 }

    运行结果:

    可以看出,本来按照预期应该是9999,但是却只有9998。所以说ArrayList是不安全的。

    为了避免消耗不必要的性能,提高效率,一般在方法里面,需要修改的内容才需要锁,锁的太多,浪费资源。比如代码中有些只读、有些只写,那么只需要对负责“只写”的那个代码块进行加锁就行。

    代码示例:

     1 package com.huolongluo.coindemo.morethread.sub3;
     2 
     3 /**
     4  * Created by 火龙裸 on 2019/11/9.
     5  * desc   : 不安全的买票
     6  * version: 1.0
     7  */
     8 public class UnsafeBuyTicket {
     9 
    10     public static void main(String[] args) {
    11         BuyTicket station = new BuyTicket();
    12 
    13         new Thread(station, "我").start();
    14         new Thread(station, "你").start();
    15         new Thread(station, "黄牛党").start();
    16     }
    17 }
    18 
    19 class BuyTicket implements Runnable {
    20 
    21     //
    22     private int ticketNums = 10;
    23     boolean flag = true;//外部停止方式
    24 
    25     @Override
    26     public void run() {
    27         //买票
    28         while (flag) {
    29             try {
    30                 buy();
    31             } catch (InterruptedException e) {
    32                 e.printStackTrace();
    33             }
    34         }
    35     }
    36 
    37     //synchronized 同步方法,锁的是this
    38     private synchronized void buy() throws InterruptedException {
    39         //判断是否有票
    40         if (ticketNums <= 0) {
    41             flag = false;
    42             return;
    43         }
    44         //模拟延时
    45         Thread.sleep(100);
    46         //买票
    47         System.out.println(Thread.currentThread().getName() + "拿到" + (ticketNums--));
    48     }
    49 }

    运行结果:

    再来第二个案例:

     1 package com.huolongluo.coindemo.morethread.sub3;
     2 
     3 /**
     4  * Created by 火龙裸 on 2019/11/9.
     5  * desc   : 不安全取钱
     6  * <p>
     7  * 两个人去银行取现,账户
     8  * version: 1.0
     9  */
    10 public class UnsafeBank {
    11     public static void main(String[] args) {
    12         //账户
    13         Account account = new Account(100, "结婚基金");
    14 
    15         Drawing you = new Drawing(account, 50, "你");
    16         Drawing girlFriend = new Drawing(account, 100, "girlFriend");
    17 
    18         you.start();
    19         girlFriend.start();
    20     }
    21 }
    22 
    23 //账户
    24 class Account {
    25     double money;//余额
    26     String name;//卡名
    27 
    28     public Account(double money, String name) {
    29         this.money = money;
    30         this.name = name;
    31     }
    32 }
    33 
    34 //银行:模拟取款
    35 class Drawing extends Thread {
    36     Account account;//账户
    37     //取了多少钱
    38     double drawingMoney;
    39     //现在手里有多少钱
    40     double nowMoney;
    41 
    42     public Drawing(Account account, double drawingMoney, String name) {
    43         super(name);
    44         this.account = account;
    45         this.drawingMoney = drawingMoney;
    46     }
    47 
    48     //取钱
    49     @Override
    50     public synchronized void run() {
    51         //判断有没有钱
    52         if (account.money - drawingMoney < 0) {
    53             System.out.println(Thread.currentThread().getName() + "钱不够,取不了");
    54             return;
    55         }
    56 
    57         //sleep可以放大问题的发生性
    58         try {
    59             Thread.sleep(1000);
    60         } catch (InterruptedException e) {
    61             e.printStackTrace();
    62         }
    63 
    64         //卡内余额 = 余额 - 你取的钱
    65         account.money = account.money - drawingMoney;
    66         //你手里的钱
    67         nowMoney = nowMoney + drawingMoney;
    68 
    69         System.out.println(account.name + "余额为:" + account.money);
    70         //Thread.currentThread().getName() 等价于 this.getName()
    71         System.out.println(this.getName() + "手里的钱:" + nowMoney);
    72     }
    73 }

    这个代码中,synchronized添加到了run方法位置处了,很明显无效。因为这里锁run方法,synchronized默认锁的是this对象,也就是它本身,放在这里也就是锁的是Drawing对象(银行)。但是我们操作的“增删改”的对象不是银行,而是账户account对象。用account作为同步监视器,所以这个时候需要用锁代码块。把涉及到“写”操作的代码都丢到块里面就行。如下:

     1 package com.huolongluo.coindemo.morethread.sub3;
     2 
     3 /**
     4  * Created by 火龙裸 on 2019/11/9.
     5  * desc   : 安全取钱
     6  * <p>
     7  * 两个人去银行取现,账户
     8  * version: 1.0
     9  */
    10 public class UnsafeBank {
    11     public static void main(String[] args) {
    12         //账户
    13         Account account = new Account(1000, "结婚基金");
    14 
    15         Drawing you = new Drawing(account, 50, "你");
    16         Drawing girlFriend = new Drawing(account, 100, "girlFriend");
    17 
    18         you.start();
    19         girlFriend.start();
    20     }
    21 }
    22 
    23 //账户
    24 class Account {
    25     double money;//余额
    26     String name;//卡名
    27 
    28     public Account(double money, String name) {
    29         this.money = money;
    30         this.name = name;
    31     }
    32 }
    33 
    34 //银行:模拟取款
    35 class Drawing extends Thread {
    36     Account account;//账户
    37     //取了多少钱
    38     double drawingMoney;
    39     //现在手里有多少钱
    40     double nowMoney;
    41 
    42     public Drawing(Account account, double drawingMoney, String name) {
    43         super(name);
    44         this.account = account;
    45         this.drawingMoney = drawingMoney;
    46     }
    47 
    48     //取钱
    49     @Override
    50     public void run() {
    51         //锁的对象应该是变化的量,需要增删改的对象,这里就是account。
    52         synchronized (account) {
    53             //判断有没有钱
    54             if (account.money - drawingMoney < 0) {
    55                 System.out.println(Thread.currentThread().getName() + "钱不够,取不了");
    56                 return;
    57             }
    58 
    59             //sleep可以放大问题的发生性
    60             try {
    61                 Thread.sleep(1000);
    62             } catch (InterruptedException e) {
    63                 e.printStackTrace();
    64             }
    65 
    66             //卡内余额 = 余额 - 你取的钱
    67             account.money = account.money - drawingMoney;
    68             //你手里的钱
    69             nowMoney = nowMoney + drawingMoney;
    70 
    71             System.out.println(account.name + "余额为:" + account.money);
    72             //Thread.currentThread().getName() 等价于 this.getName()
    73             System.out.println(this.getName() + "手里的钱:" + nowMoney);
    74         }
    75     }
    76 }

    运行结果:

    再来第三个案例:

     1 package com.huolongluo.coindemo.morethread.sub3;
     2 
     3 import java.util.ArrayList;
     4 import java.util.List;
     5 
     6 /**
     7  * Created by 火龙裸 on 2019/11/9.
     8  * desc   : 线程不安全的集合 变成 安全
     9  * version: 1.0
    10  */
    11 public class UnsafeList {
    12     public static void main(String[] args) {
    13 
    14         final List<String> list = new ArrayList<>();
    15         for (int i = 0; i < 10000; i++) {
    16             new Thread(new Runnable() {
    17                 @Override
    18                 public void run() {
    19                     //将list这个涉及到写操作的对象作为同步监视器
    20                     synchronized (list) {
    21                         list.add(Thread.currentThread().getName());
    22                     }
    23                 }
    24             }).start();
    25         }
    26 
    27         System.out.println(list.size());
    28     }
    29 }

    运行结果:

    小结:

    死锁:

    • 多个线程各自占用一些共享资源对象,并且互相等待其他线程占有的资源才能运行,而导致两个或者多个线程都在等待对方释放资源,都停止执行的情形。某一个同步块同时拥有“两个以上对象的锁”时,就可能会发生“死锁”的问题。

    代码示例:

     1 package com.huolongluo.coindemo.morethread.sub3;
     2 
     3 /**
     4  * Created by 火龙裸 on 2019/11/9.
     5  * desc   : 死锁:多个线程互相抱着对方需要的资源,然后形成僵持。
     6  * version: 1.0
     7  */
     8 public class DeadLock {
     9     public static void main(String[] args) {
    10         MakeUp g1 = new MakeUp(0, "灰姑娘");
    11         MakeUp g2 = new MakeUp(1, "白雪公主");
    12 
    13         g1.start();
    14         g2.start();
    15     }
    16 }
    17 
    18 //口红
    19 class Lipstick {
    20 
    21 }
    22 
    23 //镜子
    24 class Mirror {
    25 
    26 }
    27 
    28 class MakeUp extends Thread {
    29 
    30     //需要的资源只有一份,用static来保证只有一份
    31     static Lipstick lipstick = new Lipstick();
    32     static Mirror mirror = new Mirror();
    33 
    34     int choice;//选择
    35     String girlName;//使用化妆品的人
    36 
    37     public MakeUp(int choice, String girlName) {
    38         this.choice = choice;
    39         this.girlName = girlName;
    40     }
    41 
    42     @Override
    43     public void run() {
    44         //化妆
    45         try {
    46             makeUp();
    47         } catch (InterruptedException e) {
    48             e.printStackTrace();
    49         }
    50     }
    51 
    52     //化妆,互相持有对象的锁,就是需要拿到对方的资源
    53     private void makeUp() throws InterruptedException {
    54         if (choice == 0) {
    55             synchronized (lipstick) {//获得口红的锁
    56                 System.out.println(this.getName() + "获得口红的锁");
    57                 Thread.sleep(1000);
    58 
    59                 synchronized (mirror) {//一秒钟后想获得镜子
    60                     System.out.println(this.getName() + "获得镜子的锁");
    61                 }
    62             }
    63         } else {
    64             synchronized (mirror) {//获得镜子的锁
    65                 System.out.println(this.getName() + "获得镜子的锁");
    66                 Thread.sleep(2000);
    67 
    68                 synchronized (lipstick) {//一秒钟后想获得口红
    69                     System.out.println(this.getName() + "获得口红的锁");
    70                 }
    71             }
    72         }
    73     }
    74 }

    运行结果(代码中,一个人获得了口红的锁,想获得镜子的锁,一个人拿到了镜子的锁,想获得口红的锁,互相僵持。直接卡死在这个位置不动了!):

    解决方式如下(直接把锁拿出来):

     1 package com.huolongluo.coindemo.morethread.sub3;
     2 
     3 /**
     4  * Created by 火龙裸 on 2019/11/9.
     5  * desc   : 死锁:多个线程互相抱着对方需要的资源,然后形成僵持。
     6  * version: 1.0
     7  */
     8 public class DeadLock {
     9     public static void main(String[] args) {
    10         MakeUp g1 = new MakeUp(0, "灰姑娘");
    11         MakeUp g2 = new MakeUp(1, "白雪公主");
    12 
    13         g1.start();
    14         g2.start();
    15     }
    16 }
    17 
    18 //口红
    19 class Lipstick {
    20 
    21 }
    22 
    23 //镜子
    24 class Mirror {
    25 
    26 }
    27 
    28 class MakeUp extends Thread {
    29 
    30     //需要的资源只有一份,用static来保证只有一份
    31     static Lipstick lipstick = new Lipstick();
    32     static Mirror mirror = new Mirror();
    33 
    34     int choice;//选择
    35     String girlName;//使用化妆品的人
    36 
    37     public MakeUp(int choice, String girlName) {
    38         this.choice = choice;
    39         this.girlName = girlName;
    40     }
    41 
    42     @Override
    43     public void run() {
    44         //化妆
    45         try {
    46             makeUp();
    47         } catch (InterruptedException e) {
    48             e.printStackTrace();
    49         }
    50     }
    51 
    52     //化妆,互相持有对象的锁,就是需要拿到对方的资源
    53     private void makeUp() throws InterruptedException {
    54         if (choice == 0) {
    55             synchronized (lipstick) {//获得口红的锁
    56                 System.out.println(this.getName() + "获得口红的锁");
    57                 Thread.sleep(1000);
    58             }
    59             synchronized (mirror) {//一秒钟后想获得镜子
    60                 System.out.println(this.getName() + "获得镜子的锁");
    61             }
    62         } else {
    63             synchronized (mirror) {//获得镜子的锁
    64                 System.out.println(this.getName() + "获得镜子的锁");
    65                 Thread.sleep(2000);
    66             }
    67             synchronized (lipstick) {//一秒钟后想获得口红
    68                 System.out.println(this.getName() + "获得口红的锁");
    69             }
    70         }
    71     }
    72 }

    运行结果:

    小结:

    Lock显式锁:

    代码示例(不安全写法):

     1 package com.huolongluo.coindemo.morethread.sub3;
     2 
     3 /**
     4  * Created by 火龙裸 on 2019/11/9.
     5  * desc   : 测试Lock锁
     6  * version: 1.0
     7  */
     8 public class TestLock {
     9     public static void main(String[] args) {
    10         TestLock2 testLock2 = new TestLock2();
    11 
    12         new Thread(testLock2).start();
    13         new Thread(testLock2).start();
    14         new Thread(testLock2).start();
    15     }
    16 }
    17 
    18 class TestLock2 implements Runnable {
    19 
    20     int ticketNums = 5;
    21 
    22     @Override
    23     public void run() {
    24         while (true) {
    25             if (ticketNums > 0) {
    26                 try {
    27                     Thread.sleep(1000);
    28                 } catch (InterruptedException e) {
    29                     e.printStackTrace();
    30                 }
    31                 System.out.println(ticketNums--);
    32             } else {
    33                 break;
    34             }
    35         }
    36     }
    37 }

    运行结果:

    代码示例(安全写法):

     1 package com.huolongluo.coindemo.morethread.sub3;
     2 
     3 import java.util.concurrent.locks.ReentrantLock;
     4 
     5 /**
     6  * Created by 火龙裸 on 2019/11/9.
     7  * desc   : 测试Lock锁
     8  * version: 1.0
     9  */
    10 public class TestLock {
    11     public static void main(String[] args) {
    12         TestLock2 testLock2 = new TestLock2();
    13 
    14         new Thread(testLock2).start();
    15         new Thread(testLock2).start();
    16         new Thread(testLock2).start();
    17     }
    18 }
    19 
    20 class TestLock2 implements Runnable {
    21 
    22     int ticketNums = 5;
    23 
    24     //定义Lock锁,Lock是一个接口,ReentrantLock实现了该接口
    25     private final ReentrantLock reentrantLock = new ReentrantLock();//可重入锁
    26 
    27 
    28     @Override
    29     public void run() {
    30         while (true) {
    31             reentrantLock.lock();//加锁
    32             try {
    33                 if (ticketNums > 0) {
    34                     try {
    35                         Thread.sleep(1000);
    36                     } catch (InterruptedException e) {
    37                         e.printStackTrace();
    38                     }
    39                     System.out.println(ticketNums--);
    40                 } else {
    41                     break;
    42                 }
    43             } finally {
    44                 reentrantLock.unlock();//解锁
    45             }
    46         }
    47     }
    48 }

    运行结果:

    总结:

  • 相关阅读:
    微软小娜APP的案例分析
    嵌入式第12次实验
    嵌入式第11次实验
    嵌入式第10次实验报告
    嵌入式第9次实验
    软工 小组作业(第二次)
    嵌入式软件设计第8次实验报告-140201236-沈樟伟
    5月17下
    5月17上
    5月15上午
  • 原文地址:https://www.cnblogs.com/huolongluo/p/11827152.html
Copyright © 2020-2023  润新知