多线程访问了共享的数据,会产生线程安全问题
image.png

public class RunnalbeImpl 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 Demo04Ticket {
    public static void main(String[] args) {
        /*
        创建三个线程,同时开启,对共享的票进行出售
        同一个实现类,多个线程,才会共享
        */
        //创建Runnable接口的实现类对象
        RunnalbeImpl run = new RunnalbeImpl();
        //创建Thread类对象,传递上述实现类对象
        Thread t0 = new Thread(run);
        Thread t1 = new Thread(run);
        Thread t2 = new Thread(run);
        t0.start();
        t1.start();
        t2.start();
    }

}

image.png
image.png

线程安全问题产生的原理

image.png

sleep使得线程放弃对CPU的执行权
t0, t1, t2同时执行到了一个程序代码(代码执行有时间,所以变化值的语句还没来得及执行,上一句就被多次执行)

线程安全问题是不能产生的,因此可以让一个线程在访问共享数据的时候,无论是否失去了CPU的执行权,其他的线程只能等待。
(保证始终一个线程在工作)

解决:

1.同步代码块

格式:

synchronized(同步锁) {
    //同步代码块
    //可能会出现线程安全的代码(访问了共享数据的代码)
}

注意:

  1. 通过代码块的锁对象,可以使用任意的对象
  2. 保证多个线程使用同一个锁对象
  3. 作用是:把同步代码块锁住,只让一个线程在同步代码块中执行
public class RunnalbeImpl implements Runnable{
    //定义一个多个线程共享的票源
    private int ticket = 100;
    //创建一个锁对象,但是不能放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--;
                }
            }
        }
    }
}
同步技术的原理

image.png
同步中的线程,没有执行完毕不会释放锁
同步外的线程,没有锁进不去同步

程序频繁的判断锁、获取锁、释放锁,程序的效率降低,但提高安全性

2.同步方法

image.png
格式

修饰符 synchronized 返回值类型 方法名(参数列表) {
    //可能会出现线程安全问题的代码(访问了共享数据的代码)
}

image.png

image.png

this是创建对象之后产生的,静态方法优先于对象

image.png

3.Lock锁

java.util.concurrent.locks.lock接口
使用步骤:

  1. 在成员位置创建一个ReentrantLock对象
  2. 可能会出现安全问题的代码前调用Lock接口中的方法lock获取锁
  3. 可能会出现安全问题的代码后调用Lock接口中的方法unlock释放锁
public class RunnalbeImpl implements Runnable{
   //定义一个多个线程共享的票源
   private int ticket = 100;
   //step 1
   Lock l = new ReentrantLock();
   

   //设置线程任务:买票
   @Override
   public void run() {
       while (true) {
           //step 2
           l.lock();
           //判断票是否存在
           if (ticket > 0) {
               try {
                   Thread.sleep(10);
               } catch (InterruptedException e) {
                   e.printStackTrace();
               }
               System.out.println(Thread.currentThread().getName() + "买第" + ticket + "张票");
               ticket--;
           }
           //step 3
           l.unlock();
       }
   }
}

下面优化:无论是否发生异常,都会把锁释放掉
(释放锁放入finally)

public class RunnalbeImpl implements Runnable{
    //定义一个多个线程共享的票源
    private int ticket = 100;
    //step 1
    Lock l = new ReentrantLock();


    //设置线程任务:买票
    @Override
    public void run() {
        while (true) {
            //step 2
            l.lock();
            //判断票是否存在
            if (ticket > 0) {
                try {
                    Thread.sleep(10);
                    System.out.println(Thread.currentThread().getName() + "买第" + ticket + "张票");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }finally {
                    //step 3
                    //这样无论程序是否出现异常,都会把锁释放掉
                    l.unlock();
                }
                
                ticket--;
            }

        }
    }
}

waikiki
4 声望2 粉丝