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。
1.5使用Lock进行线程同步
1 public class demo04 { 2 //演示lock的使用 3 public static void main(String[] args) throws InterruptedException { 4 demo04 d04 = new demo04(); 5 MyList myList = d04.new MyList(); 6 7 Runnable runnable1 = new Runnable() { 8 @Override 9 public void run() { 10 myList.add("hello"); 11 } 12 }; 13 14 Runnable runnable2 = new Runnable() { 15 @Override 16 public void run() { 17 myList.add("world"); 18 } 19 }; 20 21 Thread thread1 = new Thread(runnable1,"线程1"); 22 Thread thread2 = new Thread(runnable2,"线程2"); 23 24 thread1.start(); 25 thread2.start(); 26 27 thread1.join(); 28 thread1.join(); 29 30 System.out.println(Arrays.toString(myList.getStr())); 31 } 32 33 34 class MyList { 35 private Lock lock = new ReentrantLock(); 36 private String[] str = new String[] {"A","B","","",""}; 37 private int count = 2; 38 39 public String[] getStr() { 40 return str; 41 } 42 43 public void setStr(String[] str) { 44 this.str = str; 45 } 46 47 public int getCount() { 48 return count; 49 } 50 51 public void setCount(int count) { 52 this.count = count; 53 } 54 55 public void add(String value) { 56 lock.lock(); 57 try { 58 str[count]=value; 59 count++; 60 System.out.println(Thread.currentThread().getName()+"添加了数据:"+value); 61 } 62 finally { 63 lock.unlock(); 64 } 65 } 66 } 67 }
可能的执行结果:
线程1添加了数据:hello
线程2添加了数据:world
[A, B, hello, world, ]
1.6使用读写锁代码示例:
1 public class demo05 { 2 public static void main(String[] args) { 3 demo05 d05 = new demo05(); 4 MyClass myClass = d05.new MyClass(); 5 Runnable runnable1 = new Runnable() { 6 7 @Override 8 public void run() { 9 // TODO Auto-generated method stub 10 try { 11 myClass.SetValue(1); 12 } catch (Exception e) { 13 // TODO Auto-generated catch block 14 e.printStackTrace(); 15 } 16 } 17 }; 18 19 Runnable runnable2 = new Runnable() { 20 21 @Override 22 public void run() { 23 // TODO Auto-generated method stub 24 try { 25 myClass.getValue(); 26 } catch (Exception e) { 27 // TODO Auto-generated catch block 28 e.printStackTrace(); 29 } 30 } 31 }; 32 33 ExecutorService executorService = Executors.newFixedThreadPool(20); 34 long startTime = System.currentTimeMillis(); 35 36 for(int i=0;i<2;i++) { 37 executorService.submit(runnable1); 38 } 39 for(int i=0;i<18;i++) { 40 executorService.submit(runnable2); 41 } 42 executorService.shutdown(); 43 while(!executorService.isTerminated()) { 44 45 } 46 System.out.println("共用时间:"+(System.currentTimeMillis()-startTime)+"毫秒"); 47 } 48 49 50 class MyClass{ 51 ReentrantReadWriteLock rwlLock = new ReentrantReadWriteLock(); 52 ReadLock readLock = rwlLock.readLock(); 53 WriteLock writeLock = rwlLock.writeLock(); 54 55 private int value; 56 57 public int getValue() throws Exception { 58 readLock.lock(); 59 try { 60 Thread.sleep(1000); 61 return value; 62 } finally { 63 // TODO: handle finally clause 64 readLock.unlock(); 65 } 66 } 67 68 public void SetValue(int value) throws Exception { 69 writeLock.lock(); 70 try { 71 Thread.sleep(1000); 72 this.value=value; 73 } finally { 74 // TODO: handle finally clause 75 writeLock.unlock(); 76 } 77 } 78 } 79 }
运行结果:
共用时间:3025毫秒
可以看到,使用读写锁耗时大概3秒左右。
而如果使用的是ReentrantLock,大概需要20秒左右时间。
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个。