1.synchronized:它是一个互斥锁(独占锁),是对某个对象加锁,而不是对某段代码块加锁,当一个线程获的。
这个对象锁后,其它线程只能进入等待,直到获取锁的线程执行完代码释放锁后,其它线程才能再次获取这把锁。
2.synchronized锁定的对象:
(1)可以是我们自己创建的对象:
例如:下面代码我们锁定的对象就是obj
private Object obj=new Object();public void control(){
synchronized(obj){
方法代码
}
}
(2)对象自身(this):当我们new一个对象时,指向自身,把自身对象锁定,每次执行对象的方法时锁定自己
public void control(){synchronized(this){
方法代码
}
}
这种写法类似于在方法上加synchronized
public synchronized void control(){
方法代码
}
3.synchronized用在static方法上面时,锁定的是这个类的Class对象例如:
public synchronized static void control(){
方法代码
}
相当于
synchronized (类.class) {
方法代码
}
4.不加锁的方法可能会发生线程重入,即线程执行到一半被另一个线程给打断,从而出现数据一致性问题,
解决办法就是加锁,使的线程无法被打断。
5.在一个类中同步方法和非同步方法是可以同时运行的,原因为同步方法需要获取锁,非同步方法不用获取锁,
不会形成等待锁的关系。
6.一个同步方法可以调用另一个同步方法,原因是因为synchronized锁是可重入锁,当一个线程拥有某个
对象的锁时,我们再次去申请同一把锁的时候是可以获得该锁的,只是我们在锁上会再次加上一个一。
7.脏读:出现的原因是写上加了锁,读上没有加锁,解决办法为读上也加上锁。
8.死锁:我们定义了俩个对象,在有俩个方法中分别按照不同的顺序锁定这俩个对象,这样就会形成死锁。
9.子类可以调用父类的同步方法。
10.如果我们的程序发生了异常,默认情况下ynchronized是会释放锁的,在并发时候当多个线程共享一个
资源时,如果发生异常,锁被释放,这样多个线程就会同时进入同步方法,发生数据的不一致问题,所以我们要
小心的处理异常,如果不希望在发生异常释放锁,我们可以在try catch中处理。
11.volatile:是一个变量在多个线程之间可见
(1)不使用volatile会出现的情况:假如有俩个线程A,B,他们两共享一个变量叫做share,这个变量位于
一个对象的堆内存中(主内存中),当线程A运行时,会把share的值从主内存中读到A的工作区(缓存区),
在运行中就会直接使用这个copy的值,就不会再去读取主内存的值了,线程B也会将share的值从主内存中
读到自己的工作去(缓存区),当B修改了share的值后,它发现值已经修改了就会把值写回到主内存中,
由于线程A不再去主内存中读取share的值,所以它不能发现修改后的值。
(2)当我们使用volatile后:一旦值发生变化就会提醒其它线程你们缓存区的值已经过期了,请你们从
主内存中再读取一次(不是每次都去主内存中读取),即写完进行缓存过期通知。
12. volatile只保证可见性,synchronized既保证可见性和原子性,如果只是保证可见性我们要用volatile,因为
它的效率要高于synchronized。
13.我们如果只是进行简单的数字运算,我们可以使用AtomicXXX类,因为它本身的方法都是原子性的,但不能保证
多个方法连续调用是原子的。
例如:AtomicInteger count=new AtomicInteger(0);for(int i=0;i<1000;i++){
//if(count.get()<1000)
//它俩中间的代码别的线程是可以打断执行的
count.incrementAndGet();
}
14.使用锁的注意事项:(1)同步块的代码越少越好
(2)锁定某个对象o,如果o对象的属性发生变化,不影响锁的使用,但如果o变成另一个对象,则锁对象发生改变
所以应该避免将锁定对象的引用变成另一个对象
(3)不要把字符串常量作为锁定对象,假如当我们用到一个类库时,该类库代码锁定了字符串,但是因为
读不到源码,而我们在自己的代码中也锁定了这个字符串,这个时候可能会发生死锁阻塞
15.线程间通信:(1)wait和notify:使用这个他们时必须进行锁定,如果没有锁定就不能调用这个对象wait和notify方法,
当我们调用这个锁定对象的wait方法,前线程进入等待状态,同时释放锁,别的线程可以执行,只有在
调用这个对象的notify方法,会启动在这个对象上等待的某一个线程,或则notifyAll启动所有在这个
对象上等待的线程,因为notify不会释放锁,而我们项要让另一个线程wait后面的代码继续,我们必须调用
wait方法释放锁,使自身进入等待状态,释放锁,同样我们想让刚才notify后的代码继续执行,我们需要在一个
wait线程中调用notify方法唤醒线程
(2)使用latch(门栓)代替wait,notify,来进行通知,我们使用countdownlatch的await和countdown方法替代
wait和notify方法,好处是它不涉及锁定,当count的值为0时当前线程继续执行,当不涉及同步,只是涉及线程
之间通讯时,应该考虑使用CountDownLatch,CyclicBarrier,Semaphore,因为synchronized+wait/notify太重。
16.reentrantlock:(1)可重入锁,当程序发生异常时,JVM会自动释放synchronized的锁,但reentrantlock必须手动释放锁,
所以我们一般将释放锁的代码放到finally块中。
(2)reentrantlock:它的tryLock方法,可以进行尝试锁定,如果获得了锁返回结果为true,没有获取锁结果为false,
所以我们可以根据这个返回结果写我们的处理逻辑,而synchronized没有获取锁,他会一直等待下去,
同时我们还可以通过tryLock来指定我们等待的时间。
(3)reentrantlock的lockInterruptibly方法,,可以对线程的interrupt方法做出响应,即interrupt标志为true,
则立刻抛出InterruptedException异常,因此必须捕捉该异常
(4)Reentrantlock:是公平锁,就是哪个线程等待这把锁的时间越长,谁先获取锁,synchronized为非公平锁,
哪个线程获取锁是根据根据线程调度器决定的。
ReentrantLock lock=new ReentrantLock(true)这样就声明公平锁,公平锁效率要第一下。
参考自:马士兵高并发编程