线程同步
案例 卖票
public class SellTicket implements Runnable{ private int tickets = 100; //重写run办法 实现卖票 @Override public void run(){ while(true){ if(tickets>0){//票数大于0 就卖票 并且告知是哪个窗口卖的 sout(Thread.currentThread().getName()+"正在发售第"+tickets+"张票"); tickets --; } } //主程序SellTicketDemo//创立SellTicket 对象 SellTicket st= new SellTicket ();//创立三个线程类的对象,把SellTicket 对象作为构造方法的参数,并且给出对应的窗口名称Thread t1 = new Thread (st,name:"窗口1");Thread t2 = new Thread (st,name:"窗口2");Thread t3 = new Thread (st,name:"窗口3");//启动线程t1.start();t2.start();t3.start();
卖票案例的思考
public class SellTicket implements Runnable{ private int tickets = 100; //重写run办法 实现卖票 @Override public void run(){ while(true){ if(tickets>0){//票数大于0 就卖票 并且告知是哪个窗口卖的 //通过sleep办法来模拟出票工夫 Thread.sleep(millis:100);//alt enter 捕捉异样 sout(Thread.currentThread().getName()+"正在发售第"+tickets+"张票"); tickets --; } } //主程序SellTicketDemo//创立SellTicket 对象 SellTicket st= new SellTicket ();//创立三个线程类的对象,把SellTicket 对象作为构造方法的参数,并且给出对应的窗口名称Thread t1 = new Thread (st,name:"窗口1");Thread t2 = new Thread (st,name:"窗口2");Thread t3 = new Thread (st,name:"窗口3");//启动线程t1.start();t2.start();t3.start();
下面那个在卖票类外面加了sleep函数,会呈现三个窗口卖同一张票或者卖票是正数
三个窗口卖同一张票
tickets = 100;t1 t2 t3假如t1线程抢到cpu的执行权。而后t1线程劳动100ms,在t1劳动的时候t2线程抢到了cpu的执行权,t2线程就开始执行,也是从tickets = 100执行,100>0,走进循环,而后t2线程劳动100ms,同理,在t2劳动的时候t3线程抢到了cpu的执行权,t3线程就开始执行,也是从tickets = 100执行,100>0,走进循环,而后t3线程劳动100ms而后走到输入,假如线程依照劳动的程序醒过来,t1抢到cpu的执行权,在控制台输入窗口1正在发售第100张票,而后就应该执行tickets --,然而如果在这会正好t2抢到cpu的执行权,在控制台输入窗口2正在发售第100张票,同理,如果在这会正好t3抢到cpu的执行权,在控制台输入窗口3正在发售第100张票,如果这三个线程还是依照程序来,那么这里前面就会执行三次tickets --,最终票就会变成97
售票呈现正数
tickets = 1;t1 t2 t3假如t1线程抢到cpu的执行权。而后t1线程劳动100ms,在t1劳动的时候t2线程抢到了cpu的执行权,t2线程就开始执行,也是从tickets = 1执行,100>0,走进循环,而后t2线程劳动100ms,同理,在t2劳动的时候t3线程抢到了cpu的执行权,t3线程就开始执行,也是从tickets = 1执行,1>0,走进循环,而后t3线程劳动100ms而后走到输入,假如线程依照劳动的程序醒过来,t1抢到cpu的执行权,在控制台输入窗口1正在发售第1张票,假如t1持续领有cpu的使用权,则就会执行tickets --,tickets就会变成0。而后就进不去循环了,而后与此同时,正好t2抢到cpu的执行权,在控制台输入窗口2正在发售第0张票,假如t2持续领有cpu的使用权,则就会执行tickets --,tickets就会变成-1,而后就进不去循环了,同理,正好t3抢到cpu的执行权,在控制台输入窗口2正在发售第-1张票,假如t3持续领有cpu的使用权,则就会执行tickets --,tickets就会变成-2,
卖票案例数据安全问题的解决
为什么呈现问题(也是判断多线程程序是否会有数据安全问题的规范)
- 是否是多线程环境
- 是否共享数据
- 是否有多条语句操作共享数据
public class SellTicket implements Runnable{ private int tickets = 100; //重写run办法 实现卖票 @Override public void run(){ while(true){ if(tickets>0){//票数大于0 就卖票 并且告知是哪个窗口卖的 //通过sleep办法来模拟出票工夫 Thread.sleep(millis:100);//alt enter 捕捉异样 sout(Thread.currentThread().getName()+"正在发售第"+tickets+"张票"); tickets --; } } //主程序SellTicketDemo//创立SellTicket 对象 SellTicket st= new SellTicket ();//创立三个线程类的对象,把SellTicket 对象作为构造方法的参数,并且给出对应的窗口名称Thread t1 = new Thread (st,name:"窗口1");Thread t2 = new Thread (st,name:"窗口2");Thread t3 = new Thread (st,name:"窗口3");//启动线程t1.start();t2.start();t3.start();
如何解决多线程平安问题?
- 根本思维:让程序没有平安问题的环境 个别解决下面第三个问题
怎么实现
- 把多条语句操作共享数据的代码锁起来,然任意时刻只能有一个线程执行
- java提供了同步代码块的形式解决
同步代码块
public class SellTicket implements Runnable{ private int tickets = 100; private Object obj = new Object();//创立一个新的对象 ,不论是什么线程进来都是执行obj 是同一把锁 //重写run办法 实现卖票 @Override public void run(){ while(true){ synchronized(obj){ if(tickets>0){//票数大于0 就卖票 并且告知是哪个窗口卖的 //通过sleep办法来模拟出票工夫 Thread.sleep(millis:100);//alt enter 捕捉异样 sout(Thread.currentThread().getName()+"正在发售第"+tickets+"张票"); tickets --; } } } }}
tickets = 100;t1 t2 t3假如t1线程抢到cpu的执行权,t1进来胡,就会执行synchronized把这段代码锁起来,锁完之后,就会执行if语句,而后t1线程劳动100ms,假如在t1劳动的时候t2线程抢到了cpu的执行权,t2线程而后t2线程发现被锁住了,就只能等着,而后等t1劳动好了,t1持续向下执行,在控制台输入窗口1正在发售第100张票,而后执行tickets --,tickets就会变成99,而后t1就进去了,t1进去之后这段代码的锁就会被开释了而后三个线程再持续抢cpu的使用权
同步办法
public class SellTicket implements Runnable{ private int tickets = 100; private Object obj = new Object();//创立一个新的对象 ,不论是什么线程进来都是执行obj 是同一把锁 private int x= 0; //重写run办法 实现卖票 @Override public void run(){ while(true){ if(x%2==0){ synchronized(obj){ if(tickets>0){//票数大于0 就卖票 并且告知是哪个窗口卖的 //通过sleep办法来模拟出票工夫 Thread.sleep(millis:100);//alt enter 捕捉异样 sout(Thread.currentThread().getName()+"正在发售第"+tickets+"张票"); tickets --; } } }else{ synchronized(obj){ if(tickets>0){//票数大于0 就卖票 并且告知是哪个窗口卖的 //通过sleep办法来模拟出票工夫 Thread.sleep(millis:100);//alt enter 捕捉异样 sout(Thread.currentThread().getName()+"正在发售第"+tickets+"张票"); tickets --; } } } x++; } }} //主程序SellTicketDemo//创立SellTicket 对象 SellTicket st= new SellTicket ();//创立三个线程类的对象,把SellTicket 对象作为构造方法的参数,并且给出对应的窗口名称Thread t1 = new Thread (st,name:"窗口1");Thread t2 = new Thread (st,name:"窗口2");Thread t3 = new Thread (st,name:"窗口3");//启动线程t1.start();t2.start();t3.start();
下面的if和else外面管制的都是一样的也是同一把锁,能够写成办法
同步办法就是把synchronized关键字加到办法上
- 格局:修饰符 synchronized 返回值类型 办法名(办法参数){ }
而后下面那个两种办法的对象应该是一样的 都是办法里的this对象 同步办法的锁的对象是 this这个对象
private synchronized void sellTickett(){ if(tickets>0){//票数大于0 就卖票 并且告知是哪个窗口卖的 //通过sleep办法来模拟出票工夫 Thread.sleep(millis:100);//alt enter 捕捉异样 sout(Thread.currentThread().getName()+"正在发售第"+tickets+"张票"); tickets --; }}
同步静态方法就是把synchronized关键字加到静态方法上
- 格局:修饰符 static synchronized 返回值类型 办法名(办法参数){ }
而后下面那个两种办法的对象应该是一样的 都是办法里的 类名.class 对象 - 同步静态方法的锁的对象是 类名.class这个对象
线程平安的类
- StringBuffer
线程平安,可变的字符序列
从JDK5开始,被StringBuilder代替,通常应该应用StringBuilder类,因为它反对所有雷同的操作,但它更快,因为它不执行同步 - Vector
从java2平台v1.2开始,该类改良了List接口,使其成为JavaCollectionFramework的成员。与新的汇合实现不同,Vector被同步,如果不须要线程平安的实现,倡议应用ArrayList代替Vector - Hashtable
该类实现了一个哈希表,它将键映射到值,任何非null对象都能够用作键或者值
从java2平台v1.2开始,该类进行了改良,实现了Map接口,使其成为JavaCollectionFramework的成员。与新的汇合实现不同,Hashtable被同步。如果不须要线程平安的实现,倡议应用HashMap代替Hashtable
StringBuffer sb = new StringBuffer();//加了synchronized关键字,所以也是同步办法,是线程平安的StringBuilder sb2 = new StringBuilder();//办法没有加synchronized关键字,所以是线程不平安Vector<String> v = new Vector<String>();//加了synchronized关键字,所以也是同步办法,是线程平安的ArrayList<String> array = new ArrayList<String>();//办法没有加synchronized关键字,所以是线程不平安Hashtable<String,String> ht = new Hashtable<String,String>();//加了synchronized关键字,所以也是同步办法,是线程平安的HashMap<String,String> hm = new HashMap<String,String>();//办法没有加synchronized关键字,所以是线程不平安//将线程不平安变成线程平安 所有的都是相似的示意List<String> list = Collections.synchronizedlist(new ArrayList<String>())
Lock锁
public class SellTicket implements Runnable{ private int tickets = 100; private Lock lock = new Reentrantlock(); //重写run办法 实现卖票 @Override public void run(){ while(true){ lock.lock(); if(tickets>0){//票数大于0 就卖票 并且告知是哪个窗口卖的 //通过sleep办法来模拟出票工夫 Thread.sleep(millis:100);//alt enter 捕捉异样 sout(Thread.currentThread().getName()+"正在发售第"+tickets+"张票"); tickets --; } lock.unlock(); } //主程序SellTicketDemo//创立SellTicket 对象 SellTicket st= new SellTicket ();//创立三个线程类的对象,把SellTicket 对象作为构造方法的参数,并且给出对应的窗口名称Thread t1 = new Thread (st,name:"窗口1");Thread t2 = new Thread (st,name:"窗口2");Thread t3 = new Thread (st,name:"窗口3");//启动线程t1.start();t2.start();t3.start();
通过lock和unlock加锁和开释锁
个别会用如下表是,用try catch