本文借鉴了Java 并发编程这篇博主专栏的文章,他的专栏写的很好,推荐!
在 Java 中,提供了两种方式来实现同步互斥访问:synchronized 和 Lock。本文针对 synchronized 内置锁 详细讨论了其在 Java 并发 中的应用,包括它的具体使用场景(同步方法、同步代码块、实例对象锁 和 Class 对象锁)、可重入性 和 注意事项。
单线程中不会出现线程安全的问题,在多线程编程中,有可能会出现同时访问一个共享、可变资源的情况;
由于每个线程执行的过程是不可控的,所以需要采用同步机制来协同对对象可变状态的访问
当多个线程同时访问临界资源(一个对象,对象中的属性,一个文件,一个数据库等)时,就可能会产生线程安全问题。不过,当多个线程执行一个方法时,该方法内部的局部变量并不是临界资源,因为这些局部变量是在每个线程的私有栈中,因此不具有共享性,不会导致线程安全问题。
序列化访问临界资源
同一时刻,只能有一个线程访问临界资源,也称作是同步互斥访问。换句话说,就是在访问临界资源的代码前面加上一个锁,当访问完临界资源后释放锁,让其他线程继续访问。
互斥锁,即 能到达到互斥访问目的的锁;如果对临界资源加上互斥锁,当一个线程在访问该临界资源时,其它线程便只能处于等待状态;
在 Java 中,可以使用 synchronized 关键字来标记一个方法或者代码块,当某个线程调用该对象的synchronized方法或者访问synchronized代码块时,这个线程便获得了该对象的锁,其他线程暂时无法访问这个方法,只有等待这个方法执行完毕或者代码块执行完毕,这个线程才会释放该对象的锁,其他线程才能执行这个方法或者代码块。
注意:
synchronized 代码块类似于以下这种形式:
synchronized(lock){
//访问共享可变资源
...
}
当在某个线程中执行这段代码块,该线程会获取对象lock的锁,从而使得其他线程无法同时访问该代码块。其中,lock 可以是 this,代表获取当前对象的锁,也可以是类中的一个属性,代表获取该属性的锁。
synchronized代码块 比 synchronized方法 的粒度更细一些,使用起来也灵活得多。因为也许一个方法中只有一部分代码只需要同步,如果此时对整个方法用synchronized进行同步,会影响程序执行效率。而使用synchronized代码块就可以避免这个问题,synchronized代码块可以实现只对需要同步的地方进行同步。
每个类也会有一个锁,静态的 synchronized方法 就是以Class对象作为锁。另外,它可以用来控制对 static 数据成员 (static 数据成员不专属于任何一个对象,是类成员) 的并发访问。
对于 synchronized方法 或者 synchronized代码块,当出现异常时,JVM会自动释放当前线程占用的锁,因此不会由于异常导致出现死锁现象。
一般地,当某个线程请求一个由其他线程持有的锁时,发出请求的线程就会阻塞。然而,由于 Java 的内置锁是可重入的,因此如果某个线程试图获得一个已经由它自己持有的锁时,那么这个请求就会成功。可重入锁最大的作用是避免死锁。
public class Test implements Runnable {
// 可重入锁测试
public synchronized void get() {
System.out.println(Thread.currentThread().getName());
set();
}
public synchronized void set() {
System.out.println(Thread.currentThread().getName());
}
@Override
public void run() {
get();
}
public static void main(String[] args) {
Test test = new Test();
new Thread(test,"Thread-0").start();
new Thread(test,"Thread-1").start();
new Thread(test,"Thread-2").start();
}
}/* Output:
Thread-1
Thread-1
Thread-2
Thread-2
Thread-0
Thread-0
*///:~
由于字符串常量池的原因,在大多数情况下,同步synchronized代码块 都不使用 String 作为锁对象,而改用其他,比如 new Object() 实例化一个 Object 对象,因为它并不会被放入缓存中。
在将任何数据类型作为同步锁时,需要注意的是,是否有多个线程将同时去竞争该锁对象:
1).若它们将同时竞争同一把锁,则这些线程之间就是同步的;
2).否则,这些线程之间就是异步的。
synchronized 内置锁 是一种 对象锁 (锁的是对象而非引用), 作用粒度是对象 ,可以用来实现对 临界资源的同步互斥访问 ,是 可重入 的。特别地,对于 临界资源 有:
关键字synchronized 主要包含两个特征: