参照:https://www.cnblogs.com/lizhangyong/p/8029287.html
一个程序在运行起来时,会转换为进程,通常含有多个线程。
通常情况下,一个进程中的比较耗时的操作(如长循环、文件上传下载、网络资源获取等),往往会采用多线程来解决。
比如,现实生活中,银行取钱问题、火车票多个窗口售票问题等,通常会涉及并发问题,从而需要用到多线程技术。
当进程中有多个并发线程进入一个重要数据的代码块时,在修改数据的过程中,很有可能引发线程安全问题,从而造成数据异常。例如,正常逻辑下,同一个编号的火车票只能售出一次,却由于线程安全问题而被多次售出,从而引起实际业务异常。
接下来,我以售票问题,来演示多线程问题中对核心数据保护的重要性。我们先来看不对多线程数据进行保护时会引发什么样的状况。
public class SellTicket extends Thread { static int tickets = 10; @Override public void run(){ while(tickets > 0){ System.out.println(Thread.currentThread().getName() + "-->售出第 " + tickets + "张票" ); tickets--; }try{ Thread.sleep(100); }catch(InterruptedException e){ e.printStackTrace(); } if(tickets < 0){ System.out.println(Thread.currentThread().getName()+"-->售票结束!"); } } public static void main(String[] args) { SellTicket sell = new SellTicket(); Thread t1=new Thread(sell, "1号窗口"); Thread t2=new Thread(sell, "2号窗口"); Thread t3=new Thread(sell, "3号窗口"); Thread t4=new Thread(sell, "4号窗口"); // t1.setName("1号窗口"); // SellTicket t2 = new SellTicket(); // t2.setName("2号窗口"); // SellTicket t3 = new SellTicket(); // t3.setName("3号窗口"); // SellTicket t4 = new SellTicket(); // t4.setName("4号窗口"); t1.start(); t2.start(); t3.start(); t4.start(); } }
运行结果:
1号窗口-->售出第 10张票 1号窗口-->售出第 9张票 1号窗口-->售出第 8张票 1号窗口-->售出第 7张票 1号窗口-->售出第 6张票 3号窗口-->售出第 6张票 3号窗口-->售出第 4张票 3号窗口-->售出第 3张票 3号窗口-->售出第 2张票 3号窗口-->售出第 1张票 1号窗口-->售出第 5张票 4号窗口-->售票结束! 1号窗口-->售票结束! 3号窗口-->售票结束! 2号窗口-->售票结束!
同一张票会被售出多次,显然不符合实际逻辑。
为了解决上述脏数据的问题,我为大家介绍3种使用比较普遍的三种同步方式。
第一种,同步代码块。
有synchronized关键字修饰的语句块,即为同步代码块。同步代码块会被JVM自动加上内置锁,从而实现同步。
public class SellTicket2 { static int tickets=10; class SellTickets implements Runnable{ @Override public void run() { synchronized(this){ while(tickets > 0){ System.out.println(Thread.currentThread().getName()+" -->售出第 "+tickets+" 张票"); tickets--; try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } } if(tickets<=0){ System.out.println(Thread.currentThread().getName()+" -->售票结束!"); } } } } public static void main(String[] args) { SellTickets sell = new SellTicket2().new SellTickets(); Thread t1=new Thread(sell, "1号窗口"); Thread t2=new Thread(sell, "2号窗口"); Thread t3=new Thread(sell, "3号窗口"); Thread t4=new Thread(sell, "4号窗口"); t1.start(); t2.start(); t3.start(); t4.start(); } }
运行结果:
1号窗口 -->售出第 10 张票 1号窗口 -->售出第 9 张票 1号窗口 -->售出第 8 张票 1号窗口 -->售出第 7 张票 1号窗口 -->售出第 6 张票 1号窗口 -->售出第 5 张票 1号窗口 -->售出第 4 张票 1号窗口 -->售出第 3 张票 1号窗口 -->售出第 2 张票 1号窗口 -->售出第 1 张票 1号窗口 -->售票结束! 2号窗口 -->售票结束! 4号窗口 -->售票结束! 3号窗口 -->售票结束!
第二种,同步方法 。
即有synchronized关键字修饰的方法。由于java的每个对象都有一个内置锁,当用此关键字修饰方法时,内置锁会保护整个方法。在调用该方法前,需要获得内置锁,否则就处于阻塞状态。
public class SellTicket2 { static int tickets = 10; class SellTickets implements Runnable{ @Override public void run() { while(tickets > 0){ synMethod(); try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } if(tickets <= 0){ System.out.println(Thread.currentThread().getName()+" -->售票结束!"); } } } synchronized void synMethod(){ synchronized(this){ if(tickets <= 0){ return; } System.out.println(Thread.currentThread().getName()+" -->售出第 "+tickets+" 张票"); tickets--; } } } public static void main(String[] args) { SellTickets sell = new SellTicket2().new SellTickets(); Thread t1=new Thread(sell, "1号窗口"); Thread t2=new Thread(sell, "2号窗口"); Thread t3=new Thread(sell, "3号窗口"); Thread t4=new Thread(sell, "4号窗口"); t1.start(); t2.start(); t3.start(); t4.start(); } }
第三种,Lock锁机制。
通过创建Lock对象,采用lock()加锁,采用unlock()解锁,来保护指定代码块。
import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; public class SellTicket2 { static int tickets = 10; class SellTickets implements Runnable{ Lock lock = new ReentrantLock(); @Override public void run() { while(tickets > 0){ try { lock.lock(); if(tickets <= 0){ return; } System.out.println(Thread.currentThread().getName()+" -->售出第 "+tickets+" 张票"); tickets--; } finally{ lock.unlock(); try{ Thread.sleep(100); }catch (InterruptedException e) { e.printStackTrace(); } } if(tickets <= 0){ System.out.println(Thread.currentThread().getName()+" -->售票结束!"); } } } } public static void main(String[] args) { SellTickets sell = new SellTicket2().new SellTickets(); Thread t1=new Thread(sell, "1号窗口"); Thread t2=new Thread(sell, "2号窗口"); Thread t3=new Thread(sell, "3号窗口"); Thread t4=new Thread(sell, "4号窗口"); t1.start(); t2.start(); t3.start(); t4.start(); } }