学习内容:
1.线程同步:
如果同时有多个线程对某个元素进行操作,可能会产生与预期不符的效果、数据
public class Tickets implements Runnable{ static int tickets = 100; @Override public void run() { while(true) { if(tickets>0) { try { Thread.sleep(100); } catch (Exception e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName()+"出售第"+(tickets--)+"张票"); }else { return; } } } } } public class Synch { public static void main(String[] args) { Tickets t = new Tickets(); Thread app = new Thread(t);-- Thread online = new Thread(t); Thread scene = new Thread(t); app.start(); online.start(); scene.start(); } }
上面这段代码运行到最后,可能会产生同一个编号的票被出售多次,或者出现0号票、-1号票,出现这种情况的原因:
同时有多个线程对tickets--,假设当tickets等于1的时候,一号线程进行操作,if判断,符合条件,售出,数量减1,同时二号线程执行run,因为一号线程还没执行完,tickets--还没被执行,二号线程的if条件也是满足的,但如果一号线程完成了操作,此时的tickets已经为0,但是二号线程的if判断已经完成,所以会继续执行,出现了0号票,同理如果同时三后线程也在进行操作,则会出现-1号票。
解决方式:
A.synchronized锁
(1)新建一个Object,synchronized (Object)加锁,同时只允许一个线程进行操作。
public class Tickets implements Runnable{ static int tickets = 100; Object obj = new Object(); @Override public void run() { while(true) { synchronized (obj){//加锁,同时只允许一个线程获取当前对象 if(tickets>0) { try { Thread.sleep(100); } catch (Exception e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName()+"出售第"+(tickets--)+"张票"); }else { return; } } } } } public class Synch { public static void main(String[] args) { Tickets t = new Tickets(); Thread app = new Thread(t); Thread online = new Thread(t); Thread scene = new Thread(t); app.start(); online.start(); scene.start(); } }
(2)synchronized方法
注意,如果是普通方法,则synchronized的对象是当前对象,相当于this,如果是类方法,则synchronized的对象是当前类
public class Tickets implements Runnable{ static int tickets = 100; @Override public void run() { while(true) { if(method()) { }else { return; } } } public synchronized static boolean method() {//修饰静态方法,锁当前类,修饰对象方法,锁当前对象 boolean flag = true; if(tickets>0) { try { Thread.sleep(100); } catch (Exception e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName()+"出售第"+(tickets--)+"张票"); }else { flag = false; } return flag; }
}
B.Lock锁
区别:
1. Lock是一个接口,而synchronized是Java中的关键字,synchronized是内置的语言实现,Lock是代码层面的实现。
2. Lock可以选择性的获取锁,如果一段时间获取不到,可以放弃。synchronized不行,会一根筋一直获取下去。 借助Lock的这个特性,就能够规避死锁,synchronized必须通过谨慎和良好的设计,才能减少死锁的发生。
3. synchronized在发生异常和同步块结束的时候,会自动释放锁。而Lock必须手动释放, 所以如果忘记了释放锁,一样会造成死锁。
public class Locked implements Runnable{ static int tickets = 100; Object obj = new Object(); Lock lock = new ReentrantLock(); @Override public void run() { while(true) { lock.lock(); try { Thread.sleep(100); if(tickets>0) { System.out.println(Thread.currentThread().getName()+"出售第"+(tickets--)+"张票"); }else { return; } } catch (Exception e) { e.printStackTrace(); } finally { lock.unlock(); } } } } public class Locked implements Runnable{ static int tickets = 100; Object obj = new Object(); Lock lock = new ReentrantLock(); @Override public void run() { boolean locked = false; while(true) { lock.lock(); try { locked=lock.tryLock(1,TimeUnit.SECONDS);//一秒内获取不到就放弃 Thread.sleep(100); if(tickets>0) { System.out.println(Thread.currentThread().getName()+"出售第"+(tickets--)+"张票"); }else { return; } } catch (InterruptedException e1) { // TODO Auto-generated catch block e1.printStackTrace(); } finally { if(locked) { lock.unlock(); } } } } }
2.线程交互
通过wait方法以及notify、notifyAll来实现线程的休眠与唤醒,例如当某个条件满足时,当前线程等待,通过另一个线程的在来唤醒它。
下面这段代码可以实现“锁血”,英雄血量变为1时,使用减血方法的线程wait,当加血线程加血时,再把休眠的线程唤醒:
1 public class Hero { 2 public String name; 3 public float hp; 4 public int damage; 5 6 public synchronized void recover() { 7 hp = hp + 1; 8 System.out.printf("%s 回血1点,增加血后,%s的血量是%.0f%n", name, name, hp); 9 // 通知那些等待在this对象上的线程,可以醒过来了,如第17行,等待着的减血线程,苏醒过来 10 this.notify(); 11 } 12 13 public synchronized void hurt() { 14 if (hp == 1) { 15 try { 16 // 让占有this的减血线程,暂时释放对this的占有,并等待 17 this.wait(); 18 } catch (InterruptedException e) { 19 e.printStackTrace(); 20 } 21 } 22 hp = hp - 1; 23 System.out.printf("%s 减血1点,减少血后,%s的血量是%.0f%n", name, name, hp); 24 } 25 26 } 27 28 public class Synch { 29 30 public static void main(String[] args) { 31 final Hero gareen = new Hero(); 32 gareen.name = "盖伦"; 33 gareen.hp = 616; 34 Thread t1 = new Thread(){ 35 public void run(){ 36 while(true){ 37 gareen.hurt(); 38 try { 39 Thread.sleep(10); 40 } catch (InterruptedException e) { 41 e.printStackTrace(); 42 } 43 } 44 45 } 46 }; 47 t1.start(); 48 49 Thread t2 = new Thread(){ 50 public void run(){ 51 while(true){ 52 gareen.recover(); 53 try { 54 Thread.sleep(50); 55 } catch (InterruptedException e) { 56 e.printStackTrace(); 57 } 58 } 59 60 } 61 }; 62 t2.start(); 63 64 } 65 }