一、安全问题
假如一个电影院有一部电影100张票:
- 只有一个窗口卖1-100的票(单线程):不会出现线程问题。
- 有三个窗口卖不同范围的票(多线程):不会出现线程问题,没有访问共享数据,不会产生问题。
- 有三个窗口卖的都是1-100的票(多线程):会出现线程访问共享数据–>线程安全问题。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48
| //子类!!!!! public class RunnableImpl implements Runnable { //定义一个多线程共享的票源: private int ticket=100; //设置线程任务:卖票 @Override public void run() { while(true) { //先判断票是否存在 if(ticket>0) { //提高安全问题出现的概率,让程序睡眠 try { Thread.sleep(10); } catch (InterruptedException e) { // TODO 自动生成的 catch 块 e.printStackTrace(); } //票存在,卖票 ticket--; System.out.println(Thread.currentThread().getName()+"-->正在卖第"+ticket+"张票"); ticket--; } } } }
//测试类!!!!!!!!!!!!!!!!! /* 模拟卖票案例 创建3个线程,同时开始,对共享的票进行出售 */ public class ShiXian { public static void main(String[] args) { //创建Runnable接口的实现类对象 RunnableImpl run=new RunnableImpl(); //三个线程争夺CPU Thread t0=new Thread(run);//抢夺到CPU的执行权-进入到run方法之后执行if语句就会睡眠,失去cpu的执行权 Thread t1=new Thread(run); Thread t2=new Thread(run); //调用start()方法开启多线程 t0.start(); t1.start(); t2.start(); } }
|
代码结果如下:
Thread-1–>正在卖第100张票
Thread-2–>正在卖第100张票
Thread-0–>正在卖第100张票
…
Thread-1–>正在卖第3张票
Thread-0–>正在卖第3张票
Thread-2–>正在卖第1张票
Thread-1–>正在卖第0张票
Thread-0–>正在卖第1张票
(出现了同时卖出去票的问题!!!!!!)
二、线程同步(解决方案)
- 同步代码块
- 同步方法
- 锁机制(Lock接口)
1 2 3
| synchronized(同步锁){ 需要同步操作的代码 }
|
注意:
- 锁对象可以是任意的对象
- 保证多个线程使用的是同一个锁对象
- 锁对象的作用:
把同步代码块锁住:只让一个线程在同步代码块中执行。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| public class RunnableImpl implements Runnable { //定义一个多线程共享的票源: private int ticket=100; //创建一个锁对象 Object obj=new Object(); //设置线程任务:卖票 @Override public void run() { while(true) {
synchronized (obj) //synchronized(锁对象) { //还是原来的那些if语句和sleep语句 }
} } }
|
代码结果如下:
Thread-0–>正在卖第100张票
Thread-2–>正在卖第99张票
Thread-2–>正在卖第98张票
…(省略)
Thread-2–>正在卖第4张票
Thread-2–>正在卖第3张票
Thread-2–>正在卖第2张票
Thread-2–>正在卖第1张票
- 访问了共享数据的代码–>方法
- 方法上+synchronized修饰符
格式:定义方法的格式
1
| 修饰符 synchronized 返回值类型 方法名(参数列表){}
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
| package duoxianchegn;
public class RunnableImpl implements Runnable { //定义一个多线程共享的票源: private int ticket=100; //设置线程任务:卖票 @Override public void run() { while(true) { payTicket(); //调用这个方法! } } //提出来一个payTicket()方法用于把共享的代码锁起来 public synchronized void payTicket() { //先判断票是否存在 if(ticket>0) { //提高安全问题出现的概率,让程序睡眠 try { Thread.sleep(10); } catch (InterruptedException e) { // TODO 自动生成的 catch 块 e.printStackTrace(); } //票存在,卖票 ticket--; System.out.println(Thread.currentThread().getName()+"-->正在卖第"+ticket+"张票"); ticket--; } } }
|
代码结果如下:
Thread-0–>正在卖第100张票
Thread-2–>正在卖第99张票
Thread-2–>正在卖第98张票
…(省略)
Thread-2–>正在卖第4张票
Thread-2–>正在卖第3张票
Thread-2–>正在卖第2张票
Thread-2–>正在卖第1张票
- 在成员位置–>ReentrantLock对象
- 在可能会出现安全问题的代码前调用Lock方法获取锁
- 在可能会出现安全问题的代码后调用Lock方法释放锁
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; public class RunnableImpl implements Runnable { //定义一个多线程共享的票源: private int ticket=100; //定义一个对象 Lock l=new ReentrantLock(); //设置线程任务:卖票 @Override public void run() { while(true) { l.lock();//加一个锁 //先判断票是否存在 if(ticket>0) { //提高安全问题出现的概率,让程序睡眠 try { Thread.sleep(10); } catch (InterruptedException e) { // TODO 自动生成的 catch 块 e.printStackTrace(); } //票存在,卖票 ticket--; System.out.println(Thread.currentThread().getName()+"-->正在卖第"+ticket+"张票"); ticket--; } l.unlock();//释放一个锁 } } }
|