java--线程安全
线程安全
如果有多个线程在同时运行,而这些线程可能会同时运行这段代码。
程序每次运行结果和单线程运行的结果是一样的,而且其他的变量的值也和预期的是一样的,就是线程安全的。
案例:
卖票(只能卖100张票)
初始:
public static void main(String[] args) { //创建票对象 Ticket ticket = new Ticket(); //创建3个窗口 Thread t1 = new Thread(ticket, "窗口1"); Thread t2 = new Thread(ticket, "窗口2"); Thread t3 = new Thread(ticket, "窗口3"); t1.start(); t2.start(); t3.start(); } }
public class Ticket implements Runnable { //共100票 int ticket = 100; @Override public void run() { //模拟卖票 while(true){ if (ticket > 0) { //模拟选坐的操作 try { Thread.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + "正在卖票:" + ticket--); } } }
结果:
运行结果发现:上面程序出现了问题
票出现了重复的票
错误的票 0、-1
总结:
其实,线程安全问题都是由全局变量及静态变量引起的。若每个线程中对全局变量、静态变量只有读操作,而无写操作。
一般来说,这个全局变量是线程安全的;若有多个线程同时执行写操作,一般都需要考虑线程同步,否则的话就可能影响线程安全。
-1和0的出现原因:
当刚刚判断完还有票时CPU资源被其它线程占用进入休眠状态,当有空闲资源在进行时就不会再判断直接进行了减票操作就会出现0和-1的状况
解决方式:
线程同步
利用(Synchronized)
线程同步的方式有两种:
方式1:同步代码块
方式2:同步方法
同步代码块(上厕所原理)
在代码块声明上 加上synchronized
synchronized (锁对象) { 可能会产生线程安全问题的代码 }
注意:
同步代码块中的锁对象可以是任意的对象;但多个线程时,要使用同一个锁对象才能够保证线程安全。
public class Tickets implements Runnable{ private /*static*/ int ticket=100; //对象锁 private Object obj=new Object(); public void run() { while(true){ synchronized (obj) { if(ticket>0){ try { Thread.sleep(10); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } System.out.println(Thread.currentThread().getName()+"出售第"+ticket--+"张票"); }else{ return; } } } }
原理:线程运行首先要拿到锁(obj)才能进行下面的程序,如果锁被占用那么只能等待,线程循环运行完后自动归还锁供下一个线程循环运行。
同步方法
在方法声明上加上synchronized
public synchronized void method(){
可能会产生线程安全问题的代码
}
注意:
①同步方法中的锁对象是 this
②同步锁别名:对象锁,对象监视器
演示:
private /*static*/ int ticket=100; public void run() { while(true){ sale(); } }
public /*static*/ synchronized void sale(){ if(ticket>0){ try { Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName()+"出售第"+ticket--+"张票"); }else{ return; } }
静态同步方法:
在方法声明上加上static synchronized
public static synchronized void method(){ 可能会产生线程安全问题的代码 }
静态同步方法中的锁对象是 类名.class
补充:
StringBuilder和StringBuffer真正区别体现:
StringBuffer(方法都有synchronized,都是同步的方法,但这样就会降低速度)
新方式:
Lock接口
Lock 实现提供了比使用 synchronized 方法和语句可获得的更广泛的锁定操作。
常用方法:
演示(还是拿上面的卖票为例):
public class MewTicket implements Runnable{ private int ticket=100; private Lock lock=new ReentrantLock(); public void run() { while(true){ lock.lock(); if(ticket>0){ try { Thread.sleep(10); System.out.println(Thread.currentThread().getName()+"出售第"+ticket--+"张票"); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); }finally{ //释放锁 lock.unlock(); } } } } }
死锁现象
同步锁使用的弊端:当线程任务中出现了多个同步(多个锁)时,如果同步中嵌套了其他的同步。
这时容易引发一种现象:程序出现无限等待,这种现象我们称为死锁。这种情况能避免就避免掉。
synchronzied(A锁){ synchronized(B锁){ } }
定义锁对象:
public class lockA { private lockA(){ } public static final lockA locka=new lockA(); }
public class lockB { private lockB(){ } public static final lockB lockb=new lockB(); }
任务:
public class deadlock implements Runnable{ private int i=0; public void run() { while(true){ if(i%2==0){ //偶数先进A再进B synchronized (lockA.locka) { System.out.println("if......lockA"); synchronized (lockB.lockb) { System.out.println("if......lockB"); } } }else{ //奇数先进B再进A synchronized (lockB.lockb) { System.out.println("else......lockB"); synchronized (lockA.locka) { System.out.println("else......lockA"); } } } i++; } } }
创建线程:
public static void main(String[] args) { deadlock d1=new deadlock(); Thread t1=new Thread(d1); Thread t2=new Thread(d1); t1.start(); t2.start(); }
等待唤醒机制
线程之间的通信:多个线程在处理同一个资源,但是处理的动作(线程的任务)却不相同。通过一定的手段使各个线程能有效的利用资源。
而这种手段即—— 等待唤醒机制。
等待唤醒机制所涉及到的方法:
①wait() :等待,将正在执行的线程释放其执行资格 和 执行权,并存储到线程池中。
②notify():唤醒,唤醒线程池中被wait()的线程,一次唤醒一个,而且是任意的。
③notifyAll(): 唤醒全部:可以将线程池中的所有wait() 线程都唤醒。
其实,所谓唤醒的意思就是让 线程池中的线程具备执行资格。必须注意的是,这些方法都是在同步中才有效。
同时这些方法在使用时必须标明所属锁,这样才可以明确出这些方法操作的到底是哪个锁上的线程。
举例:
分析:
1.当input发现Resource中没有数据时,开始输入,输入完成后,叫output来输出。如果发现有数据,就wait();
2.当output发现Resource中没有数据时,就wait() ;当发现有数据时,就输出,然后,叫醒input来输入数据。
演示:
public class Resouce { public String name; public String sex; public boolean flag=false; }
输入:
public class Input implements Runnable{ private Resouce r; public Input(Resouce r) { super(); this.r = r; } private int i=0; public void run(){ while(true){ //注意要用同一个锁对象,保证同步(r) synchronized (r) { //标记为true if(r.flag==true){ try { //这些方法在使用时必须标明所属锁 r.wait(); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } if(i%2==0){ r.name="张三"; r.sex="男"; }else{ r.name="李四"; r.sex="nv"; } //改变flag,唤醒Output r.flag=true; r.notify(); } i++; } } }
输出:
public class Output implements Runnable{ private Resouce r; public Output(Resouce r) { super(); this.r = r; } public void run() { while(true){ synchronized (r) { //标记为false就wait if(r.flag==false){ try { r.wait(); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } //如果为true就打印 System.out.println(r.name+"..."+r.sex); //更改flag唤醒input r.flag=false; r.notify(); } } } }
测试:
public class text { public static void main(String[] args) { Resouce r=new Resouce();//定义同一个对象 Input in=new Input(r); Output out=new Output(r); Thread tin=new Thread(in); Thread tout=new Thread(out); tin.start(); tout.start(); } }
练习:
有一个抽奖池,该抽奖池中存放了奖励的金额,该抽奖池用一个数组int[] arr = {10,5,20,50,100,200,500,800,2,80,300};
创建两个抽奖箱(线程)设置线程名称分别为“抽奖箱1”,“抽奖箱2”,随机从arr数组中获取奖项元素并打印在控制台上,格式如下:
抽奖箱1 又产生了一个 10 元大奖
抽奖箱2 又产生了一个 100 元大奖
。。。。。。
演示:
定义奖池:
public class jiangchi { public int[] arr={10,5,20,50,100,200,500,800,2,80,300}; public boolean flag=false; }
抽奖箱1:
public class xiang1 implements Runnable{ private jiangchi j; Random a=new Random();//创建随机数对象 public xiang1(jiangchi j) { super(); this.j = j; } public void run() { while(true){ synchronized (j) { if(j.flag==true){ try { j.wait(); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } }else{ //随机抽取奖品 int arr[]=j.arr; int rdm=0; int random = a.nextInt(11); rdm=random; System.out.println("抽奖箱1 又产生了一个 "+arr[rdm]+"元大奖"); j.flag=true; j.notify(); } } } } }
抽奖箱2:
public class xiang2 implements Runnable{ private jiangchi j; Random a=new Random();//创建随机数对象 public xiang2(jiangchi j) { super(); this.j = j; } public void run() { while(true){ synchronized (j) { if(j.flag==false){ try { j.wait(); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } }else{ //随机抽取奖品 int arr[]=j.arr; int rdm=0; int random = a.nextInt(11); rdm=random; System.out.println("抽奖箱2 又产生了一个 "+arr[rdm]+"元大奖"); j.flag=false; j.notify(); } } } } }
测试:
public static void main(String[] args) { jiangchi jc=new jiangchi(); xiang1 x1=new xiang1(jc); xiang2 x2=new xiang2(jc); Thread xiang1=new Thread(x1); Thread xiang2=new Thread(x2); xiang1.start(); xiang2.start(); }