原理的讲解在下章:Synchronized原理和性质
简介
一、作用
二、地位
三、问题引入
四、用法
1、对象锁
2、类锁
五、面试的7种情况
六、核心总结
一、作用
官方:同步方法支持一种简单的策略来防止线程干扰和内存一致性错误:如果一个对象对多个线程可见,则对该对象变量的所有读取或者写入都是通过同步方法完成的。
通俗:能够保证在同一时刻最多只有一个线程执行该段代码,以达到保证并发安全的效果。
二、地位
Java关键字,被Java语言原生支持
是最基本的互斥同步手段,并发中的元老级角色
三、问题引入
我们使用多线程进行简单的一个操作,使用多线程对一个数值同时a++,大家可以思考一下结果。
代码如下:
public class DispearRequest1 implements Runnable{
//因为main方法是static,则想在main方法中调用必须用static修饰
static DispearRequest1 dispearRequest1=new DispearRequest1();
static int i=0;
public static void main(String[] args) throws InterruptedException {
//两个线程操作
Thread t1=new Thread(dispearRequest1);
Thread t2=new Thread(dispearRequest1);
t1.start();
t2.start();
//join方法是为了在t1和t2两个线程结束的时候才调用system.out.println();
t1.join();
t2.join();
System.out.println(i);
}
@Override
public void run() {
for(int j=0;j<10000;j++){
i++;
}
}
}
正常情况下两个线程同时加1,最后的结果应该是20000。但是我运行的结果:17457(每次都不一样,但是肯定比20000小)。
原因如下:
count++,看上去是一个操作,实际三个
1、读取countList item
2、将count加一
3、将count写入到内存
四、用法
1、对象锁
对象锁:包含方法锁和同步代码块锁
代码块形式:手动指定锁对象
public class SynchronizedObjectCodeBlock2 implements Runnable{
static SynchronizedObjectCodeBlock2 instance=new SynchronizedObjectCodeBlock2();
Object lock1=new Object();//自己创建Lock
Object lock2=new Object();//自己创建Lock
/**
* 使用this的话就只能是一个锁的形式,在使用多个锁,而且之间没有关系的话,则需要使用自己创建的锁
*/
@Override
public void run() {
//使用this或者lock1
synchronized (this) {
System.out.println("我是对象锁1的代码块形式,我叫:"+Thread.currentThread().getName());
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("我运行1结束了:"+Thread.currentThread().getName());
}
// synchronized (lock2) {
// System.out.println("我是对象锁2的代码块形式,我叫:"+Thread.currentThread().getName());
// try {
// Thread.sleep(3000);
// } catch (InterruptedException e) {
// e.printStackTrace();
// }
// System.out.println("我2运行结束了:"+Thread.currentThread().getName());
// }
}
public static void main(String[] args) {
Thread t1=new Thread(instance);
Thread t2=new Thread(instance);
t1.start();
t2.start();
while (t1.isAlive()||t2.isAlive()) {
}
System.out.println("结束了");
}
}
最后结果:我是对象锁1的代码块形式,我叫:Thread-0
我运行1结束了:Thread-0
我是对象锁1的代码块形式,我叫:Thread-1
我运行1结束了:Thread-1
结束了
方法锁形式:Synchronized修饰的普通方法,锁对象默认this
public class SynchronizedObjectMethod3 implements Runnable{
static SynchronizedObjectMethod3 instance=new SynchronizedObjectMethod3();
@Override
public void run() {
method();
}
public synchronized void method(){
System.out.println("我是对象锁的普通方法修饰符形式,我叫:"+Thread.currentThread().getName());
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("我运行结束了:"+Thread.currentThread().getName());
}
public static void main(String[] args) {
Thread t1=new Thread(instance);
Thread t2=new Thread(instance);
t1.start();
t2.start();
while (t1.isAlive()||t2.isAlive()) {
}
System.out.println("结束了");
}
}
运行结果如下:
我是对象锁的普通方法修饰符形式,我叫:Thread-0
我运行结束了:Thread-0
我是对象锁的普通方法修饰符形式,我叫:Thread-1
我运行结束了:Thread-1
结束了
2、类锁
指Synchronized修饰的静态方法或指定锁为Class对象
概念:Java类可能有很多个对象(实例),但是只有1个Class对象
本质:所以所谓的类锁,不过是class对象的锁而已
用法效果:类锁只能在同一时刻被一个对象拥有,对象锁是在不同的实例,也是不同的锁
形式1:Synchronized加在static方法上
public class SynchronizedClassStatic4 implements Runnable{
static SynchronizedClassStatic4 instance1=new SynchronizedClassStatic4();
static SynchronizedClassStatic4 instance2=new SynchronizedClassStatic4();
@Override
public void run() {
method();
}
/**
* 全局的同步,而不是一个对象的形式的话,则用类锁的static
*/
public static synchronized void method(){
System.out.println("我是类锁的static形式,我叫:"+Thread.currentThread().getName());
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("我运行结束了:"+Thread.currentThread().getName());
}
public static void main(String[] args) {
Thread t1=new Thread(instance1);
Thread t2=new Thread(instance2);
t1.start();
t2.start();
while (t1.isAlive()||t2.isAlive()) {
}
System.out.println("结束了");
}
}
运行结果如下:
我是类锁的static形式,我叫:Thread-1
我运行结束了:Thread-1
我是类锁的static形式,我叫:Thread-0
我运行结束了:Thread-0
结束了
形式2:Synchronized(*.class)代码块
public class SynchronizedClassClass5 implements Runnable{
static SynchronizedClassStatic4 instance1=new SynchronizedClassStatic4();
static SynchronizedClassStatic4 instance2=new SynchronizedClassStatic4();
@Override
public void run() {
method();
}
/**
* 全局的同步,而不是一个对象的形式的话,则用类锁的static
*/
public void method(){
synchronized (SynchronizedClassClass5.class) {
System.out.println("我是类锁的Synchronized(*.class)代码块形式,我叫:"+Thread.currentThread().getName());
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("我运行结束了:"+Thread.currentThread().getName());
}
}
public static void main(String[] args) {
Thread t1=new Thread(instance1);
Thread t2=new Thread(instance2);
t1.start();
t2.start();
while (t1.isAlive()||t2.isAlive()) {
}
System.out.println("结束了");
}
}
运行结果如下:
我是类锁的Synchronized(.class)代码块形式,我叫:Thread-1
我运行结束了:Thread-1
我是类锁的Synchronized(.class)代码块形式,我叫:Thread-0
我运行结束了:Thread-0
结束了
上面的几种Synchronized的使用情况已经讲述了,下面来看一下面试中会出的问题
五:面试的7种情况
1、两个线程同时访问一个对象的同步方法:起作用的
2、两个线程访问的是两个对象的同步方法:不起作用的,因为锁对象是不同的对象。不同的实例
3、两个线程访问的是Synchronized的静态方法:起作用的
4、同时访问同步方法与非同步方法:代码同时开始,Synchronized只作用于加的一个方法中。非同步方法不受影响
5、访问同一个对象的不同的普通同步方法:起作用的,虽然没有指定Synchronized用的哪个锁,但是默认用的是this的对象,所以会起作用
6、同时访问静态Synchronized和非静态Synchronized方法:同时运行,static是类锁,用的对象是.class对象,但是没有static关键字的时候,他的锁是this,所以同时运行
7、方法抛出异常后,会释放锁:是的,会自动释放锁
六、核心总结
1、一把锁只能同时被一个线程获取,没有拿到锁的线程必须等待(对应1、5)
2、每个实例都对应有自己的一把锁,不同实例之间互不影响;例外:锁对象是*.class以及Synchronized修饰的是static方法的时候,所有对象共用同一把锁(2 、3、4、6)
3、无论是方法正常执行完毕或者方法抛出异常,都会释放锁(7)