那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 callable = new Callable() {@Overridepublic Integer call() throws Exception {int sum = 0;for (int i = 0; i <= 1000; i++) {sum += i;}return sum;}};//创建线程,完成任务//为了让线程执行 callable 中的任务,光使用构造方法还不够,还需要一个辅助的类//FutureTask 运行结果会保存在这个类中FutureTask 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();//1System.out.println("申请成功");semaphore.acquire();//2System.out.println("申请成功");semaphore.acquire();//3System.out.println("申请成功");semaphore.acquire();//4System.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 又做出了一系列的改进和优化