卖票案例出席线了线程安全问题
卖出了不存在和重复的票
解决1:使用同步代码块
格式:
synchronized(){
可能会出现线程安全问题的代码(访问了共享数据的代码)
}
注意:
1.通过代码块中的锁对象,可以使用任意的对象
2.但是必须保证多个线程使用的锁对象是同一个
3锁对象作用
把同步代码块锁住,只让一个线程在同步代码块中执行
【同步技术原理】
以卖票案例中的3个线程为例:
使用了一个锁对象,这个锁对象叫同步锁,也叫对象锁或对象监视器
3个线程一起抢夺CPU的执行权,谁抢到了谁执行run方法,进行卖票
t0抢到了CPU的执行权,执行run方法,遇到synchronized代码块
这时t0会检查synchronized代码块是否有锁对象
发现有,【就会获取到锁对象,进入到同步中执行】
t1抢到了CPU的执行权,执行run方法,遇到synchronized代码块
这时t1会检查synchronized代码块是否有锁对象
【发现没有,t1,就会进入到阻塞状态,会一直等待t0线程归还锁对象,一直到t0线程执行完同步中的代码,会把锁对象归还给同步代码块,t1才能继续获取到锁对象进入到同步中执行】
t2同理。
由此我们可以得出:【同步中的线程,没有执行完毕不会释放锁,同步外的线程没有锁进不去】
**【同步保证了只能有一个线程在同步中执行共享数据,保证了安全】**但由于程序频繁的判断锁,获取锁,释放锁,程序的效率会降低。
代码演示:
public class RunnableImpl implements Runnable{
//定义一个多线程共享的票源
private int ticket = 100;
//创建一个锁对象
Object lock = new Object();
//设置线程任务:卖票
@Override
public void run() {
//使用死循环,让卖票偶重复执行
while (true) {
synchronized (lock) {
//先判断票是否存在
if(ticket > 0) {
//票存在,卖票 ticket--
//提高安全问题出现的概率,让程序睡眠
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
//票存在,卖票 tichet--
String name = Thread.currentThread().getName();
System.out.println(name + "正在卖第:" + ticket-- +"张票。");
}
}
}
}
}
同步方法:使用synchronized修饰的方法,就叫做同步方法,保证A线程执行该方法的时候,其他线程只能在方法外等着。
格式:
使用步骤:
1.把访问共享数据的代码拿出来放到一个方法中
2.在方法上添加synchronized修饰符
格式:定义方法的格式
修饰符 synchronized 发挥值类型 方法名(参数列表) {
可能出现线程安全的代码(访问了共享数据的代码)
}
同步方法也会把方法内部的代码锁住
只让一个线程执行
同步方法的锁对象是谁,就是实现类对象 new RunnableImpl()
也就是this
代码演示:
public class RunnableImpl implements Runnable{
//定义一个多线程共享的票源
private int ticket = 100;
//设置线程任务:卖票
@Override
public void run() {
while (ticket > 0) {
payTicket();
}
}
/*
定义一个同步方法
同步方法也会把方法内部的代码锁住
只让一个线程执行
同步方法的锁对象是谁,就是实现类对象 new RunnableImpl()
也就是this
*/
public synchronized void payTicket() {
//先判断票是否存在
if(ticket > 0) {
//票存在,卖票 ticket--
//提高安全问题出现的概率,让程序睡眠
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+ "-->正在,卖第"+ ticket + "张票");
ticket--;
}
}
}
java.util.concurrent.locks.Lock接口
Lock 实现提供了比synchronized方法和语句可获得的更广泛的锁定操作
Lock接口中的方法:
public void lock() //:加同步锁。
public void unlock() //:释放同步锁。
【使用步骤:】
1.在成员位置创建一个ReentrantLock对象
2.在可能会出现安全问题的代码前调用Lock接口中方法lock获取锁
3.在可能会出现安全问题的代码后调用Lock接口中方法unlock释放锁
程序演示
public class RunnableImpl implements Runnable{
//定义一个多线程共享的票源
private int ticket = 100;
// 1.在成员位置创建一个ReentrantLock对象
Lock lock = new ReentrantLock();
//设置线程任务:卖票
@Override
public void run() {
while (true) {
//2.在可能会出现安全问题的代码前调用Lock接口中方法lock获取锁
lock.lock();
//先判断票是否存在
if(ticket > 0) {
//票存在,卖票 ticket--
//提高安全问题出现的概率,让程序睡眠
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+ "-->正在,卖第"+ ticket + "张票");
ticket--;
}
//3.在可能会出现安全问题的代码后调用Lock接口中方法unlock释放锁
lock.unlock();
}
}
}