Java关键字synchronized的认知
当我们在java行业工作一段时间之后,会发现,我们最常接触的就是CRUD的操作。但是当我们不满足当前薪资的情况的下,想跳槽的时候,看到大部分公司的简介上都写着精通多线程。此时我们会发现我们掌握的东西还是太少,是时候做出一些改变了。就去网上各种搜索与多线程相关的资料,但是现在网上的帖子那么多,对于初学者的我们不能分别出哪个是正确的,哪个是错误的。如果跟着错误的帖子越走越远,其结果可想而知是多么的可怕。好了书回正传吧
1.synchronized的简介
1.1. synchronized的作用
以下是Java官方解释
Java provides a way of creating threads and synchronizing their task by using synchronized blocks. Synchronized blocks in Java are marked with the synchronized keyword. A synchronized block in Java is synchronized on some object. All synchronized blocks synchronized on the same object can only have one thread executing inside them at a time. All other threads attempting to enter the synchronized block are blocked until the thread inside the synchronized block exits the block.
其实本质就是 能够保证在同一时刻最多只有一个线程执行该段代码,以达到保证并发安全的效果
1.2. synchronized的地位
是java中的关键字
1.3. 不使用并发手段可能会出现的后果
public class UnSafeDemo implements Runnable{
private int count;
public static void main(String[] args) {
UnSafeDemo instance = new UnSafeDemo();
Thread t1 = new Thread(instance);
Thread t2 = new Thread(instance);
t1.start();
t2.start();
while (t1.isAlive() || t2.isAlive()){
}
System.out.println(instance.count);
}
@Override
public void run() {
for(int i = 0; i < 100000; i++){
count++;
}
}
}
运行上述代码几次之后会发现,无论怎样结果都不会是200000,其原因是count++,看上去只是一个操作,实际上包含了三个动作:
- 1.读取count
- 2.将count+1
- 3.将count的值写入到内存中
在多线程的情况下,一个线程执行到任意一步的时候,当前线程都可能被打断,因此会出现问题。我们假设A线程的count读取的值是10,紧接着将count值修改为11,但是A线程还没来得急将count写入到内存当中,就被打断了。B线程读取到的count值还是10,它也是将count的值改为11。这就出现了count加两次1是11的情况
2. synchronized的使用方法
2.1. 对象锁
2.1.1. 普通方法锁(默认锁对象为this当前实例对象)
package thread;
public class SafeObjectClass implements Runnable {
private int count;
public static void main(String[] args) {
SafeObjectClass safeObjectClass = new SafeObjectClass();
Thread t1 = new Thread(safeObjectClass);
Thread t2 = new Thread(safeObjectClass);
t1.start();
t2.start();
while (t1.isAlive() || t2.isAlive()){
}
System.out.println(safeObjectClass.count);
}
@Override
public void run() {
method();
}
/**
* 同步方法
*/
private synchronized void method(){
for(int i = 0 ; i < 100000 ; i++){
count ++;
}
}
}
2.1.2. 同步代码块(自己指定锁对象)
package thread;
public class SafeObjectClass implements Runnable {
private int count;
Object lock = new Object();
public static void main(String[] args) {
SafeObjectClass safeObjectClass = new SafeObjectClass();
Thread t1 = new Thread(safeObjectClass);
Thread t2 = new Thread(safeObjectClass);
t1.start();
t2.start();
while (t1.isAlive() || t2.isAlive()){
}
System.out.println(safeObjectClass.count);
}
@Override
public void run() {
//同步代码块
synchronized (lock){
for(int i = 0 ; i < 100000 ; i++){
count ++;
}
}
}
}
2.2. 类锁(指synchronized修饰静态的方法或指定锁为Class对象)
Java类可能会有多个对象,但是只有一个Class对象,其所谓的类锁,就是Class对象的锁
2.2.1. 静态方法锁
package thread;
public class SafeStaticClass implements Runnable {
public static void main(String[] args) {
SafeStaticClass safeStaticClass = new SafeStaticClass();
SafeStaticClass safeStaticClass1 = new SafeStaticClass();
Thread t1 = new Thread(safeStaticClass);
Thread t2 = new Thread(safeStaticClass1);
t1.start();
t2.start();
while (t1.isAlive() || t2.isAlive()){
}
System.out.println("finished");
}
@Override
public void run() {
method();
}
/**
* 类方法锁
*/
public static synchronized void method(){
System.out.println("当前线程:"+Thread.currentThread().getName()+" 进入");
//TODO 做一些业务操作
System.out.println("当前线程:"+Thread.currentThread().getName()+" 结束");
}
}
</pre>
2.2.2. class锁
package thread;
public class SafeStaticClass implements Runnable {
private int count;
public static void main(String[] args) {
SafeStaticClass safeStaticClass = new SafeStaticClass();
SafeStaticClass safeStaticClass1 = new SafeStaticClass();
Thread t1 = new Thread(safeStaticClass);
Thread t2 = new Thread(safeStaticClass1);
t1.start();
t2.start();
while (t1.isAlive() || t2.isAlive()){
}
System.out.println(safeStaticClass.count);
}
@Override
public void run() {
//类锁
synchronized (SafeObjectClass.class){
for(int i = 0 ; i< 100000 ; i ++){
count++;
}
}
}
}
3. synchronized的性质
- 可重入 同一线程的外层函数获得锁之后,内层函数可以直接再次获取该锁
- 不可被中断 一旦锁被别人获得了,如果我还想获得,只能选择等待或者阻塞,直到别的线程释放这个锁。如果别人永远不释放锁,那么我只能永远等待下去
4. 多线程访问同步方法的7种情况
- 两个线程同时访问一个对象的同步方法 线程安全,只有一个线程执行完成之后,另一个线程才可以进入此方法体
- 两个线程访问的是两个对象的同步方法 两个方法会同时运行,因此两个方法拿到的对象锁不是同一个
- 两个线程访问的是synchronized的静态方法 线程安全,只有一个线程执行完成之后,另一个线程才可以进入此方法体,因此静态方法拿到的class锁,每个Class类只有一个class对象
- 同时访问同步方法和非同步方法 两者互相不干扰,同时进行。
- 访问同一个对象的不同的普通同步方法 只有一个线程执行完成之后,另一个线程才可以进入此方法体,因此这两个方法获取的是同一个对象锁
- 同时访问静态synchronized和非静态synchronized方法 两者互不影响,因为两者拿到的不是相同的锁
- 被synchronized修饰的方法抛出异常后,会主动释放当前对象锁
鉴于以上7种情况的总结:
- 一把锁只能同时被一个线程获取,没有拿到锁的线程必须等待
- 每个实例都对应有自己的一把锁,不同实例之间互不影响;锁对象是.class以及synchronized修饰的是static方法的时候,所有对象共用同一把锁
- 无论是方法正常运行完成还是方法抛出异常,都会释放锁
5. 缺陷
- 效率低 锁的释放情况少、试图获得锁时不能设定超时、不能终端一个正在试图获得锁的线程
- 不够灵活 枷锁和释放的时机单一,每个锁仅有单一的条件
- 无法知道是否成功获取到锁