java线程安全被分为了几个等级类,但不管怎样总体来讲就是使线程之内和几个线程之间的事务操作具有原子性,数据具有正确性,一般来讲具有以下几种方法
目录
1.互斥同步
2.非阻塞同步
3.无同步方案
互斥同步时最常用的一种并发正确性保障手段。同步是指在多个线程中并发访问数据时,保证数据在同一时刻只被一个线程使用。互斥是实现同步的一种方式,临界区、互斥量、信号量都是实现互斥的主要方式。因此,互斥是方法,同步是目的,互斥是为了同步。
synchronized:在java中,最基本的互斥手段是synchronized关键字,需要注意的是,synchronized锁住的是对象或类,如果javachen程序中明确了synchronized(xx)的参数对象,那锁住的就是这个参数对象;如果没有明确指定,那就根据synchronized修饰的是实例方法还是类方法,去获取对应的对象实例或者class对象做为锁对象。与synchronized配对shiy使用的方法和关键字常常包括了wait(),notify(),notifyAll()。
重入锁(ReentrantLock):除synchronized外,还可使用ReentrantLock来实现同步,相比synchronized,ReentrantLock提供了一些高级功能,主要有下面三项:
等待可中断:某线程在等待锁的时候可以放弃等待,转而去处理其他的事情
公平锁:多个线程在等待一个锁时,必须按照申请锁的时间顺序来获得锁。synchronized中的锁是非公平的,释放的一瞬间任何线程都可能获得锁。ReentrantLock默认是非公平的,但可以通过带bool值得构造函数要求使用公平锁。
绑定多个条件:指一个ReentrantLock对象可以同时绑定多个对象,而synchronized中通过wait()和notify()可以实现一个条件的绑定,如果要实现duoy多于一个的绑定,不得不再添加一个锁了。
还有不同的是synchronized和ReentrantLock的效率问题了,ReentrantLock的效率相对较高。但官方再不断对synchronized进行优化,并且synchronized是原生de ,所以还是tich提倡synchronized能实现的情况下,优先考虑synchronized来实现同步。
互斥同步也称为阻塞同步(悲观的并发策略),将除抢到锁的线程的其他线程都给阻塞了,因此进行线程阻塞和唤醒会带来性能问题。非阻塞同步采用一种乐观的并发策略,即基于冲突检测的并发策略,先作操作,如果没有其他线程争共享数据,那就是成功了,如果有,再加锁。
乐观的并发策略是基于硬件指令集的,使用时就将其看做一个原子命令来用即可,常用的有:
在java中主要是使用atomic包中的方法,举个例子:
public class AtomicTest {
public static AtomicInteger race = new AtomicInteger(0);
public static void increase(){
race.incrementAndGet();
}
public static void main(String[] args) {
//创建20个线程来增加race
//考虑下如果是sychronized,是不是有很多重复的wait , notify
Thread[] threads = new Thread[20];
for(int i=0 ;i<20;i++){
threads[i]=new Thread(new Runnable() {
@Override
public void run() {
for(int i =0;i<1000;i++){
increase();
}
}
});
threads[i].start();
}
while(Thread.activeCount()>1)
Thread.yield();
System.out.println(race);
}
}
要保证线程安全,并不是一定就要进行同步,有些代码天生就是安全的,介绍下面两类:
可重入代码:如果一个方法,它的返回结果是可以预测的,只要输入了相同的参数就能返回相同的结果,即是可重用的。有一些特征,如不依赖于公用的系统资源、用到的状态量都由参数传入等。其实,我们在写大多数面向对象的方法时就满足这一条件。
线程本地存储:如果一段代码中所需要的数据必须与其他代码共享,看看这些数据的代码能不能在同一个线程内执行。如果一个变量要被多线程访问,可以使用volatile关键字表明其是“易变的”。如果要被某个线程独享,可以通过ThreadLocal类实现本地储存的功能。