Java并发包是Java中提供的一个用于支持多线程编程的工具包。Java并发包提供了多种机制来控制线程的执行,保证线程的安全性和可靠性。下面我们将介绍Java并发包的使用方法,并给出示例。
synchronized
public class SynchronizedDemo {
private int v;
private static int a;
private final Object lock = new Object();
// 修饰非静态方法 对象锁
public synchronized void add(int value) {
v += value; // 临界区
}
public void sub(int value) {
// 修饰局部代码块 对象锁
synchronized (lock) {
v -= value; // 临界区
}
}
// 修饰静态方法 类锁
public static synchronized void multi(int value) {
a *= value; // 临界区
}
public static void div(int value) {
// 修饰局部代码块 类锁
synchronized (SynchronizedDemo.class) {
a /= value; // 临界区
}
}
}
复制代码
java编译器会在synchronized修饰的方法或代码块前后自动Lock,unlock。
synchronized修饰代码块,锁定是个obj对象,或者是一个类,sychronized(this.class)
synchronized修饰静态方法,锁定是当前类的class对象
synchronized修饰非静态方法,锁定的是当前实例对象this。
实现原理:
synchronized关键字底层使用的锁叫做Monitor锁。但是,我们无法直接创建和使用Monitor锁。Monitor锁是寄生存在的,每个对象都会拥有一个Monitor锁。如果我们想要使用一个新的Monitor锁,我们只需要使用一个新的对象,并在synchronized关键字后,附带声明要使用哪个对象的Monitor锁即可。
当使用sychronized修饰方法的时候,编译器只不过是在函数的flags中添加了ACC_SYNCHRONIZED标记而已,其他部分跟没有添加synchronized的函数的字节码相同。
当使用synchronized修饰局部代码块的时候,字节码通过monitorenter和monitorexit来标记synchronized的作用范围。但有两点需要再解释一下
synchronized关键字底层使用的锁叫做Monitor锁。但是,我们无法直接创建和使用Monitor锁。Monitor锁是寄生存在的,每个对象都会拥有一个Monitor锁,在字节码中,通过monitorenter前面的几行字节码来指定。
以下字节码中有两个monitorexit,添加第二个monitorexit的目的是为了在代码抛出异常时仍然能解锁。
monitor锁实现原理
synchronized在底层使用不同的锁来实现,重量级锁,轻量级锁,偏向锁等。
实际上,synchronized使用的重量级锁,就是前面提到的对象上的Monitor锁。JVM有不同的实现版本,因此,Monitor锁也有不同的实现方式。在Hotspot JVM实现中,Monitor锁对应的实现类为ObjectMonitor类。因为Hotspot JVM是用C++实现的,所以,ObjectMonitor也是用C++代码定义的。
Synchronized的缺点
无法判断获取锁的状态。
虽然会自动释放锁,但如果如果锁的那个方法执行时间较长就会一直占用着不去释放,不能让使用同一把锁的方法继续执行,影响程序的运行。不能设置超时。
当多个线程尝试获取锁时,未获取到锁的线程会不断的尝试获取锁,而不会发生中断,这样会造成性能消耗。
不能够实现公平锁
Lock和Condition
Java并发包中的 Lock 和 Condition 接口提供了一种更为灵活的同步机制。与 synchronized 不同的是,它们可以支持更为细粒度的锁控制,并且可以避免死锁问题。
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class LockConditionExample {
private Lock lock = new ReentrantLock();
private Condition condition = lock.newCondition();
private void method1() throws InterruptedException {
lock.lock();
try {
System.out.println("method1 is running");
condition.await();
System.out.println("method1 is finished");
} finally {
lock.unlock();
}
}
private void method2() {
lock.lock();
try {
System.out.println("method2 is running");
condition.signal();
System.out.println("method2 is finished");
} finally {
lock.unlock();
}
}
public static void main(String[] args) throws InterruptedException {
LockConditionExample example = new LockConditionExample();
Thread thread1 = new Thread(() -> {
try {
example.method1();
} catch (InterruptedException e) {
e.printStackTrace();
}
});
Thread thread2 = new Thread(example::method2);
thread1.start();
Thread.sleep(1000);
thread2.start();
}
}
Lock 和 Condition 接口用于控制 method1 和 method2 方法的执行顺序。其中 method1 方法会先获取锁并进入等待状态,而 method2 方法会在一段时间后唤醒 method1 方法并释放锁。这样就可以保证 method1 方法先执行。
Semaphore
Semaphore 是一个计数信号量,用于控制同时访问某个资源的线程数。可以将 Semaphore 看作是一种计数器,每当有线程访问该资源时,计数器的值减一;当计数器的值为零时,其他线程需要等待,直到有线程释放该资源。
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class Counter {
private int count = 0;
private Lock lock = new ReentrantLock();
public void increment() {
lock.lock();
try {
count++;
} finally {
lock.unlock();
}
}
public int getCount() {
return count;
}
}
public class IncrementThread extends Thread {
private Counter counter;
public IncrementThread(Counter counter) {
this.counter = counter;
}
public void run() {
for (int i = 0; i < 1000000; i++) {
counter.increment();
}
}
}
public class Main {
public static void main(String[] args) throws InterruptedException {
Counter counter = new Counter();
Thread t1 = new IncrementThread(counter);
Thread t2 = new IncrementThread(counter);
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println("Count: " + counter.getCount());
}
}
区别:
Synchronized 被称为隐式锁,也叫 JVM 锁,因为它锁的持有和释放都是隐式的,无须开发者干预。Java 1.5 引入新的锁机制,其中锁的实现基于 Lock 接口:
public interface Lock {
// 加锁
void lock();
// 解锁
void unlock();
// 可中断获取锁,获取锁时可响应中断操作
void lockInterruptibly() throws InterruptedException;
// 尝试非阻塞获取锁,能够获得返回true,否则返回false
boolean tryLock();
// 根据时间尝试获取锁,能够获得返回true,否则返回false
boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
// 获取等待通知组件,该组件与当前锁绑定
Condition newCondition();
}
ReentrantLock 锁基于 AQS 队列同步器实现,全称 AbstractQueuedSynchronizer,其中它的抽象类如下:
public abstract class AbstractQueuedSynchronizer extends AbstractOwnableSynchronizer {
// 0表示锁未被占用,1表示已占用
private volatile int state;
// 指向同步队列队头
private transient volatile Node head;
// 指向同步队列队尾
private transient volatile Node tail;
// 其余属性省略
}
公平锁和非公平锁的差别在于:
公平锁:先请求锁的线程会优先获取到锁,原理是所有需要获得锁的线程都会进入到队列中,队列的特点是先进先出,先进入的请求线程会在头部,后进入的请求线程都会在队列的尾部。
非公平锁:则不会按照线程请求获得锁的先后顺序,会立马进行一次获取锁的请求操作。
synchronized:属于独占锁、悲观锁、可重入锁、非公平锁
ReentrantLock:继承了Lock类,可重入锁、悲观锁、独占锁、互斥锁、同步锁。
Lock:Java中的接口,可重入锁、悲观锁、独占锁、互斥锁、同步锁