当我们使用多个线程访问同一资源的时候,且多个线程中对资源有写的操作,就容易出现线程安全问题。
多窗口卖票问题public class RunnableImpl implements Runnable{
private int ticket=100;
@Override
public void run(){
while(true){
if(ticket>0){
try{
Thread.sleep(10);
}catch(InterruptException e){
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"正在卖第"+
ticket+"张票");
ticket--;
}
}
}
}
public class Demo01Ticket{
public static void main(String[] args){
//创建实现类对象
RunnableImpl run=new RunnableImpl();
//同一个任务多个线程执行
Thread t0=new Thread(run);
Thread t1=new Thread(run);
Thread t2=new Thread(run);
//启动
t0.start();
t1.start();
t2.start();
}
}
要解决上述多线程并发访问一一个资源的安全性问题:也就是解决重复票与不存在票问题, Java中提供了同步机制(synchronized)来解决。
Java中可以通过3种方式完成同步操作:- 1,同步代码块
- 2,同步方法
- 3,锁机制
synchronized关键字可以用于方法中的某个区块中,表示对这个区块中的资源进行互斥的访问。
synchronized(同步锁对象){
需要同步操作的代码;
}
1.2 同步锁
一个抽象概念,可以想象为在对象上标记了一个锁
- 1,锁对象,可以是任意对象
- 2,多个线程对象要使用同意把锁
注意:在任何时候,最多允许一个线程拥有同步锁,谁拿到锁就进入代码块,其他的线程只能在外等着(BLOCKED阻塞)。
public class RunnableImpl 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(InterruptException e){
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"正在卖 第"+ticket+"张票");
ticket--;
}
}
}
}
}
总结
同步中的线程没有执行完毕不会释放锁,同步外的线程没有锁进不去同步。
2,同步方法 步骤:- 1,把访问了共享数据的代码抽取出来,放到一个方法中
- 2,在方法上添加synchronized修饰符
该方法本质上就是同步代码块,其锁对象即为new RunnableImpl(),也就是this。
public class RunnableImpl implements Runnable{
private int ticket=100;
@Override
public void run(){
while(true){
sellTicket();
}
}
public synchronized void sellTicket(){//-------------同步关键字
if(ticket>0){
try{
Thread.sleep(10);
}catch(InterruptException e){
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"正在卖第"+
ticket+"张票");
ticket--;
}
}
静态的同步方法:
静态的同步方法也能实现同步,但由于是静态的,所以锁对象不可能再是this对象。
静态同步方法的锁对象是本类的class属性,即class文件对象,字节码文件
synchronized(Runnable.class){
要同步的代码;
}
public class RunnableImpl implements Runnable{
private Static int ticket=100;//------------------静态同步方法只能访问静态变量
@Override
public void run(){
while(true){
sellTicket();
}
}
public Static synchronized void sellTicketStatic(){//-------------同步关键字
if(ticket>0){
try{
Thread.sleep(10);
}catch(InterruptException e){
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"正在卖第"+
ticket+"张票");
ticket--;
}
}
3,Lock锁(接口)
jdk 1.5之后
java.util.concurrent.locks.Lock机制提供了比synchronized代码块和synchronized方法更广泛的锁定操作,同步代码块/同步方法具有的功能Lock都有,除此之外更强大,更体现面向对象。
Lock接口中的方法:lock(),unlock()
java. util. concurrent. Locks. ReentrantLock实现了 Lock接口
步骤:- 1,在成员位置创建一个ReentrantLock对象;
- 2,在可能出现线程安全问题的代码前调用Lock接口中的方法lock()获取锁。
- 3,在其后使用unlock()方法释放锁;
public class RunnableImpl implements Runnable{
private int ticket=100;
//1,在成员位置创建一个ReentrantLock对象;
Lock lo=new ReentrantLock();
@Override
public void run(){
while(true){
//2,在可能出现线程安全问题的代码前调用Lock接口中的方法lock()获取锁。
lo.lock();
if(ticket>0){
try{
Thread.sleep(10);
}catch(InterruptException e){
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"正在卖第"+
ticket+"张票");
ticket--;
}
//3,在其后使用unlock()方法释放锁;
lo.unlock();
}
}
}
优化后:保证最后能够释放锁对象finally
public class RunnableImpl implements Runnable{
private int ticket=100;
//1,在成员位置创建一个ReentrantLock对象;
Lock lo=new ReentrantLock();
@Override
public void run(){
while(true){
//2,在可能出现线程安全问题的代码前调用Lock接口中的方法lock()获取锁。
lo.lock();
if(ticket>0){
try{
Thread.sleep(10);
System.out.println(Thread.currentThread().getName()+"正在卖 第"+ ticket+"张票");
ticket--;
}catch(InterruptException e){
e.printStackTrace();
}finally{
//保证最后能够释放锁对象
lo.unlock();
}
}
//3,在其后使用unlock()方法释放锁;
lo.unlock();
}
}
}



