那synchronized基于这些策略有哪些特性呢?
JVM 将 synchronized 锁分为四种情况.
四种锁状态也会根据实际情况依次进行升级.
锁粗化涉及到锁的粒度
有这么一种情况: 有些代码,明明不用加锁,结果你给加上锁了.编译器就会发现这个加锁好像没啥必要,就直接把锁给去掉了(锁消除).
当然你可能说,我不会乱加锁的.不过,有的时候加锁操作并不是那么明显,稍不留神就会做出了这种错误的决定.
例如: StringBuffer, Vector…这些类,在标准库中进行了加锁操作,而我们在单个线程中用到这些类的时候,就是单线程进行了加锁解锁.而我们并不会发觉,不过我们也不用担心.因为编译器会发现并处理这一类情况,也就是上述的锁消除.
Callable 通常需要搭配 FutureTask 来使用. FutureTask 用来保存 Callable 的返回结果. 因为 Callable 往往是在另一个线程中执行的, 啥时候执行完并不确定.所以 FutureTask 就可以负责这个 等待结果出来 的工作.
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
/**
* Callable 接口
* java.util.concurrent
*
* Callable 解决了 Runnable 不方便返回结果的问题
*/
public class Test {
public static void main(String[] args) {
Callable<Integer> callable = new Callable<Integer>() {
@Override
public Integer call() throws Exception {
int sum = 0;
for (int i = 0; i <= 1000; i++) {
sum += i;
}
return sum;
}
};
//创建线程,完成任务
//为了让线程执行 callable 中的任务,光使用构造方法还不够,还需要一个辅助的类
//FutureTask 运行结果会保存在这个类中
FutureTask<Integer> task = new FutureTask<>(callable);//任务
Thread t = new Thread(task);
t.start();
try {
//如果线程的任务没有执行完,get就会阻塞,一直阻塞到,有return 的结果
System.out.println(task.get());
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
有三个主要的方法
相对于习惯使用 synchronized 的我来说,这里加锁和解锁 分开的做法,是不太友好的,因为很容易遗漏 unlock(),出现锁死.
synchronized是一个关键字(背后的逻辑是JVM内部实现的,C++),ReentrantLock是一个标准库中的类(背后的逻辑是Java代码写的)
synchronized 不需要手动释放锁,出了代码块,锁自然释放.ReentrantLock必须要手动释放锁,要谨防忘记释放.
synchronized 如果竞争锁的时候失败,就会阻塞等待.但是ReentrantLock除了阻塞等待这一手之外,还有一手, trylock()超时失败了会返回.
synchronized是一个非公平锁.ReentrantLock 提供了非公平和公平锁两个版本!在构造方法中,通过参数就可以来指定当前是公平锁还是非公平锁.
import java.util.concurrent.locks.ReentrantLock;
/**
* ReentrantLock : 可重入锁
*/
public class Test2 {
public static void main(String[] args) {
ReentrantLock locker = new ReentrantLock(true);
//参数为true 为公平锁 默认为false 非公平锁
locker.lock();//加锁
try {
} finally {
locker.unlock();//解锁
}
}
}
锁就相当于一个二元信号量,可用资源只有一个.计数器非0,即1
Semaphore 的 PV 操作中的加减计数器操作都是原子的, 可以在多线程环境下直接使用.
类的参数为信号量个数
import java.util.concurrent.Semaphore;
/**
* Semaphore 信号量
* 锁:二元信号量, 可用资源就一个,计数器 非0,即1
*/
public class Test {
public static void main(String[] args) throws InterruptedException {
Semaphore semaphore = new Semaphore(4);
//参数为4 代表可用资源有 4 个
//申请资源 P操作
semaphore.acquire();//1
System.out.println("申请成功");
semaphore.acquire();//2
System.out.println("申请成功");
semaphore.acquire();//3
System.out.println("申请成功");
semaphore.acquire();//4
System.out.println("申请成功");
// semaphore.acquire();//5 阻塞等待
// System.out.println("申请成功");
semaphore.release();//释放资源 V操作
semaphore.acquire();
System.out.println("申请成功");
}
}
类的参数.表示需要等待任务的个数.
import java.util.concurrent.CountDownLatch;
/**
* CountDownLatch 同时等待 N 个任务执行结束
*/
public class Test {
public static void main(String[] args) throws InterruptedException {
//构造方法的参数表示 有多少个选手参赛
CountDownLatch countDownLatch = new CountDownLatch(10);
for (int i = 0; i < 10; i++) {
Thread t = new Thread(() -> {
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "到达终点");
countDownLatch.countDown();
});
t.start();
}
countDownLatch.await();//等待所有线程到达
System.out.println("比赛结束");
}
}
当我们往一个容器添加元素的时候,不直接往当前容器添加,而是先将当前容器进行Copy.复制出一个新的容器后往新的容器里添加元素,添加完元素之后,再将原容器的引用指向新的容器.
这样做的好处是我们可以对CopyOnWrite容器进行并发的读,而不需要加锁,因为只读的时候不会有线程安全问题.
ConcurrentHashMap 相比于 Hashtable 又做出了一系列的改进和优化