同步方法:
由于我们可以通过 private 关键字来保证数据对象只能被方法访问,所以我们只需要针对方法提供一套机制,这套机制就是 synchronized 关键字,它包括两种用法:synchronized 方法 和 synchronized 块
synchronized方法控制对"对象"的访问,每个对象对应一把锁,每个synchronized方法都必须获得调用该方法的对象的锁才能执行,否则线程会阻塞,方法一旦执行,就独占该锁,直到该方法返回才释放锁,后面被阻塞的线程才能获得这个锁,继续执行
同步方法弊端:
只读代码,不需要同步每个人都可以读,读的时候是不会有错的
修改代码的时候才需要同步。所以还有个 sychronized 块,
方法里面需要修改的内容的才需要锁,锁的太多,浪费资源。
所以说同步方法有时候也不是那么高效。
同步块:
- synchronized(Obj){}
- Obj 称之为同步监视器
- Obj可以是任何对象,但是推荐使用共享资源作为同步监视器
- 同步方法中无需指定同步监视器,因为同步方法的同步监视器就是this,就是这个对象的本身,或者是 class [反射中讲解]
- 同步监视器的执行过程
- 第一个线程访问,锁定同步监视器,执行其中代码
- 第二个线程访问,发现同步监视器被锁定,无法访问
- 第一个线程访问完毕,解锁同步监视器
- 第二线程访问,发现同步监视器没有锁,然后锁定并访问
案例一:安全的火车站买票
synchronized 同步方法,锁的是this,也就是BuyTicket的对象
1 package com.thread.syn; 2 3 public class SafeBuyTicket { 4 5 public static void main(String[] args) { 6 BuyTicket station = new BuyTicket(); 7 8 new Thread(station, "苦逼的我").start(); 9 new Thread(station, "牛逼的你们").start(); 10 new Thread(station, "可恶的黄牛党").start(); 11 12 } 13 } 14 15 16 class BuyTicket implements Runnable { 17 18 //票 19 private int ticketNums = 10; 20 boolean flag = true;//外部停止方式 21 22 @Override 23 public void run() { 24 25 //买票 26 while (flag) { 27 try { 28 buy(); 29 } catch (InterruptedException e) { 30 e.printStackTrace(); 31 } 32 } 33 } 34 35 //synchronized 同步方法,锁的是this 36 private synchronized void buy() throws InterruptedException { 37 //判断是否有票 38 if (ticketNums <= 0) { 39 flag = false; 40 return; 41 } 42 43 //模拟延时 44 Thread.sleep(100); 45 //买票 46 System.out.println(Thread.currentThread().getName() + "拿到" + ticketNums--); 47 } 48 49 } 50 51 结果: 52 苦逼的我拿到10 53 苦逼的我拿到9 54 苦逼的我拿到8 55 苦逼的我拿到7 56 苦逼的我拿到6 57 苦逼的我拿到5 58 苦逼的我拿到4 59 牛逼的你们拿到3 60 牛逼的你们拿到2 61 牛逼的你们拿到1
案例二:银行取钱
synchronized默认是锁this,而this锁不住,因为它操作的是账户,不是银行。就需要用到同步块。
sychronized(Obj){},锁的对象是变化的量,需要增删改的对象
1 package com.thread.syn; 2 3 public class SafeBank { 4 public static void main(String[] args) { 5 //账户 6 Account account = new Account(100, "结婚基金"); 7 8 Drawing you = new Drawing(account, 50, "你"); 9 Drawing girlFriend = new Drawing(account, 100, "girlFriend"); 10 11 you.start(); 12 girlFriend.start(); 13 14 } 15 } 16 17 18 //账户 19 class Account { 20 int money;//余额 21 String name;//卡名 22 23 public Account(int money, String name) { 24 this.money = money; 25 this.name = name; 26 } 27 } 28 29 //银行:模拟取款 30 class Drawing extends Thread { 31 32 //账户 33 Account account; 34 //取了多少钱 35 int drawingMoney; 36 //现在手里有多少钱 37 int nowMoney; 38 39 public Drawing(Account account, int drawingMoney, String name) { 40 super(name); 41 this.account = account; 42 this.drawingMoney = drawingMoney; 43 } 44 45 //取钱 46 //synchronized 默认锁的是this. 47 @Override 48 public void run() { 49 //锁的对象就是变化的量,需要增删改的对象 50 synchronized (account) { 51 //判断有没有钱 52 if (account.money - drawingMoney < 0) { 53 System.out.println(Thread.currentThread().getName() + "钱不够,取不了"); 54 return; 55 } 56 57 try { 58 Thread.sleep(100); 59 } catch (InterruptedException e) { 60 e.printStackTrace(); 61 } 62 63 //卡内余额 = 余额 - 你取得钱 64 account.money = account.money - drawingMoney; 65 //你手里的钱 66 nowMoney = nowMoney + drawingMoney; 67 68 System.out.println(account.name + "余额为:" + account.money); 69 //Thread.currentThread().getName() = this.getName() 70 System.out.println(this.getName() + "手里的钱:" + nowMoney); 71 72 } 73 } 74 75 } 76 结果: 77 结婚基金余额为:50 78 你手里的钱:50 79 girlFriend钱不够,取不了
案例三:线程安全的集合
把list对象放进同步代码块,锁住即可
1 package com.thread.syn; 2 3 import java.util.ArrayList; 4 import java.util.List; 5 6 public class SafeList { 7 8 public static void main(String[] args) { 9 10 List<String> list = new ArrayList<>(); 11 for (int i = 0; i < 10000; i++) { 12 new Thread(() -> { 13 synchronized (list){ 14 list.add(Thread.currentThread().getName()); 15 } 16 }).start(); 17 } 18 try { 19 Thread.sleep(3000); 20 } catch (InterruptedException e) { 21 e.printStackTrace(); 22 } 23 System.out.println(list.size()); 24 } 25 } 26 27 结果: 28 10000