线程安全问题
单线程程序是不会出现线程安全问题的,例如:一个电影院售票处卖编号1-100的门票,不会有问题。
多线程程序,没有访问共享数据,也不会产生线程安全问题,例如:三个电影售票处卖标号1-100的门票,但门票编号不同,也不会出现问题。
多线程程序访问了共享的数据,就会产生线程安全问题,例如:三个电影售票处卖编号1-100的门票,门票编号相同,就会出现问题。
线程安全问题的代码实现
package M1.day27.day28;
/*
实现卖票案例
*/
public class RunnableImpl implements Runnable{
//定义一个多线程的票源
private int ticket = 100;
//设置线程任务(卖票)
@Override
public void run(){
//创建死循环,让卖票一直进行下去
while (true){
//为了提高安全事件出现概率,让程序睡眠
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
//先判断票是否存在
if (ticket>0){
//票存在,卖票
System.out.println(Thread.currentThread().getName()+"正在卖"+ticket+"号票");
ticket--;
}else break;
}
}
}
package M1.day27.day28;
/*
模拟卖票案例创建三个线程,同时开启,对共享的票进行出售
*/
public class Ticket {
public static void main(String[] args) {
//创建Runnable接口的实现类对象
RunnableImpl run = new RunnableImpl();
//创建Thread类对象,构造方法中传递Runnable接口的实现类对象
Thread t0 = new Thread(run);
Thread t1 = new Thread(run);
Thread t2 = new Thread(run);
//调用start方法开启多线程
t0.start();
t1.start();
t2.start();
}
}
线程安全问题是不能产生的,我们可以让一个线程在访问共享数据的时候,无论是否失去了cpu的执行权,让其他线程只能等待,等待该线程执行完后,其他线程再进行该操作。
同步代码块
同步代码块:synchronized关键字可以用于方法中的某个区块中,表示只对这个取快递资源实行互斥访问。
格式:
synchronized(同步锁){
可能会出现线程安全问题的代码(访问了共享数据的代码)
}
注意:
- 通过代码块中的锁对象,可以使用任意的对象
- 但是必须保证多个线程使用的锁对象是同一个
- 锁对象作用:
把同步代码块锁住,会让一个线程在同步代码块中执行。
代码演示:
package M1.day27.day28;
/*
实现卖票案例
*/
public class RunnableImpl implements Runnable{
//定义一个多线程的票源
private int ticket = 100;
//创建一个锁对象
Object obj = new Object();
//设置线程任务(卖票)
@Override
public void run(){
//创建死循环,让卖票一直进行下去
while (true){
//同步代码块
synchronized (obj){
//先判断票是否存在
if (ticket>0){
//票存在,卖票
//为了提高安全事件出现概率,让程序睡眠
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"正在卖"+ticket+"号票");
ticket--;
}else break;
}
}
}
}
同步技术的原理
使用了一个锁对象,这个锁对象也叫对象所,也叫对象监视器。
同步中的线程,没有执行完毕不会释放所,同步外的程序,没有锁对象进去不去同步。
同步保证了只能有一个线程在同步中执行共享数据,保证了安全。弊端在于,程序频繁的判断锁,获取锁,释放锁会导致程序的效率降低。
同步方法
代码演示:
package M1.day27.day28;
public class tongbufangfa implements Runnable{
private int ticket = 100;
@Override
public void run(){
while (true){
payTicket();
}
}
/*
定义一个同步方法
*/
public synchronized void payTicket(){
if (ticket>0){
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"正在卖"+ticket+"号票");
ticket--;
}
}
}
同步方法也会把方法内部的代码锁住。
只让一个线程执行。
同步方法的锁对象就是实现类对象。
静态同步方法
静态同步方法锁对象是谁?
不能是this
this是创建对象之后产生的,静态方法优先于对象
静态方法的锁对象是本类的class属性-->class文件对象(反射)
Lock锁
java.util.concurrent.locks是解决线程安全问题的第三种方案。
java.util.concurrent.locks.lock接口
lock视线提供了比使用synchronized方法和语句可获得的更广泛的锁定操作。
lock接口中的方法:
void lock()获取锁
void unlock()释放锁
使用步骤:
- 在成员位置创建一个Reentrantloc对象
- 在可能会出现安全问题的代码前调用lock接口中的方法lock获取锁
- 在可能会出现安全问题的代码前调用lock接口中的方法lock释放锁