线程同步:
现实生活中,我们会遇到“同一个资源,多个人都想使用”的问题,比如,食堂排队打饭,每个人都想吃饭,最天然的解决办法就是,排队,一个个来。
处理多线程问题时,多个线程访问同一个资源对象,并且某些线程还想修改这个对象。这时候我们就需要线程同步。线程同步其实就是一种等待机制,多个需要同时访问此对象的线程进入这个对象的等待池形成队列,等待前面线程使用完毕,下一个线程再使用。
- 由于同一个进程的多个线程共享同一块存储空间,在带来方便的同时,也带来了访问冲突问题,为了保证数据在方法中被访问时的正确性,在访问时加入锁机制synchronized,当一个线程获得对象的排它锁,独占资源,其他线程必须等待,使用后释放锁即可,存在以下问题:
- 一个线程持有锁会导致其他所有需要此锁的线程挂起;
- 在多线程竞争下,加锁,释放锁会导致比较多的上下文切换 和 调度延时,引起性能问题;
- 如果一个优先级高的线程等待一个优先级低的线程释放锁,会导致优先级导致,引起性能问题。
多线程不安全案例一:
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 }
运行结果:

总结:
