线程安全问题

关注数据在多线程并发环境下是否安全。

1、什么时候数据会在多线程并发的环境下会存在安全问题?

三个条件:

条件1:多线程并发

条件2:有共享数据

条件3:共享数据有修改行为

满足以上三个条件后,就会存在线程安全问题。

2、怎么样解决线程安全问题?

当多线程并发的环境下,有共享数据,并且这个数据还会被修改,此时就会存在安全问题。

如何解决?

线程排队执行。(不能并发) 用排队解决线程安全问题。

这种机制被称为: 线程同步机制。

专业术语叫: 线程同步,实际上就是线程不能并发了,必须排队执行

解决线程安全问题: 使用线程同步机制。

线程同步就是线程排队了,线程排队就会牺牲一部分效率。但是,没办法,数据安全才是第一位,只有数据安全了,我们才能谈效率,数据不安全,就没有效率那回事了。

3、说到线程同步,就涉及到:

异步编程模型:

线程t1和线程t2,各自执行各自的,t1不管t2,t2也不管t1,谁也不需要等谁,这种编程模型就叫作:异步编程模型。

其实就是:多线程并发(效率较高)

同步编程模型:

线程t1和线程t2,在线程t1执行的时候,必须等待线程t2执行结束,或者说线程t2执行时,必须等待线程t1执行结束,俩个线程之间发生了等待关系,这就是同步编程模型。

线程同步:

同步就是排队。由于同一进程的多个线程共享同一块存储空间,再带来方便的同时,也带来了访问冲突问题,为保证数据在方法中被访问时的正确性,于是在访问时加入了 锁机制 synchronized,当一个对象获得对象的排它锁,独占资源,其他线程必须等待,使用后释放锁即可。但是存在以下问题:

  • 一个线程持有锁会导致其他所有需要此锁的线程挂起;
  • 在多线程的竞争下,加锁,释放锁会导致比较多的上下文切换和调度延时,引起性能问题;
  • 如果一个优先级高的线程等待一个优先级低的线程释放锁,会导致优先级到置,引起性能问题;
4、Java中有三大变量

**实例变量:**在堆中。

**静态变量:**在方法去中。

**局部变量:**在栈中 。

以上三大变量中:

局部变量永远都不会存在线程安全问题,因为局部变量不共享。(一个线程一个栈)

实例变量在堆中,堆只有一个。

静态变量在方法区中,方法区只有一个。

堆和方法区都是多线程共享的,所以可能存在线程安全问题 。

如果使用局部变量的话:

建议使用:StringBuilder。

因为局部变量不存在线程安全问题,选择StringBuilder。

StringBuffer效率较低。

ArrayList是非线程安全的。

Vector是线程安全的。

HashMap , HashSet是非线程安全的。

Hashtable是线程安全的。

5、synchronized有三种写法:

第一种:同步代码块

灵活

synchronized (线程共享对象 Obj)

同步代码块

}

**Obj **称之为 同步监视器 。 Obj 可以是任何对象,但是推荐使用共享对象作为同步监视器

第二种:在实例方法上使用synchronized

表示共享对象一定是this

并且同步代码块是整个方法体

第三种:在静态方法上使用synchronized

表示找类锁

类锁永远只有一把

就算创建了10000个对象,类锁也只有一把

  • 同步方法中无需指定同步监视器,因为同步方法的同步监视器就是 this ,就是这个对象本身,

或者class。

  • 对象锁:一个对象一把锁,100个对象100把锁。
  • 类锁:100个对象也只有一把类锁。
6、Lock(锁)

扩展

  • JDK 5.0开始,Java提供了更强大的线程同步机制——通过显示定义同步锁对象来实现同步。同步锁使用Lock对象充当。
  • java.util.concurrent.locks.Lock 接口是控制多个线程对共享资源进行访问的工具。锁提供了对象的独占访问,每一次只能有一个线程对Lock对象加锁,线程开始访问共享资源之前应先获得Lock对象。
  • ReentrantLock 类实现了 Lock ,它拥有与synchronized 相同的并发性和内存语义,在实现线程安全控制中,常用 ReentrantLock ,可显示加锁,释放锁。
//定义lock 锁
	private final ReentrantLock lock = new ReentrantLock();
    public void method() {
        lock.lock();  //加锁
        try{
            .....
        }
        finally{
            lock.unlock(); //解锁
        }
    }
Lock 与 synchronized 的对比
  • Lock是显式锁(手动开启和关闭锁,不能忘记关锁)synchronized是隐式锁,出了作用域自动释放
  • Lock锁只有代码块锁,synchronized有代码块锁和方法锁
  • 使用Lock锁,JVM将花费较少的时间来调度线程,性能更好,并且有更好的扩展性(提高更多的子类)
7、以后在开发中如何解决线程安全问题?

一上来就使用线程同步?synchronized

不是,synchronized会让程序的执行效率变低,用户体验不佳。系统的用户吞吐量降低,用户体验差,在不得已的情况下再选择线程同步机制。

第一种方案:尽量使用局部变量代替实例变量和静态变量。

第二种方案:如果必须是实例变量,那么就可以考虑创建多个对象,这样实例变量的内存就不共享了,(一个线程对应一个对象,100线程对应100对象,对象不共享,就没有数据安全问题了)

第三种方案:如果不能使用局部变量,也不能创建多个对象,这个时候就选择synchronized

线程同步机制。