线程同步机制
1.线程安全问题概述
如果有多个线程在同时运行,而这些线程可能会同时运行这段代码。程序每次运行结果和单线程运行的结果是一样的,而且其他的变量的值也和预期的是一样的,就是线程安全的。
以上3例说明只有多线程访问共享的数据才会产生线程安全问题
2.线程安全问题的代码实现
public class DemoTicket{
//模拟卖票案例 创建3个线程,同时开启,对共享的票进行出售
public static void main(String[] args){
//创建Runnable接口的实现类对象
Runnable run = new RunnableImpl();//共享100张票
//创建Thread类对象,构造方法中传递Runnable接口的实现类对象
Thread t0 = new Thread(run);
Thread t1 = new Thread(run);
Thread t2 = new Thread(run);
//调用start方法开启多个线程
t0.start();t1.start();t2.start();
}
}
/*
实现卖票案例
*/
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){e.printStackTrace();}//提高安全问题出现的概率,让程序睡眠
System.out.println(Thread.currentThread().getName()+"-->正在卖第"+ticket+"张票");//票存在,卖票,票--
ticket--;}}
}
}
3.线程安全问题产生的原理
因为t0,t1,t2都进入while循环中(共享数据区),此时3个线程都执行sleep()方法,从而失去cpu执行权,当sleep()方法结束,3个线程都在共享数据区,t2率先获得执行权,使ticket--至0,从而退出循环,可此时t0,t1都在循环中,即使ticket=0了,仍要执行完循环,从而出现ticket=-1;
4.解决线程安全问题 同步代码块
为了保证每个线程都能正常执行原子操作,java引入了线程同步机制,那么怎么去使用呢,有三种完成同步操作
1.同步代码块
2.同步方法
3.锁机制
同步代码块格式:
synchronized(锁对象){
可能会出现线程安全问题的代码(访问了共享数据的代码)
}
注意:
1.同步代码块中的锁对象可以是任意对象
2.但是必须保证多个线程使用的锁对象是同一个
3.锁对象作用:把同步代码块锁住,只让一个线程在同步代码块运行
//创建一个锁对象 创建在run方法外,不然线程每执行一次run方法就生成一个锁对象,不能保证锁住
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--;}
}}}
5.同步技术的原理
6.解决线程安全问题 同步方法
同步方法的使用步骤:
1.把访问了共享数据的代码块抽取出来,放到一个方法中
2.在方法上添加synchronized修饰符
格式:定义方法的格式
修饰符 synchronized 返回值类型 方法名(参数列表){
可能会出现线程安全问题的代码(访问了共享数据的代码)
}
//定义一个同步方法 同步方法也会把方法内部的代码锁住,只让一个线程执行,同步方法的锁对象是谁?就是实现类对象 new RunnableImpl(),也就是this
public synchronized void payticket(){
if(ticket>0){//票是否存在
try{Thread.sleep(10);}catch(InterruptedException e){e.printStackTrace();}//提高安全问题出现的概率,让程序睡眠
System.out.println(Thread.currentThread().getName()+"-->正在卖第"+ticket+"张票");//票存在,卖票,票--
ticket--;}
}
//使用时
while(true){ payticket(); }
7.静态同步方法
private static int ticket = 100;//将ticket设置为静态,否则同步方法中的ticket会报错
public static synchronized void payticket(){
if(ticket>0){//票是否存在
try{Thread.sleep(10);}catch(InterruptedException e){e.printStackTrace();}//提高安全问题出现的概率,让程序睡眠
System.out.println(Thread.currentThread().getName()+"-->正在卖第"+ticket+"张票");//票存在,卖票,票--
ticket--;}
}
静态同步方法:
1.锁对象是谁?
不能是this,this是创建对象之后产生的,静态方法优先于对象,静态方法的锁对象是本类的class属性-->class文件对象(反射),则同步代码块可如下使用
public static /*synchronized*/ void payticket(){
synchronized(RunnableImpl.class){
if(ticket>0){//票是否存在
try{Thread.sleep(10);}catch(InterruptedException e){e.printStackTrace();}//提高安全问题出现的概率,让程序睡眠
System.out.println(Thread.currentThread().getName()+"-->正在卖第"+ticket+"张票");//票存在,卖票,票--
ticket--;}}
}
8.解决线程安全问题 Lock锁
Lock锁:
java.util.concurrent.locks.lock接口,lock接口实现提供比使用synchronized方法和语句可获得更广泛的锁定操作
lock接口中的方法:
void lock();获取锁
void unlock();释放锁
java.util.concurrent.locks.ReenrantLock implements lock接口
lock锁使用步骤:
1.在成员位置创建一个Reenrantlock对象
2.在可能出现线程安全问题的代码前调用lock接口中的方法lock()获取锁
3.在可能出现线程安全问题的代码后调用lock接口中的方法unlock()释放锁
public class RunnableImpl implements Runnable{
//定义一个多个线程共享的票源
private int ticket = 100;
1.在成员位置创建一个Reenrantlock对象
Lock l = new ReentrantLock();
//设置线程任务:卖票
@Override
public void run(){
while(true){//卖票操作重复执行
//2.在可能出现线程安全问题的代码前调用lock接口中的方法lock()获取锁
l.lock();
if(ticket>0){//票是否存在
try{Thread.sleep(10);}catch(InterruptedException e){e.printStackTrace();}//提高安全问题出现的概率,让程序睡眠
System.out.println(Thread.currentThread().getName()+"-->正在卖第"+ticket+"张票");//票存在,卖票,票--
ticket--;}
//3.在可能出现线程安全问题的代码后调用lock接口中的方法unlock()释放锁
l.unlock();
}
}
}
更优化的Lock用法:
public class RunnableImpl implements Runnable{
//定义一个多个线程共享的票源
private int ticket = 100;
1.在成员位置创建一个Reenrantlock对象
Lock l = new ReentrantLock();
//设置线程任务:卖票
@Override
public void run(){
while(true){//卖票操作重复执行
//2.在可能出现线程安全问题的代码前调用lock接口中的方法lock()获取锁
l.lock();
if(ticket>0){//票是否存在
try{
Thread.sleep(10);//提高安全问题出现的概率,让程序睡眠
System.out.println(Thread.currentThread().getName()+"-->正在卖第"+ticket+"张票");//票存在,卖票,票--
ticket--;} //卖票的代码移动了
}catch(InterruptedException e){
e.printStackTrace();
}finally{
//3.在可能出现线程安全问题的代码后调用lock接口中的方法unlock()释放锁
l.unlock();//移动到finally块
}
}
}
}
优化后的作用:无论程序是否出现异常,锁对象都会被释放