即上次的线程问题,这次来写个例子
张三吃包子问题。我们来看代码
/** * 包子类 */ public class Package { //包子个数 private int p; //初始化包子个数为100 public Package(){ p = 100; } public int getP() { return p; } public void setP(int p) { this.p = p; } //同步方法 锁 public synchronized void t(Person person ){ System.out.println(person.getName1() + "吃了" + this.getP()+ "个包子"); this.setP(this.getP() - 1); } }
public class Person extends Thread { public String getName1() { return name1; } public void setName1(String name) { this.name1 = name; } private String name1; private Package p= null; public Person(String name,Package p){ this.name1 = name; this.p = p; } @Override public void run(){ try{ while(true){ p.t(this); Thread.sleep(1); if(p.getP()<=0){ System.exit(0); } //同步块,监视器,两个线程都能访问的同一个 // synchronized (p) { // System.out.println(this.name + "吃了" + p.getP() + "个包子"); // p.setP(p.getP() - 1); // Thread.sleep(1); // } } }catch (Exception e){ e.printStackTrace(); } } }
public static void main(String[] args){ Package P = new Package(); Person p1 = new Person("张三",P); Person p2 = new Person("李四",P); p1.start(); p2.start(); }
运行结果:
其实,代码初步并不是这样,由此例子引出下面几个问题
1.线程争用
2.线程争用问题解决 用 sychronized同步块或同步方法
3.线程同步 sychronized同步块 加锁
4.同步方法 有锁
5.线程死锁
6.线程协作
线程争用:
原始的代码是这样:
public void run(){ try{ while(true){ if(p.getP()<=0){ System.exit(0); } System.out.println(this.name1 + "吃了" + p.getP() + "个包子"); p.setP(p.getP() - 1); } }catch (Exception e){ e.printStackTrace(); } }
运行结果是这样:
为什么会出现这样的情况呢?
这是因为吃包子和包子数减1,是两个不可分割的操作。
当张三的线程吃包子后,还没数量减1,这个线程时间就用完了,到李四线程,此时包子数量还是100,李四又继续吃包子,包子数量减1后,这样李四线程时间刚好用完,到张三吃包子,张三就从99开始,造成这个问题原因是什么?是因为每个线程时间不确定,两个不能分割的操作不能在同一段时间内完后,就出现了资源争用问题。
如何解决这个问题呢?
线程同步
解决这个问题,必须将不能分割的两个操作同步在一起,叫同步块,用sychronized来实现线程同步
public void run(){ try{ while(true){ if(p.getP()<=0){ System.exit(0); } //同步块,监视器,两个线程都能访问的同一个 synchronized (p) { System.out.println(this.name1 + "吃了" + p.getP() + "个包子"); p.setP(p.getP() - 1); Thread.sleep(1); } } }catch (Exception e){ e.printStackTrace(); } }
将两个不能分割的操作同步到一起。运行结果
这样就解决了不同线程访问同一资源而导致的线程争用冲突
synchronized解析:
这里我们加锁的对象为包子,因为包子是两个线程共同访问的对象,同一个资源。
同步方法
除了这个的同步块,我们还可以实现同步方法。
代码改造:
因为包子是共同访问的对象,所以这个同步方法写在包子里面,包子就是锁
//同步方法 锁 public synchronized void t(Person person ){ System.out.println(person.getName1() + "吃了" + this.getP()+ "个包子"); this.setP(this.getP() - 1); }
调用这样调
public void run(){ try{ while(true){ p.t(this); Thread.sleep(1); if(p.getP()<=0){ System.exit(0); } } } }catch (Exception e){ e.printStackTrace(); } }
运行结果:
线程死锁
代码详细:
package cn.edu.xatu.deadlock; public class T1 { public static String k = "刀"; public static String f = "叉"; }
package cn.edu.xatu.deadlock; public class P implements Runnable { @Override public void run() { try { if (Thread.currentThread().getName() == "张三") { synchronized (T1.k) { synchronized (T1.f) { } } } else { synchronized (T1.f) { synchronized (T1.k) { } } } }catch (Exception e){ e.printStackTrace(); } } }
运行结果:
死锁:所谓死锁是指多个线程因竞争资源而造成的一种僵局(互相等待),若无外力作用,这些进程都将无法向前推进。上面的例子张三先锁刀再锁叉,李四先锁叉再锁刀,当张三拿到刀等待拿叉的时候,叉已经被李四拿走了,而李四拿了叉再拿刀的时候,到已经被张三拿走了。然后张三等待李四,李四等待张三,互相等待造成死锁,虚拟机也没有停。
死锁深度解析:https://blog.csdn.net/alex123980/article/details/52318016
线程协作:
package cooperation; public class Producer implements Runnable { private String pname; private Package P1; public Producer(String name,Package pp){ this.P1 = pp; this.pname = name; } public String getPname() { return pname; } public void setPname(String pname) { this.pname = pname; } public Package getP() { return P1; } public void setP(Package p) { P1 = p; } @Override public void run() { try { int count = 0; while (true) { synchronized (P1) { if (P1.getP() <= 0) { P1.setP(100); count++; System.out.println(this.pname + "做的100个热腾腾的包子出锅喽!"); System.out.println("第"+ count +"锅包子出锅了"); }else { P1.wait();//此线程释放锁,在线程休息室等待。wait方法必须持有锁 P1.notify();//唤醒其他处于等待室里面的线程 } } } }catch (Exception e){ e.printStackTrace(); } } }
package cooperation; public class Consumer implements Runnable { private String Cname; public String getCname() { return Cname; } public void setCname(String cname) { Cname = cname; } public Package getP() { return P1; } public void setP(Package p) { P1 = p; } private Package P1; public Consumer (String name,Package PP){ this.P1 =PP; this.Cname = name; } @Override public void run() { try { while (true) { synchronized (P1) { if (P1.getP()>0) { System.out.println(this.Cname + "吃了第" + P1.getP() + "个包子"); P1.setP(P1.getP() - 1); } else { P1.notify(); P1.wait(); } } } }catch (Exception e){ e.printStackTrace(); } } }
运行结果:
最经典的生产者-消费者模型:当队列满时,生产者需要等待队列有空间才能继续往里面放入商品,而在等待的期间内,生产者必须释放对临界资源(即队列)的占用权。因为生产者如果不释放对临界资源的占用权,那么消费者就无法消费队列中的商品,就不会让队列有空间,那么生产者就会一直无限等待下去。因此,一般情况下,当队列满时,会让生产者交出对临界资源的占用权,并进入挂起状态。然后等待消费者消费了商品,然后消费者通知生产者队列有空间了。同样地,当队列空时,消费者也必须等待,等待生产者通知它队列中有商品了。这种互相通信的过程就是线程间的协作。
线程协作使用的方法。
获取了锁了的线程A的wait()方法。线程A进入等待队列释放锁,然后,线程B 获取锁。
获取了锁的B执行notify方法,释放掉在等待队列当中的A,A想进入下一个wait操作,但B持有者锁。B调用wait释放掉锁,进入等待 队列,此时线程A获取锁,再执行wait方法,以此类推,尽可能减少资源占用。
同wait 方法一样,若要执行notify 方法,线程也必须持有要调用的实例的锁(这是规则) 。