线程安全问题?安全
什么是线程安全问题?简单的说,当多个线程在共享同一个变量,作读写的时候,会因为其余线程的干扰,致使数据偏差,就会出现线程安全问题。多线程
好比说,多个窗口同时卖票这个案例:并发
1 public class ThreadTrain2 implements Runnable { 2 private int tickets = 50; 3 @Override 4 public void run() { 5 while(tickets > 0){ 6 if (tickets > 0) { System.out.println(Thread.currentThread().getName() + "卖了第" + (50 - tickets + 1) + "张票"); 7 tickets--; 8 } 9 } 10 } 11 } 12 public static void main(String[] args) { 13 ThreadTrain2 tt = new ThreadTrain2(); 14 Thread th1 = new Thread(tt, "1号窗口"); 15 Thread th2 = new Thread(tt, "2号窗口"); 16 th1.start(); 17 th2.start(); 18 } 19 }
模拟两个窗口共同卖50张票,什么都不考虑,按照上面的写法,运行的结果有时候并非咱们想要的,会彻底乱了套。ide
使用多线程同步(synchronized)或者加锁lock函数
什么是多线程同步?就是当多个线程共享同一个资源时,不会受到其余线程的干扰。this
为何这两种方法能够解决线程的安全问题?spa
当把可能发生冲突的代码包裹在synchronized或者lock里面后,同一时刻只会有一个线程执行该段代码,其余线程必须等该线程执行完毕释放锁之后,才能去抢锁,得到锁之后,才拥有执行权,这样就解决的数据的冲突,实现了线程的安全。线程
卖票的案例同步后为:对象
1 public class ThreadTrain2 implements Runnable { 2 private int tickets = 50; 3 private static Object obj = new Object();//锁的对象,能够是任意的对象 4 @Override 5 public void run() { 6 while(tickets > 0){ 7 synchronized (obj) {// 同步代码块 8 if (tickets > 0) { 9 System.out.println(Thread.currentThread().getName() + "卖了第" + (50 - tickets + 1) + "张票"); 10 tickets--; 11 } 12 } 13 } 14 } 15 public static void main(String[] args) { 16 ThreadTrain2 tt = new ThreadTrain2(); 17 Thread th1 = new Thread(tt, "1号窗口"); 18 Thread th2 = new Thread(tt, "2号窗口"); 19 th1.start(); 20 th2.start(); 21 } 22 } 23
上面是同步代码块的加锁方式,能够解决线程安全问题。同时,还有一种同步函数的方式,就是在方法上直接加synchronized,能够实现一样的效果,那么如今有一个问题,在方法上加synchronized修饰,锁的对象是什么呢???this。。下面来验证一下为何是this:blog
点击+号查看代码,代码中的执行结果是绝对正确的,咱们是采用一个线程使用同步代码块,另外一个线程使用同步函数的方式,看是否会发生数据错误,做为对比,下面的代码中同步代码块咱们不使用this,而是使用obj这个对象:
显然,这段代码最后会出现数据冲突的状况,由于两个线程拿到的不是同一把锁,也证实了同步函数锁的是this。
明白了同步函数的锁是this,那么加上static之后,锁的对象会不会发生改变,仍是依然是this???
先锁this,验证是不是this:
出现了数据错误,这里咱们不作猜想,只作验证,静态的同步函数锁的是当前类的字节码文件,代码验证:
同步中嵌套同步,锁没有来得及释放,一直等待,就致使死锁。
下面这段代码,多运行几回就会出现死锁,思路是开启两个线程,让这两个线程执行的代码获取的锁的顺序不一样,第一个线程须要先得到obj对象锁,而后再得到this锁,才能够执行代码,而后释放两把锁。线程2须要先得到this锁,再获取obj对象锁才可执行代码,而后释放两把锁。可是,当线程1得到了obj锁以后,线程2得到了this锁,这时候线程1须要得到this锁才可执行,可是线程2也没法获取到obj对象锁执行代码并释放,因此两个线程都拿着一把锁不释放,这就产生了死锁。
原子性就是在执行一个或者多个操做的过程当中,要么所有执行完不被任何因素打断,要么不执行。好比银行转帐,A帐户减去100元,B帐户必须增长100元,对这两个帐户的操做必须保证原子性,才不会出现问题。还有好比:i=i+1的操做,须要先取出i,而后对i进行+1操做,而后再给i赋值,这个式子就不是原子性的,须要同步来实现数据的安全。
原子性就是为了保证数据一致,线程安全。
当多个线程访问同一个变量时,一个线程修改了变量的值,其余的线程能当即看到,这就是可见性。
这里讲一下Java内存模型?简称JMM,决定了一个线程与另外一个线程是否可见,包括主内存(存放共享的全局变量)和私有本地内存(存放本地线程私有变量)
本地私有内存存放的是共享变量的副本,线程操做共享变量,首先操做的是本身本地内存的副本,当同一时刻只有一个线程操做共享变量时,该线程操做完毕本地内存,而后会刷新到主内存,而后主内存会通知另外一个线程,进而更新;可是若是同一时刻有多个线程操做共享变量,会来不及更新主内存进而通知其余线程更新变量,就会出现冲突问题。
就是程序的执行顺序会按照代码前后顺序进行执行,通常状况下,处理器因为要提升执行效率,对代码进行重排序,运行的顺序可能和代码前后顺序不一样,可是结果同样。单线程下不会出现问题,多线程就会出现问题了。
保证可见性,可是不保证原子性。
下面这个案例10个线程共享同一个count,进行+1操做:
public class VolatileTest extends Thread{ private volatile static int count = 0; @Override public void run() { for (int i = 0; i < 100; i++) { count++; } System.out.println(Thread.currentThread().getName()+":"+count); } public static void main(String[] args) { VolatileTest[] list = new VolatileTest[10]; for (int i = 0; i < list.length; i++) { list[i] = new VolatileTest(); } for (int i = 0; i < list.length; i++) { list[i].start(); } } }
多运行几回,就会出现最后结果有不到1000的状况,也就证实了volatile不会保证原子性。
保证原子性,jdk1.5以后,并发包提供了不少原子类,例如AtomicInteger :
public class VolatileTest2 extends Thread{ private static AtomicInteger count = new AtomicInteger(); @Override public void run() { for (int i = 0; i < 100; i++) { count.incrementAndGet(); } System.out.println(Thread.currentThread().getName()+":"+count.get()); } public static void main(String[] args) { VolatileTest2[] list = new VolatileTest2[10]; for (int i = 0; i < list.length; i++) { list[i] = new VolatileTest2(); } for (int i = 0; i < list.length; i++) { list[i].start(); } } }
AtomicInteger解决了同步, 最后的结果最大的确定是1000