先回顾一下上节课讲的多个窗口共同卖票的问题,首先我们设置三个不同的线程来同步进行卖票,我们把所有的票数全部设置成静态变量,让这三个线程可以共享这些票。通过设置这个静态变量我们实现了三个窗口共同卖票的问题。但是现实生活中我们打开APP买票时,通常会有广告出现,这些就是线程睡眠的结果。这个线程睡眠可能就会导致共享出现问题,可能会出现0票,甚至还有-1票的问题。那么这是什么原因呢?其实这是由于三个线程之间是抢夺式调度模型,当有一个线程进入了子线程准备购票的时候,这个时候线程中只有1张票了;另一个线程也开始了进入线程准备购票,由于第一个线程进入以后还没有来得及将票数减1,第二个线程已经完成了所有操作,这个时候就会出现0票的情况,而第三个线程又抢先一步于第一个线程完成所有的操作,那么就会出现0票的情况。所以接下我们要研究的是如何解决这个问题?
(1)出现了0张票或负数票的问题,原因:这是由于线程的随机性导致的。
(2)出现了相同的票数,原因:这是由于线程的原子性导致的。
原子性问题(不可分割性) 线程对(piao–不是一个原子性操作,他要对票这个变量进行读改写三个操作)
出现线程安全问题符合三个条件:
(1)是否为多线程环境
(2)多个线程之间有没有共享数据
(3)有没有多条语句在操作这个共享变量piao–
对于数据安全的问题,我们把有可能出现数据安全问题的代码使用同步代码块进行包裹。
synchronized(对象){
放你认为有可能出现问题的代码;
}
锁:你可以用Java里面的任意一个对象来充当锁,多个线程要共享一把锁才能锁住。
这个同步代码块能够保证数据的安全性的一个主要原因是这个对象要被定义成静态成员变量,才能被所有线程共享。需要这个对象被所有的线程所共享,这个对象其实就是一把锁,这个对象习惯叫作监视器。
同步代码块的好处:同步的出现解决了多线程的安全问题。
同步代码块的弊端:当线程相当多时,因为每一个线程都会区判断同步上的锁,很耗费资源,无形中会降低程序的运行效率。
public class Mytest { public static void main(String[] args) { CellRunnable cellRunnable = new CellRunnable(); Thread thread1 = new Thread(cellRunnable, "窗口1:"); Thread thread2 = new Thread(cellRunnable, "窗口2:"); Thread thread3 = new Thread(cellRunnable, "窗口3:"); thread1.start(); thread2.start(); thread3.start(); } } ---------------------------------- public class CellRunnable implements Runnable{ //这些票让三个线程共享 static int piao=100; //确保这个锁对象只有一个,多个线程共用一把锁 static Object obj=new Object(); @Override public void run() { while (true){ synchronized (obj){ //当线程1进来同步代码块后,就持有了这个锁,其他线程没有持有锁,那么就处于等待状态,等待在同步代码块的外面。 if (piao>=1){ try { Thread.sleep(50); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName()+"正在出售第:"+(piao--)+"张票"); } } //执行完了,出了同步代码块,就会释放锁。释放锁了之后,多个线程再去争抢CPU的时间片 } } }
我们可以把一个方法用synchronized这个关键字修饰,来封装一段代码解决线程安全问题。
同步方法使用的锁对象是this(隐含对象)。
public class Mytest { public static void main(String[] args) { CellRunnable cellRunnable = new CellRunnable(); Thread thread1 = new Thread(cellRunnable, "窗口1:"); Thread thread2 = new Thread(cellRunnable, "窗口2:"); Thread thread3 = new Thread(cellRunnable, "窗口3:"); thread1.start(); thread2.start(); thread3.start(); } } ---------------------------------- public class CellRunnable implements Runnable{ //这些票让三个线程共享 static int piao=100; //确保这个锁对象只有一个,多个线程共用一把锁 static Object obj=new Object(); int i=1; @Override public void run() { while (true){ if (i%2==0){ //由于同步方法的锁对象默认是this,同步代码块和同步方法使用的锁不是同一个锁, // 因为我们要将其改成同一个锁对象。 synchronized (this){ //当线程1进来同步代码块后,就持有了这个锁,其他线程没有持有锁,那么就处于等待状态,等待在同步代码块的外面。 if (piao>=1){ try { Thread.sleep(50); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName()+"正在出售第:"+(piao--)+"张票"); } } }else { maipiao(); } i++; } } //同步方法:我们可以把一个方法用synchronized这个关键字来修饰,来封装一段代码,解决线程安全问题, //同步方法默认使用的锁对象就是this //同步方法使用的锁对象是this,和上面的锁使用的对象不一致,因此我们要将上面的锁对象改为this private synchronized void maipiao() { if (piao >= 1) { try { Thread.sleep(50); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + "正在出售第:" + (piao--) + "张票"); } } }
静态同步方法的锁对象是当前类的字节码对象。
public class Mytest { public static void main(String[] args) { CellRunnable cellRunnable = new CellRunnable(); Thread thread1 = new Thread(cellRunnable, "窗口1:"); Thread thread2 = new Thread(cellRunnable, "窗口2:"); Thread thread3 = new Thread(cellRunnable, "窗口3:"); thread1.start(); thread2.start(); thread3.start(); } } ---------------------------------------- public class CellRunnable implements Runnable{ //这些票让三个线程共享 static int piao=100; //确保这个锁对象只有一个,多个线程共用一把锁 private final static Object obj=new Object(); int i=1; @Override public void run() { while (true){ if (i%2==0){ //由于静态同步方法和同步代码块使用的锁对象不是同一个锁对象, // 因为我们要将同步代码块的锁对象修改成当前类的字节码对象。 synchronized (CellRunnable.class){ //当线程1进来同步代码块后,就持有了这个锁,其他线程没有持有锁,那么就处于等待状态,等待在同步代码块的外面。 if (piao>=1){ try { Thread.sleep(50); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName()+"正在出售第:"+(piao--)+"张票"); } } //执行完了,出了同步代码块,就会释放锁。释放锁了之后,多个线程再去争抢CPU的时间片 }else { maiPiao(); } } } //同步方法:我们可以把一个方法用synchronized这个关键字修饰,来封装一段代码,解决线程安全的问题。 //静态同步方法:默认使用的锁对象,是当前类的字节码对象。 private static synchronized void maiPiao() { if (piao >= 1) { try { Thread.sleep(50); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + "正在出售第:" + (piao--) + "张票"); } } }
lock.lock(); lock.unlock();
public class Mytest { public static void main(String[] args) { CellRunnable cellRunnable = new CellRunnable(); Thread thread1 = new Thread(cellRunnable, "窗口1:"); Thread thread2 = new Thread(cellRunnable, "窗口2:"); Thread thread3 = new Thread(cellRunnable, "窗口3:"); thread1.start(); thread2.start(); thread3.start(); } } ------------------------------------- public class CellRunnable implements Runnable{ //这些票三个线程共享 static int piao=100; private final static ReentrantLock lock=new ReentrantLock(); @Override public void run() { while (true){ //th1 th2 th3 加锁 lock.lock(); try { if (piao>=1){ try { Thread.sleep(50); System.out.println(Thread.currentThread().getName()+"正在出售第:"+(piao--)+"张票"); } catch (InterruptedException e) { e.printStackTrace(); } } } catch (Exception e) { e.printStackTrace(); } finally { lock.unlock();//解锁 } } } }
如果出现了同步嵌套,就容易出现死锁问题,指的是两个或者两个以上的线程在执行的过程中,因争夺资源而产生的一种互相等待的现象。
public class Mytest { public static void main(String[] args) throws InterruptedException { MyThread th1 = new MyThread(true); MyThread th2 = new MyThread(false); th1.start(); Thread.sleep(50); th2.start(); } } ----------------------------------------- public class MyThread extends Thread{ //标记 boolean flag; public MyThread(boolean flag) { this.flag = flag; } @Override public void run() { if (flag){ synchronized (ObjectUtils.objA){ System.out.println("true 线程持有了objA锁,进来执行了AAA"); synchronized (ObjectUtils.objB){ System.out.println("true 线程持有了objB锁,进来执行了BBB"); } } }else { synchronized (ObjectUtils.objB){ System.out.println("false 线程持有了objB锁,进来执行了BBB"); synchronized (ObjectUtils.objA){ System.out.println("false 线程持有了objA锁,进来执行了AAA"); } } } } } -------------------------------------- public interface ObjectUtils { //创建两个对象,来充当两把锁对象 public static final Object objA=new Object(); public static final Object objB=new Object(); }