假如Java程序中有多个线程在同时运行,而这些线程可能会同时运行一部分的代码。若是说该Java程序每次运行的结果和单线程的运行结果是同样的,而且其余的变量值也都是和预期的结果是同样的,那么就能够说线程是安全的。java
假若有一个电影院上映《葫芦娃大战奥特曼》,售票100张(1-100号),分三种状况卖票:安全
该电影院开设一个售票窗口,一个窗口卖一百张票,没有问题。就如同单线程程序不会出现安全问题同样。多线程
该电影院开设n(n>1)个售票窗口,每一个售票窗口售出指定号码的票,也不会出现问题。就如同多线程程序,没有访问共享数据,不会产生问题。ide
该电影院开设n(n>1)个售票窗口,每一个售票窗口出售的票都是没有规定的(如:全部的窗口均可以出售1号票),这就会出现问题了,假如三个窗口同时在卖同一张票,或有的票已经售出,还有窗口还在出售。就如同多线程程序,访问了共享数据,会产生线程安全问题。测试
public class MovieTicket01 implements Runnable { /** * 电影票数量 */ private static int ticketNumber = 100; /** * 在实现类中重写Runnable接口的run方法,并设置此线程要执行的任务 */ @Override public void run() { // 设置此线程要执行的任务 while (ticketNumber > 0) { // 提升程序安全的几率,让程序睡眠10毫秒 try { Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } // 电影票出售 System.out.println("售票窗口(" + Thread.currentThread().getName() + ")正在出售:" + MovieTicket01.ticketNumber + "号电影票"); ticketNumber --; } } }
// 测试 public class Demo01MovieTicket { public static void main(String[] args) { // 建立一个 Runnable接口的实现类对象。 MovieTicket01 movieTicket = new MovieTicket01(); // 建立Thread类对象,构造方法中传递Runnable接口的实现类对象(三个窗口)。 Thread window0 = new Thread(movieTicket); Thread window1 = new Thread(movieTicket); Thread window2 = new Thread(movieTicket); // 设置一下窗口名字,方便输出确认 window0.setName("window0"); window1.setName("window1"); window2.setName("window2"); // 调用Threads类中的start方法,开启新的线程执行run方法 window0.start(); window1.start(); window2.start(); } }
控制台部分输出:
售票窗口(window0)正在出售:100号电影票
售票窗口(window2)正在出售:99号电影票
售票窗口(window1)正在出售:100号电影票
售票窗口(window0)正在出售:97号电影票
售票窗口(window2)正在出售:97号电影票
售票窗口(window1)正在出售:97号电影票
售票窗口(window1)正在出售:94号电影票
售票窗口(window2)正在出售:94号电影票
.
.
.
.
.
.
售票窗口(window0)正在出售:7号电影票
售票窗口(window2)正在出售:4号电影票
售票窗口(window0)正在出售:4号电影票
售票窗口(window1)正在出售:2号电影票
售票窗口(window1)正在出售:1号电影票
售票窗口(window2)正在出售:0号电影票
售票窗口(window0)正在出售:-1号电影票
能够看到,三个窗口(线程)同时出售不指定号数的票(访问共享数据),出现了卖票重复,和出售了不存在的票号数(0、-1)spa
售票窗口(window0)正在出售:100号电影票
售票窗口(window2)正在出售:99号电影票
售票窗口(window1)正在出售:100号电影票
window0、window一、window2分别对应线程0、线程一、线程2线程
经过线程的同步,来解决共享数据问题。有三种方式,分别是同步代码块、同步方法、锁机制。code
public class MovieTicket02 implements Runnable { /** * 电影票数量 */ private static int ticketNumber = 100; /** * 建立锁对象 */ Object object = new Object(); /** * 在实现类中重写Runnable接口的run方法,并设置此线程要执行的任务 */ @Override public void run() { // 设置此线程要执行的任务 synchronized (object) { // 把访问了共享数据的代码放到同步代码中 while (ticketNumber > 0) { // 提升程序安全的几率,让程序睡眠10毫秒 try { Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } // 电影票出售 System.out.println("售票窗口(" + Thread.currentThread().getName() + ")正在出售:" + MovieTicket02.ticketNumber + "号电影票"); ticketNumber --; } } } }
// 进行测试 public class Demo02MovieTicket { public static void main(String[] args) { // 建立一个 Runnable接口的实现类对象。 MovieTicket02 movieTicket = new MovieTicket02(); // 建立Thread类对象,构造方法中传递Runnable接口的实现类对象(三个窗口)。 Thread window0 = new Thread(movieTicket); Thread window1 = new Thread(movieTicket); Thread window2 = new Thread(movieTicket); // 设置一下窗口名字,方便输出确认 window0.setName("window0"); window1.setName("window1"); window2.setName("window2"); // 调用Threads类中的start方法,开启新的线程执行run方法 window0.start(); window1.start(); window2.start(); } }
控制台输出:
售票窗口(window0)正在出售:100号电影票
售票窗口(window0)正在出售:99号电影票
售票窗口(window0)正在出售:98号电影票
售票窗口(window0)正在出售:97号电影票
售票窗口(window0)正在出售:96号电影票
.
.
.
.
.
.
售票窗口(window0)正在出售:5号电影票
售票窗口(window0)正在出售:4号电影票
售票窗口(window0)正在出售:3号电影票
售票窗口(window0)正在出售:2号电影票
售票窗口(window0)正在出售:1号电影票
这时候,控制台再也不出售不存在的电影号数以及重复的电影号数了。对象
经过代码块中的锁对象,可使用任意的对象。可是必须保证多个线程使用的锁对象是同一。锁对象做用:把同步代码块锁住,只让一个线程在同步代码块中执行。blog
总结:同步中的线程,没有执行完毕,不会释放锁,同步外的线程,没有锁,进不去同步。
public class MovieTicket03 implements Runnable { /** * 电影票数量 */ private static int ticketNumber = 100; /** * 建立锁对象 */ Object object = new Object(); /** * 在实现类中重写Runnable接口的run方法,并设置此线程要执行的任务 */ @Override public void run() { // 设置此线程要执行的任务 ticket(); } public synchronized void ticket() { // 把访问了共享数据的代码放到同步代码中 while (ticketNumber > 0) { // 提升程序安全的几率,让程序睡眠10毫秒 try { Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } // 电影票出售 System.out.println("售票窗口(" + Thread.currentThread().getName() + ")正在出售:" + MovieTicket03.ticketNumber + "号电影票"); ticketNumber --; } } }
测试与同步代码块同样。
在Java中,Lock锁机制又称为同步锁,加锁public void lock(),释放同步锁public void unlock()。
import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; public class MovieTicket05 implements Runnable { /** * 电影票数量 */ private static int ticketNumber = 100; Lock reentrantLock = new ReentrantLock(); /** * 在实现类中重写Runnable接口的run方法,并设置此线程要执行的任务 */ @Override public void run() { // 设置此线程要执行的任务 while (ticketNumber > 0) { reentrantLock.lock(); // 提升程序安全的几率,让程序睡眠10毫秒 try { Thread.sleep(10); // 电影票出售 System.out.println("售票窗口(" + Thread.currentThread().getName() + ")正在出售:" + MovieTicket05.ticketNumber + "号电影票"); ticketNumber --; } catch (InterruptedException e) { e.printStackTrace(); } finally { reentrantLock.unlock(); } } } }
// 测试 public class Demo05MovieTicket { public static void main(String[] args) { // 建立一个 Runnable接口的实现类对象。 MovieTicket05 movieTicket = new MovieTicket05(); // 建立Thread类对象,构造方法中传递Runnable接口的实现类对象(三个窗口)。 Thread window0 = new Thread(movieTicket); Thread window1 = new Thread(movieTicket); Thread window2 = new Thread(movieTicket); // 设置一下窗口名字,方便输出确认 window0.setName("window0"); window1.setName("window1"); window2.setName("window2"); // 调用Threads类中的start方法,开启新的线程执行run方法 window0.start(); window1.start(); window2.start(); } }
控制台部分输出:
售票窗口(window0)正在出售:100号电影票
售票窗口(window0)正在出售:99号电影票
售票窗口(window0)正在出售:98号电影票
售票窗口(window0)正在出售:97号电影票
售票窗口(window0)正在出售:96号电影票
.
.
.
.
.
.
售票窗口(window1)正在出售:7号电影票
售票窗口(window1)正在出售:6号电影票
售票窗口(window1)正在出售:5号电影票
售票窗口(window1)正在出售:4号电影票
售票窗口(window1)正在出售:3号电影票
售票窗口(window2)正在出售:2号电影票
售票窗口(window1)正在出售:1号电影票
与前两种方式不一样,前两种方式,只有线程0可以进入同步机制执行代码,Lock锁机制,三个线程均可以进行执行,经过Lock锁机制来解决共享数据问题。
Java 多线程安全问题就到这里了,若是有什么不足、错误的地方,但愿大佬们指正。