线程同步

线程同步:

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

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

  • 由于同一个进程的多个线程共享同一块存储空间,在带来方便的同时,也带来了访问冲突问题,为了保证数据在方法中被访问时的正确性,在访问时加入锁机制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 }

运行结果:

总结:

posted @ 2019-11-10 12:21  火龙裸先生  阅读(392)  评论(0编辑  收藏  举报