线程安全
一、线程安全
- 多线程访问共享数据会产生线程安全问题
- 线程安全问题实例:
如果有多个线程在同时运行,而这些线程可能同时运行这段代码。程序每次运行结果和单线程运行的结果是一样的,而且其他的变量的值也和预期的是一样的,就是线程安全的。
模拟电影院买票,多个窗口卖100张票(采用实现Runnable接口的方法)
模拟买票:
//模拟卖电影票
public class RunnableImpl implements Runnable{
//票数100张
private int ticket=100;
@Override
public void run() {
//判断票数是否大于0,如果大于0可以进行买票
while (true){
if(ticket>0){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"正在卖"+ticket+"张票");
ticket--;
}
}
}
}
测试类:
//创建接口的实现类对象
RunnableImpl run=new RunnableImpl();
//创建线程对象,传递接口的实现类对象
Thread t1=new Thread(run);
Thread t2=new Thread(run);
Thread t3=new Thread(run);
t1.start();
t2.start();
t3.start();
运行结果:
- 出现相同票数的现象,比如同时在卖第100张票
- 出现了不存在的票,第0张
几个窗口(线程)票数不能同步,就出现了线程安全问题。
二、如何解决线程安全问题
为了保证每个线程都能正常执行原子操作,Java引入了线程同步机制。
有三种方法可以完成同步操作:
1. 同步代码块
使用格式:
synchronized(同步锁){
可能会出现问题的代码块
}
同步锁:
- 通过锁对象可以使用任意对象
- 但必须保证多个线程使用的锁对象是同一个
- 锁对象的作用:把同步代码块锁住,只让一个线程在同步代码中执行
代码实现:
private int ticket=100;
//锁对象
Object obj=new Object();
@Override
public void run() {
//判断票数是否大于0,如果大于0可以进行买票
while (true){
//使用同步代码块
synchronized (obj){
if(ticket>0){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"正在卖"+ticket+"张票");
ticket--;
}
}
}
同步中的线程,没有执行完毕不会释放锁
同步外的线程没有锁进不去同步
因为频繁的判断锁,所以程序的效率会降低
2. 同步方法
- 同步方法:使用synchronized修饰的方法,就叫做同步方法,保证A线程执行该方法的时候,其他线程只能在方法外等着。使用格式如下:
public synchronized void method(){
可能会产生线程安全问题的代码
}
- 使用同步方法代码如下:
/*使用同步方法解决线程安全问题
* 1.把访问了共享数据的代码块抽取出来,放到一个方法中
* 2.方法加上synchronized修饰符*/
public class RunnableImpl implements Runnable{
private int ticket=100;
//同步方法
public synchronized void ticket(){
if(ticket>0){
System.out.println(Thread.currentThread().getName()+"正在卖"+ticket+"张票");
ticket--;
}
}
@Override
public void run() {
//判断票数是否大于0,如果大于0可以进行买票
while (true){
//payticket();
ticket();
}
}
- 静态同步方法的使用
//静态同步方法,其对象锁是本类的class属性
public static void payticket(){
synchronized (RunnableImpl.class){
if(ticket>0){
System.out.println(Thread.currentThread().getName()+"正在卖"+ticket+"张票");
ticket--;
}
}
}
- 同步方法和静态同步方法中锁对象的区别
①同步方法的锁对象是实现类对象,即this
②静态同步方法的锁对象不能是this,this是创建对象之后产生的,静态方法优先于对象。它的锁对象是本类的class属性----->class文件对象(反射)
3. Lock锁
java.util.concurrent.locks.Lock 机制提供了比synchronized代码块和synchronized方法更广泛的锁定操作, 同步代码块/同步方法具有的功能Lock都有,除此之外更强大,更体现面向对象。
Lock锁也称同步锁,加锁与释放锁方法化了,如下:
- public void lock() :加同步锁。
- public void unlock() :释放同步锁
实现步骤: - 在成员位置创建一个ReentrantLock对象
- 在可能出现安全问题的代码前调用Lock接口中的lock方法获取锁
- 在可能出现安全问题的代码后调用Lock接口中的unlock方法释放锁
代码实现如下:
public class RunnableImpl implements Runnable{
private static int ticket=100;
//使用多态创建一个ReentrantLock对象
Lock l=new ReentrantLock();
@Override
public void run() {
//判断票数是否大于0,如果大于0可以进行买票
while (true){
//获得锁
l.lock();
if(ticket>0){
try {
Thread.sleep(10);
System.out.println(Thread.currentThread().getName()+"正在卖"+ticket+"张票");
ticket--;
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
//释放锁
l.unlock();//无论程序是否异常,都会把锁释放
}
}
}
}
}