Java中解决公共变量访问的线程安全问题的主要的方法是加锁,包括synchronized、Lock
一、synchronized
1.1、synchronized 修饰目标
synchronized 可用于修饰方法(静态的、常规的)、代码块。其中,锁定代码块常用。
/**************************************************
* synchronized 修饰静态方法(锁粒度太大,不常用)
**************************************************/
public class Synchronized1 {
//内部静态量
private static int n = 0;
//内部静态类
private static class Counter{
private static int MAX = 1000000;
private synchronized static void increase() {
for (int i = 0; i < MAX; i++) {
n++;
}
}
private synchronized static void decrease() {
for (int i = 0; i < MAX; i++) {
n--;
}
}
}
public static void main(String[] args) throws InterruptedException {
Thread thread1 = new Thread(() -> { Counter.increase(); });
thread1.start();
Thread thread2 = new Thread(() -> { Counter.decrease(); });
thread2.start();
thread1.join();
thread2.join();
System.out.println(n);
}
}
/************************************************
* synchronized 修饰普通方法(锁粒度大,不常用)
************************************************/
public class Synchronized2 {
//内部静态量
private static int n = 0;
//内部静态类
private static class Counter{
private static int MAX = 1000000;
private synchronized void increase() {
for (int i = 0; i < MAX; i++) {
n++;
}
}
private synchronized void decrease() {
for (int i = 0; i < MAX; i++) {
n--;
}
}
}
public static void main(String[] args) throws InterruptedException {
Counter counter = new Counter();
Thread thread1 = new Thread(() -> { counter.increase(); });
thread1.start();
Thread thread2 = new Thread(() -> { counter.decrease(); });
thread2.start();
thread1.join();
thread2.join();
System.out.println(n);
}
}
/*******************************************
* synchronized 修饰代码块(常用)
*******************************************/
public class Synchronized3 {
// 内部静态量
private static int n = 0;
// 内部静态类
private static class Counter {
private static int MAX = 1000000;
private void increase() {
for (int i = 0; i < MAX; i++) {
//synchronized (锁载体),方法里锁载体要一样
synchronized (this) {
n++;
}
}
}
private void decrease() {
for (int i = 0; i < MAX; i++) {
//synchronized (锁载体),方法里锁载体要一样
synchronized (this){
n--;
}
}
}
}
public static void main(String[] args) throws InterruptedException {
Counter counter = new Counter();
Thread thread1 = new Thread(() -> { counter.increase(); });
thread1.start();
Thread thread2 = new Thread(() -> { counter.decrease(); });
thread2.start();
thread1.join();
thread2.join();
System.out.println(n);
}
}
1.2、synchronized 加锁对象(锁载体)
synchronized 的加锁对象可以是【类】、【this对象】、【自定义对象】。其中,自定义对象常用。对于同一个业务的多个线程的加锁对象,需是同一对象(即加同一把锁),否则加锁无效。
/*******************************************
* synchronized 加锁对象为类、this对象
*******************************************/
public class Synchronz {
// 内部静态量
private static int n = 0;
// 内部静态类
private static class Counter {
private static int MAX = 1000000;
private void increase() {
for (int i = 0; i < MAX; i++) {
//synchronized (锁载体),方法里锁载体要一样
synchronized (this) {
n++;
}
}
}
private void decrease() {
for (int i = 0; i < MAX; i++) {
//synchronized (锁载体),方法里锁载体要一样
synchronized (Synchronz.class){
n--;
}
}
}
}
public static void main(String[] args) throws InterruptedException {
Counter counter = new Counter();
Thread thread1 = new Thread(() -> { counter.increase(); });
thread1.start();
Thread thread2 = new Thread(() -> { counter.decrease(); });
thread2.start();
thread1.join();
thread2.join();
System.out.println(n);
}
}
/*******************************************
* synchronized 加锁对象为自定义对象
*******************************************/
public class Synchronz2 {
// 内部静态量
private static int n = 0;
private static Object obj = new Object();
// 内部静态类
private static class Counter {
private static int MAX = 1000000;
private void increase() {
for (int i = 0; i < MAX; i++) {
//synchronized (锁载体),方法里锁载体要一样
synchronized (obj) {
n++;
}
}
}
private void decrease() {
for (int i = 0; i < MAX; i++) {
//synchronized (锁载体),方法里锁载体要一样
synchronized (obj){
n--;
}
}
}
}
public static void main(String[] args) throws InterruptedException {
Counter counter = new Counter();
Thread thread1 = new Thread(() -> { counter.increase(); });
thread1.start();
Thread thread2 = new Thread(() -> { counter.decrease(); });
thread2.start();
thread1.join();
thread2.join();
System.out.println(n);
}
}
1、synchronized是非公平锁(来的早不如来的巧),具有互斥性、排他性:线程执行到某个对象的 synchronized 时占用锁,其他线程如果也执行到同一对象 synchronized 发现锁占用,会自旋尝试获取锁一段时间,失败后进入休眠状态(排队)等待被操作系统唤醒。
2、synchronized 的锁信息存储在Java对象头里(隐藏属性):Java对象有个隐藏的对象头,其中存储了当前锁的状态标识(占用or空闲),同时还存储了占用当前锁的线程ID;
1.3、synchronized 实现原理
①在代码层面:
synchronized加锁的对象里有一个的隐藏的对象头,这个对象头(可看作一个类)里有很多属性,其中比较关注的两个属性是:是否加锁的标识和占有当前锁的线程id。每次进⼊ synchronized 修饰的代码块时,会去对象头中先判断加锁的标识,再判断拥有当前锁的线程id,从而决定当前线程能否继续执行。
◆判断加锁标识为false,即对象头未加锁,当前线程可以进入synchronized 修饰的代码块,并设置加锁标识为true,设置拥有当前锁的线程id为此线程id。
◆判断加锁标识为true,即对象头已加锁,需进一步判断拥有当前锁的线程id是否为此线程id,若是,则继续往下执行(即可重入);否则,不能往下执行,需要等待锁资源释放后重新竞争再获取锁。
②在JVM和操作系统层面:
synchronized同步锁是通过JVM内置的Monitor监视器实现的,而监视器又是依赖操作系统的互斥锁Mutex实现的。
二、Lock
/**
* *.lock()要放到try外(官建)或try首行
* *.unlock()要放在finally里,防止锁资源永久占用
*/
public class LockTest {
private static int n = 0;
private static class Counter{
private static Lock lock = new ReentrantLock();
private static int MAX = 1000000;
private static void increase() {
for (int i = 0; i < MAX; i++) {
// 加锁
lock.lock();
try{
n++;
} finally {
// 释放锁
lock.unlock();
}
}
}
private static void decr() {
for (int i = 0; i < MAX; i++) {
// 加锁
lock.lock();
try{
n--;
} finally {
// 释放锁
lock.unlock();
}
}
}
}
public static void main(String[] args) throws InterruptedException {
Thread thread1 = new Thread(() -> { Counter.increase(); });
thread1.start();
Thread thread2 = new Thread(() -> { Counter.decr(); });
thread2.start();
thread1.join();
thread2.join();
System.out.println(n);
}
}