1.线程同步
1.1 存在线程安全问题的代码
1 package com.example.concurrency; 2 3 import java.util.Arrays; 4 5 public class demo09 { 6 7 private static int index = 0; 8 9 public static void main(String[] args) throws Exception { 10 String[] s = new String[5]; 11 12 Runnable ra = new Runnable() { 13 @Override 14 public void run() { 15 s[index] = "hello"; 16 index++; 17 } 18 }; 19 20 Runnable rb = new Runnable() { 21 22 @Override 23 public void run() { 24 s[index] = "world"; 25 index++; 26 27 } 28 }; 29 30 Thread a = new Thread(ra, "a"); 31 Thread b = new Thread(rb, "b"); 32 a.start(); 33 b.start(); 34 35 a.join(); 36 b.join(); 37 38 System.out.println(Arrays.toString(s)); 39 } 40 }
反复执行,有可能得到如下的结果:
1.2 使用同步代码块
1 package com.example.concurrency; 2 3 import java.util.Arrays; 4 5 public class demo09 { 6 7 private static int index = 0; 8 9 public static void main(String[] args) throws Exception { 10 String[] s = new String[5]; 11 12 Runnable ra = new Runnable() { 13 @Override 14 public void run() { 15 synchronized (s) { 16 s[index] = "hello"; 17 index++; 18 } 19 } 20 }; 21 22 Runnable rb = new Runnable() { 23 24 @Override 25 public void run() { 26 synchronized (s) { 27 s[index] = "world"; 28 index++; 29 } 30 } 31 }; 32 33 Thread a = new Thread(ra, "a"); 34 Thread b = new Thread(rb, "b"); 35 a.start(); 36 b.start(); 37 38 a.join(); 39 b.join(); 40 41 System.out.println(Arrays.toString(s)); 42 } 43 }
注意第15行和26行,增加了同步关键字synchronized,在同步代码块中,临界资源s被锁住,保证同时只能有一个线程执行这段代码。
1.3使用同步方法,解决买票问题
1 package com.example.concurrency; 2 3 public class MyTicket implements Runnable { 4 5 private int ticket = 100; 6 7 @Override 8 public void run() { 9 while (true) { 10 if (!sale()) { 11 break; 12 } 13 } 14 } 15 16 private synchronized boolean sale() { 17 if (ticket <= 0) { 18 return false; 19 } 20 System.out.println(Thread.currentThread().getName() + "卖了第" + ticket + "张票"); 21 ticket--; 22 return true; 23 } 24 }
1 package com.example.concurrency; 2 3 public class TestTicket { 4 public static void main(String[] args) { 5 // TODO Auto-generated method stub 6 MyTicket ticket = new MyTicket(); 7 Thread t1 = new Thread(ticket, "win1"); 8 Thread t2 = new Thread(ticket, "win2"); 9 Thread t3 = new Thread(ticket, "win3"); 10 Thread t4 = new Thread(ticket, "win4"); 11 12 t1.start(); 13 t2.start(); 14 t3.start(); 15 t4.start(); 16 } 17 }
使用同步方法,就不会再将同一张票重复销售了。
每个对象都有一个互斥锁标记,用来分配给线程。
只有拥有对象互斥锁标记的线程,才能进入对该新对象加锁的同步代码块。
线程退出同步代码块时,会释放相应的互斥锁标记。
1.4存钱取钱问题
1 package com.example.concurrency; 2 3 public class BankCard { 4 private double money; 5 6 public double getMoney() { 7 return money; 8 } 9 10 public void setMoney(double money) { 11 this.money = money; 12 } 13 14 public void add(double money) { 15 this.money += money; 16 } 17 18 public void sub(double money) { 19 this.money -= money; 20 } 21 }
1 package com.example.concurrency; 2 3 public class TestBankCard { 4 5 public static void main(String[] args) { 6 // TODO Auto-generated method stub 7 BankCard card = new BankCard(); 8 9 Runnable add = new Runnable() { 10 11 @Override 12 public void run() { 13 for (int i = 0; i < 10; i++) { 14 synchronized (card) { 15 card.add(100); 16 System.out.println("存入100元,当前余额为:" + card.getMoney()); 17 } 18 } 19 20 } 21 }; 22 23 Runnable sub = new Runnable() { 24 @Override 25 public void run() { 26 // TODO Auto-generated method stub 27 for (int i = 0; i < 10; i++) { 28 synchronized (card) { 29 if (card.getMoney() >= 100) { 30 card.sub(100); 31 System.out.println("取出100元,当前余额为:" + card.getMoney()); 32 } else { 33 System.out.println("余额不足"); 34 i--; 35 } 36 } 37 } 38 } 39 }; 40 41 new Thread(add).start(); 42 new Thread(sub).start(); 43 } 44 }
同步规则:
只有再调用包含同步代码块的方法,或者同步方法时,才需要对象的锁标记。
如调用不包含同步代码得方法,或者普通方法时,则不需要锁标记,可直接使用。
JDK中实现的同步数据结构有:StringBuffer、Vector、Hashtable。
2.线程死锁
2.1存在死锁问题的代码演示:
1 package com.example.concurrency; 2 3 public class MyLock { 4 public static Object a = new Object(); 5 public static Object b = new Object(); 6 }
1 package com.example.concurrency; 2 3 public class Boy extends Thread { 4 @Override 5 public void run() { 6 synchronized (MyLock.a) { 7 System.out.println("boy拿到了a"); 8 synchronized (MyLock.b) { 9 System.out.println("boy拿到了b"); 10 System.out.println("boy获取了全部资源"); 11 } 12 } 13 } 14 }
1 package com.example.concurrency; 2 3 public class Girl extends Thread { 4 @Override 5 public void run() { 6 7 synchronized (MyLock.b) { 8 System.out.println("girl拿到了b"); 9 synchronized (MyLock.a) { 10 System.out.println("girl拿到了a"); 11 System.out.println("girl获取了全部资源"); 12 } 13 } 14 } 15 }
1 package com.example.concurrency; 2 3 public class TestEat { 4 public static void main(String[] args) { 5 // TODO Auto-generated method stub 6 Boy boy = new Boy(); 7 Girl girl = new Girl(); 8 9 boy.start(); 10 girl.start(); 11 } 12 }
执行TestEat中的main方法,有可能出现死锁。boy线程和girl线程,都在等待对方释放锁,但由于自己没有释放锁,对方也不能释放锁。
2.2使用线程通信,避免出现死锁
1 package com.example.concurrency; 2 3 public class BankCard { 4 private double money; 5 6 private boolean flag = false;//标记当前是否有钱,true有钱,false没钱 7 8 public double getMoney() { 9 return money; 10 } 11 12 public void setMoney(double money) { 13 this.money = money; 14 } 15 16 public synchronized void add(double money) throws Exception { 17 while (flag) { 18 this.wait(); 19 // System.out.println("现在想存钱,但是账户里有钱,等待先取走"); 20 } 21 22 this.money += money; 23 this.flag = true; 24 System.out.println("存入100元,账户余额:" + this.money); 25 this.notifyAll();// 通知其他线程,可以获取锁 26 } 27 28 public synchronized void sub(double money) throws Exception { 29 while (!flag) { 30 this.wait(); 31 // System.out.println("现在想取钱,但是账户没有钱,等待先存入"); 32 } 33 34 this.money -= money; 35 this.flag = false; 36 System.out.println("取出100元,账户余额:" + this.money); 37 this.notifyAll(); 38 } 39 }
1 package com.example.concurrency; 2 3 public class TestBankCard { 4 5 public static void main(String[] args) { 6 // TODO Auto-generated method stub 7 BankCard card = new BankCard(); 8 9 Runnable add = new Runnable() { 10 11 @Override 12 public void run() { 13 for (int i = 0; i < 10; i++) { 14 try { 15 card.add(100); 16 } catch (Exception e) { 17 // TODO Auto-generated catch block 18 e.printStackTrace(); 19 } 20 } 21 22 } 23 }; 24 25 Runnable sub = new Runnable() { 26 @Override 27 public void run() { 28 // TODO Auto-generated method stub 29 for (int i = 0; i < 10; i++) { 30 try { 31 card.sub(100); 32 } catch (Exception e) { 33 // TODO Auto-generated catch block 34 e.printStackTrace(); 35 } 36 } 37 } 38 }; 39 40 new Thread(add).start(); 41 new Thread(sub).start(); 42 } 43 }
执行结果如下:
1 存入100元,账户余额:100.0 2 取出100元,账户余额:0.0 3 存入100元,账户余额:100.0 4 取出100元,账户余额:0.0 5 存入100元,账户余额:100.0 6 取出100元,账户余额:0.0 7 存入100元,账户余额:100.0 8 取出100元,账户余额:0.0 9 存入100元,账户余额:100.0 10 取出100元,账户余额:0.0 11 存入100元,账户余额:100.0 12 取出100元,账户余额:0.0 13 存入100元,账户余额:100.0 14 取出100元,账户余额:0.0 15 存入100元,账户余额:100.0 16 取出100元,账户余额:0.0 17 存入100元,账户余额:100.0 18 取出100元,账户余额:0.0 19 存入100元,账户余额:100.0 20 取出100元,账户余额:0.0
使用wait()和notifyAll()实现了线程的通信,完成“一存一取,交叉运行”的效果。
3.生产者消费者问题
1 package com.example.concurrency; 2 3 public class Bread { 4 private Integer id; 5 private String name; 6 7 public Integer getId() { 8 return id; 9 } 10 public void setId(Integer id) { 11 this.id = id; 12 } 13 public String getName() { 14 return name; 15 } 16 public void setName(String name) { 17 this.name = name; 18 } 19 20 public Bread() { 21 super(); 22 // TODO Auto-generated constructor stub 23 } 24 public Bread(Integer id, String name) { 25 super(); 26 this.id = id; 27 this.name = name; 28 } 29 }
1 package com.example.concurrency; 2 3 public class BreadFactory { 4 private Bread[] factory = new Bread[10]; 5 6 int index = 0; 7 8 public synchronized void product(Bread b) { 9 while (index >= 5) { 10 try { 11 this.wait(); 12 } catch (InterruptedException e) { 13 // TODO Auto-generated catch block 14 e.printStackTrace(); 15 } 16 } 17 18 factory[index] = b; 19 index++; 20 System.out.println(Thread.currentThread().getName() + "生产了1个面包,库存还有" + index + "个"); 21 this.notifyAll(); 22 } 23 24 public synchronized Bread sale() { 25 while (index <= 0) { 26 try { 27 this.wait(); 28 } catch (InterruptedException e) { 29 // TODO Auto-generated catch block 30 e.printStackTrace(); 31 } 32 } 33 index--; 34 Bread b = factory[index]; 35 factory[index] = null; 36 System.out.println(Thread.currentThread().getName() + "销售了1个面包,库存还有" + index + "个"); 37 this.notifyAll(); 38 return b; 39 } 40 }
1 package com.example.concurrency; 2 3 public class BreadProd implements Runnable { 4 private BreadFactory factory = new BreadFactory(); 5 6 private String name; 7 8 public String getName() { 9 return name; 10 } 11 12 public void setName(String name) { 13 this.name = name; 14 } 15 16 public BreadProd() { 17 super(); 18 // TODO Auto-generated constructor stub 19 } 20 21 public BreadProd(BreadFactory factory, String name) { 22 super(); 23 this.factory = factory; 24 this.name = name; 25 } 26 27 @Override 28 public void run() { 29 // TODO Auto-generated method stub 30 for (int i = 0; i < 20; i++) { 31 factory.product(new Bread(i, this.name)); 32 } 33 } 34 }
1 package com.example.concurrency; 2 3 public class BreadSaler implements Runnable { 4 private BreadFactory factory = new BreadFactory(); 5 6 private String name; 7 8 public String getName() { 9 return name; 10 } 11 12 public void setName(String name) { 13 this.name = name; 14 } 15 16 public BreadSaler() { 17 super(); 18 // TODO Auto-generated constructor stub 19 } 20 21 public BreadSaler(BreadFactory factory, String name) { 22 super(); 23 this.factory = factory; 24 this.name = name; 25 } 26 27 @Override 28 public void run() { 29 // TODO Auto-generated method stub 30 for (int i = 0; i < 20; i++) { 31 factory.sale(); 32 } 33 } 34 }
1 package com.example.concurrency; 2 3 public class BreadTest { 4 public static void main(String[] args) { 5 BreadFactory factory = new BreadFactory(); 6 BreadProd prod = new BreadProd(factory, "顾客"); 7 BreadSaler saler = new BreadSaler(factory, "面包店"); 8 9 new Thread(prod).start(); 10 new Thread(saler).start(); 11 } 12 }
执行结果可能为:
1 Thread-0生产了1个面包,库存还有1个 2 Thread-0生产了1个面包,库存还有2个 3 Thread-0生产了1个面包,库存还有3个 4 Thread-0生产了1个面包,库存还有4个 5 Thread-0生产了1个面包,库存还有5个 6 Thread-1销售了1个面包,库存还有4个 7 Thread-1销售了1个面包,库存还有3个 8 Thread-1销售了1个面包,库存还有2个 9 Thread-1销售了1个面包,库存还有1个 10 Thread-1销售了1个面包,库存还有0个 11 Thread-0生产了1个面包,库存还有1个 12 Thread-0生产了1个面包,库存还有2个 13 Thread-0生产了1个面包,库存还有3个 14 Thread-0生产了1个面包,库存还有4个 15 Thread-0生产了1个面包,库存还有5个 16 Thread-1销售了1个面包,库存还有4个 17 Thread-1销售了1个面包,库存还有3个 18 Thread-1销售了1个面包,库存还有2个 19 Thread-1销售了1个面包,库存还有1个 20 Thread-1销售了1个面包,库存还有0个 21 Thread-0生产了1个面包,库存还有1个 22 Thread-0生产了1个面包,库存还有2个 23 Thread-0生产了1个面包,库存还有3个 24 Thread-0生产了1个面包,库存还有4个 25 Thread-0生产了1个面包,库存还有5个 26 Thread-1销售了1个面包,库存还有4个 27 Thread-1销售了1个面包,库存还有3个 28 Thread-1销售了1个面包,库存还有2个 29 Thread-1销售了1个面包,库存还有1个 30 Thread-1销售了1个面包,库存还有0个 31 Thread-0生产了1个面包,库存还有1个 32 Thread-0生产了1个面包,库存还有2个 33 Thread-0生产了1个面包,库存还有3个 34 Thread-0生产了1个面包,库存还有4个 35 Thread-0生产了1个面包,库存还有5个 36 Thread-1销售了1个面包,库存还有4个 37 Thread-1销售了1个面包,库存还有3个 38 Thread-1销售了1个面包,库存还有2个 39 Thread-1销售了1个面包,库存还有1个 40 Thread-1销售了1个面包,库存还有0个
生产的产品数量最多为5个,消费后的产品数量不会小于0个。