目录
目录
1 synchronized的特性
1.1互斥特性
synchronized会起到互斥效果,synchronized的底层是使用操作系统的mutex lock来实现的。当某个线程执行到某个对象的synchronized中时,其他线程如果也执行到同一个对象synchronized就会阻塞等待。
.进入synchronized修饰的代码块,相当于加锁。
.退出synchronized修饰的代码块,相当于解锁。
比如下列代码:
synchronized void increase(){
count++;
}
在代码第一行进入了increase()方法。相当于完成了加锁操作。在执行到最后一个大括号之后,相当于针对当前所创建的对象进行了解锁操作。
synchronized用的锁是存储在Java对象里面的。
当一个线程上了锁之后,那么其他线程就只能等待这个线程释放锁。其他的线程也会尝试加锁,但是加不上,因此这种现象就叫做阻塞等待现象。虽然其他线程都会等待这个线程释放锁,但是当其释放了锁之后,其余线程也会争抢这把锁,不遵循先来后到的原则。
1.2 刷新内存
suychronized的工作过程:
. 获得互斥锁
. 从主内存拷贝变量的最新副本到工作内存
. 执行代码
. 将更改后的共享变量的值刷新到主内存
. 释放互斥锁
1.3 可重入
synchronized 同步块对同一条线程来说是可重入的,不会出现把自己锁死的情况。
如下两段代码所示:
这是不加synchronized的锁,会出现锁死现象。
//第一次加锁成功
lock();
//第二次阻塞等待
lock();
这是加了synchronized的锁,因为可重入,所以并不会锁死,在该代码中的synchronized加锁对象是该类中的this,因为是同一把锁。
static class Counter{
public int count=0;
synchronized void increase(){
count++;
}
synchronized void increase2(){
increase();
}
}
在可重入锁内部包含了线程持有者和计数器两个信息
. 如果某个线程想要加锁的时候发现锁已经被人占用,如果该人正好是自己,那么仍然可以获取到锁,并让计数器自增。
. 解锁的时候,当计数器减为0之后,才会真正的释放锁。这样别的线程才可以获取到这把锁。
2 synchronized使用示例
synchronized本质上是要修改指定对象的对象头,从使用的角度来看,synchronized也需要搭配一个具体的对象来使用。
2.1 修饰普通方法和静态方法
直接修饰普通方法:锁synchronizedDemo对象
public class synchronizedDemo{
public synchronized void method(){
}
}
修饰静态方法:锁的synchronizedDemo类的对象
public class SynchronizedDemo {
public synchronized static void method() {
}
}
这么说可能能简洁明了一点,因为静态成员在创建不同的对象时,只创建一次,因此被static所修饰的变量或者方法是所有实例所公用的。因此在使用synchronized锁静态方法时(因为静态方法是和类直接关联的),无论该类有多少个实例,都会来竞争这个锁资源。但是非普通的方法就不存在不同实例竞争锁资源的情况。
2.2 修饰代码块:明确指定锁那个对象
锁当前对象:
public class SynchronizedDemo {
public void method() {
synchronized (this) {
}
}
}
锁类对象:
public class synchronizedDemo{
public void method(){
synchronized(synchronizedDemo.class);
}
}
第一段代码使用当前实例对象作为锁对象,意味着每一个实例对象都有自己的锁资源,不同实例之间的锁是独立的,不会相互干扰。
第二段代码使用类对象作为锁对象,意味着当多个线程访问该类的不同实例时,他们会竞争同一个锁资源。
3 Java标准库中的线程安全类和不安全类
安全 | 不安全 |
Vector HashTable ConcurrentHashMap StringBuffer | ArrayList LinkedList HashMap TreeMap HashSet TreeSet StringBuilder |
StringBuffer的核心方法带有synchronized。还有的类不涉及到加锁,是因为没有涉及到修改,比如String 。