二,线程安全
一,三类线程安全问题
当多个线程同时访问一个对象,如果不用考虑线程之间的协作,不需要额外的同步控制,就可以得到正确的结果,那么这个对象是线程安全的。
常见的线程安全错误有三种:
1,运行结果错误
public static void main(String[] args) throws Exception { Runnable r = ()->{ for(int i = 0; i < 1000; i++){ a++; } }; Thread t1 = new Thread(r); Thread t2 = new Thread(r); t1.start(); t2.start(); t1.join(); t2.join(); System.out.println(a); }
由于a++非原子操作,导致a最终的结果不是2000
2,发布和初始化导致线程安全问题
如果在线程中发布或初始化变量,由于无法保证线程开始运行的时间,导致数据没有按照顺序执行。
3,活跃性问题
分为死锁、活锁、饥饿
死锁,当两个线程相互拥有对方等待的资源,同时又等待对方的资源。这样就造成死锁。
Object o1 = new Object(),o2 = new Object(); public void thread1(){ synchronized (o1) { try { Thread.sleep(1000); synchronized(o2){ } } catch (InterruptedException e) { e.printStackTrace(); } } } public void thread2(){ synchronized (o2) { try { Thread.sleep(1000); synchronized(o1){ } } catch (InterruptedException e) { e.printStackTrace(); } } } public static void main(String[] args) throws Exception { P3 p = new P3(); new Thread(()->{p.thread1();}).start(); new Thread(()->{p.thread2();}).start(); }
活锁,指发送在循环中的错误,一直执行,但是一直失败
饥饿,线程需要的资源始终无法得到,而导致线程一直无法运行。
二,需要注意线程安全的场景
1,访问共享变量和资源
比如多个线程同时访问static变量
2,依赖时序的操作
比如检查-执行操作。
if(x == 7)
x = 2 * x;
先检查,后执行,这两步操作不是原子的,如果多个线程同时运行,就需要加锁保证原子性。
3,不同数据之间存在绑定关系
4,对方没有声明自己是线程安全的
三,为什么多线程会带来性能问题
1,线程调度的开销
上下文切换,缓存失效
2,线程协作的开销