- synchronized是java 提供的一个并发控制的关键字,作用于对象上.
- 每个java对象都有一个内部对象锁,通过synchronized可以向指定对象请求获取对象锁,该锁是互斥锁,一个时刻只能有一个线程能获得这把锁,其他请求获得这把锁的线程都进入阻塞状态
- synchronized提供的锁是可重入的锁
- 可重入锁实现原理:
- 每个锁关联一个请求计数器和一个占有它的线程。当计数为0时,认为锁是未被占有的;线程请求一个未被占有的锁时,JVM将记录锁的占有者,并且将请求计数器置为1 。
- 如果同一个线程再次请求这个锁,计数器将递增;
- 每次占用线程退出同步块,计数器值将递减。直到计数器为0,锁被释放。
- 可重入锁的意义之一在于防止死锁
- 可重入锁实现原理:
锁代码块
- synchronized通过锁住一个实例对象实现同步代码块的,而不是锁住代码块
public class myThread6 implements Runnable{
private static int count=0;
@Override
public void run() {
synchronized (this){
/*
* 这里的this指代的是调用该run方法的当前对象
* 只有获得当前对象的对象锁,才能执行{}里的内容,也就是同步代码块的内容
* */
for (int i = 0; i < 10; i++) {
try {
count++;
System.out.println(Thread.currentThread().getName()+":"+count);
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public static void main(String[] args) {
myThread6 mt = new myThread6();
//这里使用同一个实例对象来创建线程,这样synchronized请求的才是同一把锁
new Thread(mt,"t1").start();
new Thread(mt,"t2").start();
}
}
锁非静态方法
public class myThread6 implements Runnable{
private static int count=0;
@Override
public void run() {
for (int i = 0; i < 10; i++) {
try {
countAdd();
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public synchronized void countAdd(){
/*
被synchronized修饰的方法变成同步方法
调用countAdd方法时候相当于
synchronized (this){
countAdd();
}*/
count++;
System.out.println(Thread.currentThread().getName()+":"+count);
}
public static void main(String[] args) {
myThread6 mt = new myThread6();
new Thread(mt,"t1").start();
new Thread(mt,"t2").start();
}
}
- synchronized关键字不能继承。synchronized不属于方法定义的一部分。如果父类某个方法用synchronized修饰,子类继承或者覆盖了这个方法,默认情况下,子类方法不是同步的,除非子类方法也用synchronized修饰或者在重写子类方法,并调用父类方法
public class myThread6 extends FatherThread6{
@Override
public void run() {
for (int i = 0; i < 10; i++) {
try {
//这里直接调用继承自父类的countAdd()方法
//但在子类中,countAdd()方法是非同步方法
countAdd();
System.out.println(Thread.currentThread().getName()+":"+count);
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
myThread6 mt = new myThread6();
new Thread(mt,"t1").start();
new Thread(mt,"t2").start();
}
}
class FatherThread6 implements Runnable{
public static int count=0;
@Override
public void run() {
for (int i = 0; i < 10; i++) {
try {
countAdd();
System.out.println(Thread.currentThread().getName()+":"+count);
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public synchronized void countAdd(){
count++;
}
}
- synchronized锁定的方法和 非synchronized锁定的方法可以同时执行
public class myThread(){
//这里定义了同步方法A和非同步方法B
//假设有两个线程t1,t2(通过同一个实例对象构造)
//t1线程首先获取锁,执行方法A
//这是t2线程如果想执行方法A需要等线程t1执行完毕并释放锁
//但t2线程可以直接执行方法B,不需要等t1释放对象锁,因为方法B没有上锁
public synchronized void A(){
}
public void B(){
}
}
锁静态方法
- synchronized修饰静态方法的时候,持有的锁会从对象锁变成类锁,即锁住的是整个类. 实质上synchronized持有的锁还是一个对象锁,只不过这个对象是Class对象.所以持有了Class对象的锁,也就相当于锁住了整个类
- 关于Class对象:我们编写的每个类,无论创建多少个实例对象,在JVM中都只有一个Class对象,即在内存中每个类有且只有一个相对应的Class对象.
public class myThread6 implements Runnable{
private static int count=0;
@Override
public void run() {
for (int i = 0; i < 10; i++) {
try {
countAdd();
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static synchronized void countAdd(){
/*
被synchronized修饰的方法变成同步方法
调用countAdd方法时候相当于
synchronized (myThread6.class){
countAdd();
}*/
count++;
System.out.println(Thread.currentThread().getName()+":"+count);
}
public static void main(String[] args) {
myThread6 mt1 = new myThread6();
myThread6 mt2 = new myThread6();
//这里mt1和mt2属于不同的实例对象,
// 但调用静态同步方法countAdd()的时候,请求的是对象锁是同一把锁
new Thread(mt1,"t1").start();
new Thread(mt2,"t2").start();
}
}
锁升级
为了性能的考虑,如果只有一个线程请求获得锁,synchronized会提供偏向锁,直接在对象头markword(存储对象运行时数据的地方)记录该线程id,达到上锁的目的
如果出现了多个线程争用该锁的情况,偏向锁升级成自旋锁,一个线程获得锁后,其他申请获得该锁的线程不是直接进入阻塞状态,进入自旋等待.
如果其他线程自旋次数的次数过多,自旋锁会升级成重量级锁(直接向操作系统申请一把锁),其他等待获得锁的线程进入阻塞状态,不在占用cpu
ps:hotspot实现里,锁只能升级,不能降级