用代码来模拟铁路售票系统,实现通过四个售票点发售某日某次列车的100张车票,一个售票点用一个线程表示
第一种方法:通过继承Thread类的方法创建线程
package com.Gary1; public class TicketThread extends Thread{ //设置有100张票 private static int count = 100; public TicketThread(String name) { super(name); } @Override public void run() { while(true) { //当票大于0张的时候卖票 if(count>0) { System.out.println(getName() + "卖出第" + count + "卖票"); count--; }else { break; } } } }
package com.Gary1; public class GaryTest { public static void main(String[] args) { TicketThread t1 = new TicketThread("售票点1"); TicketThread t2 = new TicketThread("售票点2"); TicketThread t3 = new TicketThread("售票点3"); TicketThread t4 = new TicketThread("售票点4"); t1.start(); t2.start(); t3.start(); t4.start(); } }
可以看出,当把count设置成static之后,线程同步时还是会造成count票的数量不安全
第二种方法:通过实现Runnable接口(实时共享数据,不需将count设置成静态)
package com.Gary1; public class TicketRunnable implements Runnable{ //设置有100张票 count不需要设置成static private int count = 100; public void run() { while(true) { //当票大于0张的时候卖票 if(count>0) { System.out.println(Thread.currentThread().getName() + "卖出第" + count + "卖票"); count--; }else { break; } } } }
package com.Gary1; public class GaryTest { public static void main(String[] args) { TicketRunnable t = new TicketRunnable(); Thread t1 = new Thread(t,"售票点1"); Thread t2 = new Thread(t,"售票点1"); Thread t3 = new Thread(t,"售票点1"); Thread t4 = new Thread(t,"售票点1"); t1.start(); t2.start(); t3.start(); t4.start(); } }
发现此时不管用继承Thread类或者实现Runnable接口,都无法保证线程中变量的安全!!!
(多个线程同时要修改一个变量的时候,引起冲突)
解决线程不安全方法
a)线程安全问题解决
synchronized(对象){}//锁住某个对象,如果这个对象已经被锁定,那么等待。
public void run() { while(true) { synchronized(lock) {//第一个线程来的时候会锁上,并拿走钥匙,第二个线程来的时候,发现被锁上,等待 //当票大于0张的时候卖票 if(count>0) { System.out.println(Thread.currentThread().getName() + "卖出第" + count + "卖票"); count--; }else { break; } }//执行完,归还钥匙 } }
package com.Gary1; public class TicketRunnable implements Runnable{ //设置有100张票 count不需要设置成static private int count = 100; private Object lock = new Object(); public void run() { while(true) { synchronized(lock) {//第一个线程来的时候会锁上,并拿走钥匙,第二个线程来的时候,发现被锁上,等待 //当票大于0张的时候卖票 if(count>0) { System.out.println(Thread.currentThread().getName() + "卖出第" + count + "卖票"); count--; }else { break; } }//执行完,归还钥匙 } } }
通过继承Thread类解决线程不安全方法
package com.Gary1; public class TicketThread extends Thread{ //设置有100张票 private static int count = 100; private static Object lock = new Object(); public TicketThread(String name) { super(name); } @Override public void run() { synchronized(lock){ while(true) { //当票大于0张的时候卖票 if(count>0) { System.out.println(getName() + "卖出第" + count + "卖票"); count--; }else { break; } } } } }
优化,使用Thread.sleep()解决抢占式优先问题,避免同一个售票点一直抢占着线程的CPU
package com.Gary1; public class TicketRunnable implements Runnable{ //设置有100张票 count不需要设置成static private int count = 100; private Object lock = new Object(); public void run() { while(true) { synchronized(lock) {//第一个线程来的时候会锁上,并拿走钥匙,第二个线程来的时候,发现被锁上,等待 //当票大于0张的时候卖票 if(count>0) { System.out.println(Thread.currentThread().getName() + "卖出第" + count + "卖票"); count--; }else { break; } }//执行完,归还钥匙 try { Thread.sleep(100); }catch(InterruptedException e) { e.printStackTrace(); } } } }
b)出现线程安全问题的地方,要锁同一个对象(可以是当前对象,也可以单独创建一个对象)
c)锁住某个对象,如果这个对象已经被锁定,那么停止当前线程的执行,一直等待,一直等到对象被解锁。
(保证同一个时间,只有一个线程在使用这个对象,)
d)创建同步方法
同步方法锁的是哪个对象呢?锁定的是当前对象this
public synchronized void sellTicket() { //synchronized(lock) {//第一个线程来的时候会锁上,并拿走钥匙,第二个线程来的时候,发现被锁上,等待 //当票大于0张的时候卖票 if(count>0) { System.out.println(Thread.currentThread().getName() + "卖出第" + count + "卖票"); count--; } }//执行完,归还钥匙
package com.Gary1; public class TicketRunnable implements Runnable{ //设置有100张票 count不需要设置成static private int count = 100; private Object lock = new Object(); public void run() { while(count>0) { sellTicket(); try { Thread.sleep(100); }catch(InterruptedException e) { e.printStackTrace(); } } } public synchronized void sellTicket() { //synchronized(lock) {//第一个线程来的时候会锁上,并拿走钥匙,第二个线程来的时候,发现被锁上,等待 //当票大于0张的时候卖票 if(count>0) { System.out.println(Thread.currentThread().getName() + "卖出第" + count + "卖票"); count--; } }//执行完,归还钥匙 }
题外话:因为StringBuffer类下的方法append()添加字符串被加了synchronized锁,所以线程安全!!!同理可查看Vector集合和ArrayList集合下的add()方法,可以发现Vector集合下的add()方法线程安全,ArrayList集合下的add()方法线程不安全。
线程安全的类
安全: StringBuffer Vector
不安全:StringBuilder ArrayList
@Override public synchronized StringBuffer append(String str) { toStringCache = null; super.append(str); return this; }
同步锁的第二种使用方式
a)创建锁对象 ReentrantLock lock = new ReentrantLock();
b)加锁和解锁使用tryfinally、lock.lock()、lock.unlock()
lock.lock(); //加锁 try { if(count>0) { System.out.println(Thread.currentThread().getName() + "卖出第" + count + "卖票"); count--; } }finally { //不管上边代码时不时出现异常,都能保证解锁 lock.unlock(); //解锁 }
package com.Gary1; import java.util.concurrent.locks.ReentrantLock; public class TicketRunnable implements Runnable{ //设置有100张票 count不需要设置成static private int count = 100; private ReentrantLock lock= new ReentrantLock(); public void run() { while(count>0) { lock.lock(); //加锁 try { if(count>0) { System.out.println(Thread.currentThread().getName() + "卖出第" + count + "卖票"); count--; } }finally { //不管上边代码时不时出现异常,都能保证解锁 lock.unlock(); //解锁 } try { Thread.sleep(100); }catch(InterruptedException e) { e.printStackTrace(); } } } }
买票问题升级(两种卖票方式,一种通过电影院窗口,一种通过手机App)
主要目的:保证两种方式使用的是同一把锁
package com.Gary1; //管理不同方式,单都是卖同一种资源 public class TicketMng { //表示票的剩余数量 public static int count = 100; }
package com.Gary1; public class AppThread implements Runnable{ //保证使用同一个lock锁 public AppThread(Object lock) { this.lock = lock; } private Object lock; @Override public void run() { while(TicketMng.count>0) { synchronized(lock) { if(TicketMng.count>0) { System.out.println(Thread.currentThread().getName()+"售出第"+TicketMng.count+"票"); TicketMng.count--; } } try { Thread.sleep(10); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } }
package com.Gary1; public class WindowThread implements Runnable{ //保证使用同一个lock锁 public WindowThread(Object lock) { this.lock = lock; } private Object lock; @Override public void run() { while(TicketMng.count>0) { synchronized(lock) { if(TicketMng.count>0) { System.out.println(Thread.currentThread().getName()+"售出第"+TicketMng.count+"票"); TicketMng.count--; } } try { Thread.sleep(10); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } }
package com.Gary1; public class GaryTest2 { public static void main(String[] args) { //windowThread和appThread共用同意一把锁 Object lock = new Object(); WindowThread windowThread = new WindowThread(lock); AppThread appThread = new AppThread(lock); new Thread(windowThread,"窗口").start(); new Thread(appThread,"手机APP").start(); } }