线程安全问题
如果有多个线程在同时运行,而这些线程可能会同时运行这段代码。程序每次运行结果和单线程运行的结果是一样的,而且其他的变量的值也和预期的是一样的,就是线程安全的。(单线程程序是不会出现线程安全问题的;多线程访问了共享的数据,会产生线程安全问题)
通过卖票案例演示线程的安全问题:
我们来模拟电影院的售票窗口,实现多个窗口同时卖一场电影票(多个窗口一起卖这100张票)需要窗口,采用线程对象来模拟﹔需要票,Runnable接口子类来模拟
模拟票︰
public class safe implements Runnable{
private int ticket=100;
@Override
public void run() {
while(true){
if(ticket>0) {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"正在卖第"+ticket+"张票");
ticket--;
}
}
}
}
模拟卖票案例:
public class safemain {
public static void main(String[] args) {
safe s1=new safe();
Thread t0=new Thread(s1);
Thread t1=new Thread(s1);
Thread t2=new Thread(s1);
t0.start();
t1.start();
t2.start();
}
}
结果会出现卖重复票和0票,-1票的情况,出现这种情况的原因:
解决方法:线程同步
完成同步操作有三种方法:
1. 同步代码块
同步代码块︰synchronized关键字可以用于方法中的某个区块中,表示只对这个区块的资源实行互斥访问。
格式:
synchronized(同步锁){
需要同步操作的代码
}
同步锁:
对象的同步锁只是一个概念,可以想象为在对象上标记了一个锁.
1.锁对象可以是任意类型。
2.多个线程对象要使用同一把锁。
注意:在任何时候,最多允许一个线程拥有同步锁,谁拿到锁就进入代码块,其他的线程只能在外等着(BLOCKED)。
使用同步代码块解决代码︰
public class safe implements Runnable{
private int ticket=100;
@Override
public void run() {
while(true){
synchronized (this){
if(ticket>0) {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"正在卖第"+ticket+"张票");
ticket--;
}
}
}
}
}
/*或
public class safe implements Runnable{
private int ticket=100;
Object obj=new Object();//放在run方法外部,否则多个线程会产生多个对象,不能保证唯一
@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--;
}
}
}
}
}*/
同步技术的原理:
2.同步方法
同步方法:使用synchronized修饰的方法,就叫做同步方法,保证A线程执行该方法的时候,其他线程只能在方法外等着。
步骤:
1.把访问了共享数据的代码抽取出来,放到一个方法中
2 .在方法上添加synchronized修饰符
格式︰
public synchronized void method( ){
可能会改生线程安全问题的代码
}
对于非static方法,同步锁就是this。
对于static方法,我们使用当前方法所在类的字节码对象(类名.class)。
/*定义一个同步方法
同步方法也会把方法内部的代码锁住只让一个线程执行
同步方法的锁对象就是实现类对象 new RunnableimpL(),也是就是this
*/
public class safe implements Runnable{
private int ticket=100;
@Override
public void run() {
while(true){
payTacket();
}
}
public synchronized void payTacket(){
if(ticket>0) {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"正在卖第"+ticket+"张票");
ticket--;
}
}
}
//静态同步方法
public class safe implements Runnable{
private static int ticket=100;
@Override
public void run() {
while(true){
payTacket();
}
}
public static synchronized void payTacket(){
if(ticket>0) {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"正在卖第"+ticket+"张票");
ticket--;
}
}
}
3.锁机制
使用Lock锁:java.util.concurrent.Locks.Lock接口
Lock实现提供了比使用synchronized方法和语句可获得的更广泛的锁定操作。
Lock接口中的方法:
void lock()获取锁。
void unLock( )释放锁。
java.util.concurrent.Locks.ReentrantLock implements Lock接口
使用步骤:
1.在成员位置创建一个ReentrantLock对象
2.在可能会出现安全问题的代码前调用Lock接口中的方法Lock获取锁
3.在可能会出现安全问题的代码前调用Lock接口中的方法Lock获取锁
public class safe 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);
System.out.println(Thread.currentThread().getName()+"正在卖第"+ticket+"张票");
ticket--;
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
l.unlock();//无论程序是否异常,都会释放锁
}
}
}
}
}