线程安全
如果一段代码本身封装了一些安全机制(如同步),而调用者在使用的时候也无需采取任何保护措施来保证多线程的正确调用,那么代码就是线程安全的。
Java的线程安全
前提是多线程之间共享资源,有竞争关系才讨论是否是线程安全的,若不存在共享当然安全。java各种操作共享数据根据安全程度分为5类.
不可变
不可变的对象必然线程安全,实现不可变可以使用final关键字修饰,例如我们的String类或者Integer类等
如果共享的数据是这种类型就是安全的,如果共享一个基本数据类型,我们可以把这个变量定义为final,就可以是安全的
绝对线程安全
定义就是多线程并发的情况下不需要加任何同步机制也能保证结果的正确性,这个一般是实现不了的,区别于下面的相对线程安全,一般在java API标注线程安全的类比如Vetor,HashTable都是相对线程安全的。比如Vector类中他的add(),get(),size()方法都加了同步锁,但是多线程情况下还是不安全的,必须在调用的时候在加安全同步机制。
比如在类中定义一个Vector对象,有两个线程在run方法中对这个变量进行操作,一个线程删除,一个线程拿出打印,那么就不安全,我们必须在这两个线程对Vector对象操作时,也就是进入run方法之前使用synchronized锁定这个对象。
相对线程安全
如上面所说,一般都是相对线程安全的,只需要保证在对象单独操作的时候是线程安全的,那就认为是线程相对安全。
线程兼容
这就是我们常说的某个类不是线程安全的,通过调用端来正确同步
线程对立
无论调用端采取什么手段,都无法在多线程中执行的代码就是线程对立的。很少(Thread的suspend()和resume()弃用,挂起和继续执行方法)
同步控制实现线程安全
(1)synchronized
在java代码中我们可以通过synchronized同步代码块,那么在编译之后,会在同步的代码块中分别生成monitorenter和monitorexit两个字节码指令,所以我们说synchronized是基于JVM的,这两个字节码指令都需要有引用类型的参数来执行锁定的是哪个对象。如果在synchronized中我么指定了参数比如synchronized(this){},那么这两个字节码指令的参数就是synchronized的参数,如果synchronized没有参数,那么会根据synchronized修饰的实例方法还是类方法来对对象的实例(this)或者Class对象作为锁对象。
执行monitorenter指令时,虚拟机首先判断这个对象是否被锁定,如果未被锁定或者这个线程已经拥有这个锁(synchronized西同步的代码块是可重入的,就是线程自己不会阻塞自己),就把锁的计数器加1,那么执行monitorexit计数器减1,计数器为0是释放锁。
那么当一个线程获得锁就会阻塞其他来竞争锁资源的线程,那我们知道阻塞线程和唤醒线程都是需要操作系统完成的,也就是需要从用户态切换到核心态,耗费大,所以synchronized是重量级操作
synchronized锁的对象:
synchronized加在实例方法,代码块,获得锁的是调用这些方法和执行代码块的对象引用加锁(this)
synchronized加在静态方法或者类前,作用的是类的class对象,也就是类的所有对象。
(2)重入锁java.util.concurrent.locks.Reentrantlook
可以使用重入锁Reentrantlook来实现同步重入锁的概念就是不会把自己锁死,它是JAVA层面上的互斥锁,实现了Lock接口,Lock接口的方法
Reentrantlook 实现提供了比使用 synchronized 方法和语句可获得的更广泛的锁定操作,使用lock()和unlock()方法配合try/catch使用,B它相比synchronized 增加的功能有4点。
-
等待可中断
对于synchronized来说,如果一个线程在等待说,结果要么就是获得锁,要么就是继续等待。而重入锁可以中断响应,就是在等待的过程中,可以放弃等待,对于死锁有一定帮助。lock.lockInterruptibly()函数申请锁资源.
lock1.lockInterruptibly(); lock1.lock(); 两个方法都是申请锁资源,lockInterruptibly()方法优先响应中断,下面程序发生死锁,t2会响应中断,放弃资源等待,那么t1就会顺利完成
public class IntLock implements Runnable{
public static ReentrantLock lock1=new ReentrantLock();
public static ReentrantLock lock2=new ReentrantLock();
int lock;
public int intlock(int lock){
this.lock=lock;
}
@overide
public void run(){
try{
if(lock==1){
lock1.lockInterruptibly(); //lock1.lock();
Thread.sleep(500)
lock2.lockInterruptibly();
}else{
lock2.lockInterruptibly(); //lock2.lock();
Thread.sleep(500)
lock1.lockInterruptibly();
}
}catch(InterruptedException e){
e.printStackTrace();
}finally{
if(lock1.isHeldByCurrentThread())
lock1.unlock;
if(lock2.isHeldByCurrentThread())
lock2.unlock;
}
}
}
public static void main(String args[]){
IntLock r1=new IntLock(1);
Thread t1=new Thread(r);
IntLock r2=new IntLock(2);
Thread t2=new Thread(r2);
t1.starat();
t2.start();
t2.interrupt();//中断t2
}
}
-
可实现公平锁
在大多数情况下,所得申请都是不公平的系统会随机的从等待锁的队列中挑选一个执行,重入锁允许我么设置锁的公平性,它有一个构造函数,fair是true时,申请锁是公平的,在队列中等待的时间越长就会优先获得锁
public ReentrantLock(boolean fair)
package xidian.lili.testreentrantlock;
import java.util.concurrent.locks.ReentrantLock;
public class FairLock implements Runnable{
//构造参数创建公平锁
public static final ReentrantLock fairlock=new ReentrantLock(true);
@Override
public void run() {
while(true){
fairlock.lock();
try{
System.out.println(Thread.currentThread().getName()+" 获得锁");
}finally{
fairlock.unlock();
}
}
}
public static void main(String[] args) {
FairLock r1=new FairLock();
Thread t1=new Thread (r1,"thread 1");
Thread t2 =new Thread (r1,"thread 2");
t1.start();
t2.start();
}
}
不带参数,默认不公平
-
锁申请等待限时
把lock1.lockInterruptibly();这个方法换成lock1.tryLock(),尝试获得锁,获得锁就返回true,否则返回false
或者lock1.tryLock(long time,TimeUnit unit)方法,比如lock1.tryLock(5,TimeUnit.SEECONDS),等待5秒还没获得锁就返回false。
package xidian.lili.testreentrantlock;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;
public class TimeLock implements Runnable{
public static final ReentrantLock lock=new ReentrantLock();
@Override
public void run() {
try {//最多等5秒
if(lock.tryLock(5,TimeUnit.SECONDS)){
System.out.println(Thread.currentThread().getId()+" th thread get lock success");
Thread.sleep(6000);//睡眠时间超过了第二个线程的等待时间,第二个返回
}
else{
System.out.println(Thread.currentThread().getId()+" th thread get lock failed");
}
} catch (InterruptedException e) {
e.printStackTrace();
}finally{
if(lock.isHeldByCurrentThread()){
lock.unlock();
}
}
//lock.tryLock();
}
public static void main(String[] args) {
TimeLock t=new TimeLock();
Thread t1=new Thread(t);
Thread t2=new Thread(t);
t1.start();
t2.start();
}
}
-
锁可以绑定多个条件
Condition条件是重入锁的好搭档,Condition是和重入锁关联的,Condition接口提供的方法await()和signal(),signalAll()与Object的wait和notify类似,首先要获得锁才能调用方法
public static ReentrantLock lock=new ReentrantLock();
public static Condition condition=lock.newConditon();
lock.lock()
condition.await();
lock.unlock;
condition.signal;
package xidian.lili.testreentrantlock;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
public class ReenterLockCondition implements Runnable{
public static ReentrantLock lock=new ReentrantLock();
//重入锁绑定condition
public static Condition condition =lock.newCondition();
@Override
public void run() {
try{
lock.lock();
System.out.println(Thread.currentThread().getName()+"获得锁");
condition.await();
System.out.println(Thread.currentThread().getName()+"被唤醒");
} catch (InterruptedException e) {
e.printStackTrace();
}finally{
lock.unlock();
}
}
public static void main(String[] args) throws InterruptedException {
ReenterLockCondition r1=new ReenterLockCondition();
Thread t1=new Thread(r1,"Thread1");
t1.start();
Thread.sleep(2000);
System.out.println(Thread.currentThread().getName()+"休眠");
lock.lock();
System.out.println(Thread.currentThread().getName()+"尝试获得锁");
condition.signal();//要想换新在condition对象上暂停的线程,先要获得锁
lock.unlock();
}
}
synchronized和重入锁都是实现同步的方法,在jdk1.6以前在多线程环境下重入锁比synchronized效率高,但是1.6之后对synchronized做了很多优化,所以使用方便推荐使用