线程安全性问题
多线程环境下
多个线程共享一个资源
对资源进行非原子性操作
线程所带来的风险
线程安全性问题
活跃性问题(死锁,饥饿,活锁)
性能问题
1 public class Sequence { 2 private int value; 3 4 5 public int getNext() { 6 return value ++; 7 } 8 9 // public synchronized int getNext() { //加synchronized,同一时刻,只有一个线程在执行 10 // return value ++; 11 // } 12 13 public static void main(String[] args) { 14 Sequence s = new Sequence(); 15 16 17 new Thread(new Runnable() { 18 19 @Override 20 public void run() { 21 while(true) { 22 System.out.println(Thread.currentThread().getName()+" "+s.getNext()); 23 try { 24 Thread.sleep(100); 25 } catch (InterruptedException e) { 26 // TODO Auto-generated catch block 27 e.printStackTrace(); 28 } 29 } 30 } 31 }).start(); 32 33 new Thread(new Runnable() { 34 35 @Override 36 public void run() { 37 while(true) { 38 System.out.println(Thread.currentThread().getName()+" "+s.getNext()); 39 try { 40 Thread.sleep(100); 41 } catch (InterruptedException e) { 42 // TODO Auto-generated catch block 43 e.printStackTrace(); 44 } 45 } 46 } 47 }).start(); 48 49 new Thread(new Runnable() { 50 51 @Override 52 public void run() { 53 while(true) { 54 System.out.println(Thread.currentThread().getName()+" "+s.getNext()); 55 try { 56 Thread.sleep(100); 57 } catch (InterruptedException e) { 58 // TODO Auto-generated catch block 59 e.printStackTrace(); 60 } 61 } 62 } 63 }).start(); 64 65 } 66 67 }
首先我们知道类的实例化对象是在堆内存中的,堆属于线程所共享的区域,程序计数器是线程独享的区域,value变量属于多个线程共享的区域。
0 -> 第一个线程执行后为 iadd 显示为一,但未(puffield)设置
同时第二个线程 getfield获取值,得到为0,抢到时间片执行 iadd显示为一
第一个线程抢到时间片 执行(puffield),为一
第二个线程执行(puffield),为一,即所谓线程安全性问题
synchronized 关键字提供了一种锁的机制,能够确保共享变量的互斥访问,从而防止数据数据不一致问题的出现
synchronized 关键字包括monitor enter 和monitor exit 两个JVM指令,它保证在任何时候任何线程执行到monitor enter 成功之前都必须从主内存中获取数据,而不是在缓存中,在monitor exit 运行成功之后,共享变量被更新后的值必须刷入主内存
synchronized 放在普通方法上,内置锁就是当前类的实例
修饰静态方法,内置锁是当前的Class字节码对象
修饰代码块
任何对象都可以作为锁,锁信息存在对象头中
对象头中的信息,
- Mark Word //
- Class Metadata Address
- Array Length
饥饿与公平
1,高优先级吞噬所有低优先级的CPU时间片2,线程被永久堵塞在一个等待进入同步块的状态3,等待的线程永远不被唤醒
如何尽量避免饥饿问题1,设置合理的优先级2,使用锁来代替synchronized(偏向锁,轻量级锁,重量级锁)
1 public class Demo1 { 2 public static void main(String[] args) { 3 4 5 Thread t1 = new Thread(new Target()); 6 Thread t2 = new Thread(new Target()); 7 8 t1.setPriority(10); //1<=5<=10 9 t2.setPriority(1); 10 11 t1.start(); 12 t2.start(); 13 } 14 }
public class Target implements Runnable{ @Override public void run() { while(true) { System.out.println(Thread.currentThread().getName()+" ... "); } } }
//执行如下:
//Thread-0 ...
//Thread-0 ...
//Thread-0 ...
//Thread-0 ...
偏向锁
每次获取锁和释放锁会浪费资源
很多情况下,竞争锁不是由多个线程,而是由一个线程在使用。
第一次线程进入后,会检查锁标志位,是否是偏向锁,检查线程id,如果和当前线程id一致,就不用再获取
第二次进入,其并没有释放锁,一直在运行,接着运行,没有锁的获取和释放的过程,锁的撤销则等到竞争出现才释放索的机制,适用于只有一个线程访问代码块的场景
轻量级锁
同时让多个线程进入同步代码块中,同时获取锁,JVM会在当前线程的栈帧中创建用于存储锁记录的空间,并把对象头中的Mark Word复制到锁记录空间中,开始竞争,成功之后锁标志位改为轻量级锁,轻量级锁所适应的场景是线程交替执行同步块的场合,如果存在同一时间访问同一锁的场合,就会导致轻量级锁膨胀为重量级锁。
线程安全性问题总结
出现线程安全性问题的条件
在多线程的环境下
必须有共享资源
对共享资源进行非原子性操作
解决线程安全性问题的途径
synchronized(偏向锁,轻量级锁,重量级锁)
volatile
jdk提供的原子类
使用Lock(共享锁,排它锁)
完