锁
锁的种类
公平锁/非公平锁
公平锁指多个线程按照申请锁的顺序来获取锁
非公平锁是指多个线程不按照申请锁的顺序来获取锁,有可能造成线程饥饿现象
ReentrantLock是通过AQS来实现线程调度,可以实现公平锁,但synchronized是非公平的,无法实现公平锁
可重入锁
又叫递归锁,是指同一个线程,在外层方法获取到锁的时候,进入内层方法会自动获取锁
独享锁/共享锁
独享锁是指该锁一次只能被一个线程所持有
共享锁是指该锁可被多个线程所持有
互斥锁/读写锁
独享锁/共享锁是一种广义的说法,互斥锁/读写锁是具体的实现
互斥锁在Java中的实现就是ReentrantLock
读写锁在Java中的实现就是ReadWriteLock
乐观锁/悲观锁
悲观锁认为对于同一个数据的并发操作,一定会发生修改的,哪怕没有修改,也会认为修改。因此对于同一个数据的并发操作,悲观锁采取枷锁的形式。悲观的认为,不加锁的并发操作一定会出问题。
乐观锁则认为对于同一数据的并发操作,是不会发生修改的,在更新数据的时候,会采用尝试更新。乐观的认为,不加锁的并发操作是没有事情的
分段锁
分段锁是一种锁设计,并不是具体的一种锁,对于ConcurrentHashMap而言,其并发的实现就是通过分段锁的形式来实现高效的并发操作
偏向锁
偏向锁是指一段同步代码一直被一个线程所访问,那么该线程会自动获取锁,降低获取锁的代价
自旋锁(java.util.concurrent包下的几乎都是利用锁)
自旋锁是指尝试获取锁的线程不会立即阻塞,而是采用循环的方式去尝试获取锁,这样的好处是减少线程上下文切换的消耗,缺点是循环会消耗CPU
synchronized关键字
- synchronized关键字 是一种同步锁
- 原理是:一个线程访问对象中的Synchronized(this)同步代码块时,其他试图访问该对象的线程将会被阻塞。
package com.huixiang.synch;
import static java.lang.Thread.*;
public class SynchronizedTest {
public static void main(String[] args) {
System.out.println("使用关键字synchronized");
SyncThread syncThread = new SyncThread();
Thread syncThread1 = new Thread(syncThread, "SyncThread1");
Thread syncThread2 = new Thread(syncThread, "SyncThread2");
syncThread2.start();
syncThread1.start();
}
}
class SyncThread implements Runnable{
private static int count;
public SyncThread(){
count = 0;
}
//实现run方法
@Override
public void run() {
synchronized (this){
for (int i =0;i<5;i++){
try {
//currentThread().getName()获取当前线程的名字
System.out.println("线程name"+currentThread().getName());
//休眠100ms
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public static int getCount() {
return count;
}
}
分析
- 当两个并发线程访问同一个对象中的synchronized代码块时,在同一时刻只能有一个线程得到执行,另一个线程受阻塞,必须等待当前线程执行完这个代码块之后才能执行该代码块。
- 两个线程是互斥的,因为在执行synchronized代码块是会锁定当前的对象,只有执行完该代码块才能释放该对象锁,下一个线程才能执行并锁定该对象。
修饰一个方法
synchronized修饰一个方法很简单就是在方法的前面加synchronized。
将上边代码块改为
@Override
public synchronized void run() {
for (int i =0;i<5;i++){
try {
//currentThread().getName()获取当前线程的名字
System.out.println("线程name"+currentThread().getName());
//休眠100ms
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
分析
- 虽然可以使用synchronized来定义方法,但是synchronized并不属于方法定义的一部分,因此,synchronized并不能被继承。如果在父类的某个方法中使用了synchronized关键字,而子类中覆盖了这个方法,在子类中的这个方法默认情况下并不是同步的,而必须显式的在子类的这个方法中加上synchronized关键字才可以。