文章目录
- synchronized简介
- 两种方法
- 1. 对象锁
- 2. 类锁
- 多线程访问同步方法实例
- 总结
- 与Lock对比
- synchronized 原理
- 可重入性
- 反编译
- 可重入性原理
- 可见性原理
- 缺陷
- 常见面试问题
synchronized简介
Synchronized 是Java最基本的关键字,它保证同一时刻只有一个线程访问,属于独占锁
两种方法
//非同步方法示例
//两个线程一起做加法10000次,最后结果小于20000
public class DisappearRequest1 implements Runnable{
static DisappearRequest1 instance = new DisappearRequest1();
static int i =0;
public static void main(String[] args) throws InterruptedException {
Thread t1 =new Thread(instance);
Thread t2 =new Thread(instance);
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println(i);
}
@Override
public void run() {
for (int j = 0; j < 10000; j++) {
i++;
}
}
}
1. 对象锁
对对象的实例上锁,一个实例一把锁,不同实例的锁不同。
1.1 方法锁 public synchronized void method(){}
@Override
public void run() {
method();
}
public synchronized void method() {
//synchronized code
}
1.2 同步代码块锁 synchronized (OBJ){}
@Override
public void run() {
synchronized (this)
{
//synchronized code
}
}
2. 类锁
Java中有多个对象,但是只有一个class对象,所谓类锁就是class对象锁
2.1 静态方法 synchronized static method(){}
@Override
public void run() {
method();
}
public static synchronized void method(){
//synchronized code
}
}
2.2 Class对象 synchronized(*.class)
@Override
public void run() {
method();
}
private void method(){
synchronized (CLASS_NAME.class){
}
}
多线程访问同步方法实例
- 两个线程访问一个对象——串行输出
解释:一个实例一个锁
public class SynchronizedMethod3 implements Runnable{
static SynchronizedMethod3 instance =new SynchronizedMethod3();
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("all finished !");
}
@Override
public void run() {
method();
}
public synchronized void method() {
System.out.println("Im object lock for method type,Im"+Thread.currentThread().getName());
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"finished !");
}
}
- 两个线程同步访问两个对象——并行输出
解释:两个对象两个锁
public class SynchronizedCodeBlock2 implements Runnable{
static SynchronizedCodeBlock2 instance1 =new SynchronizedCodeBlock2();
static SynchronizedCodeBlock2 instance2 =new SynchronizedCodeBlock2();
@Override
public void run() {
synchronized (this){
System.out.println(" I'm lock1 "+Thread.currentThread().getName());
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+" is run over");
}
}
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("finished!");
}
}
- 两个线程同步访问两个对象synchronized的静态方法——串行输出
解释:一个类锁
public class SynchronizedClassStatic4 implements Runnable{
static SynchronizedClassStatic4 instance1 =new SynchronizedClassStatic4();
static SynchronizedClassStatic4 instance2 =new SynchronizedClassStatic4();
public static void main(String[] args) throws InterruptedException{
Thread t1 =new Thread(instance1);
Thread t2 = new Thread(instance2);
t1.start();
t2.start();
while (t1.isAlive() || t2.isAlive()){
}
System.out.println(" all finished !");
}
@Override
public void run() {
method();
}
public static synchronized void method(){
System.out.println("im class lock type static im: "+Thread.currentThread().getName());
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+ " finished ");
}
}
- 两个线程同时访问一个对象的同步方法和非同步方法——并行输出
解释:一个有锁,一个无锁
public class SynchronizedYesAndNo6 implements Runnable{
static SynchronizedYesAndNo6 instance = new SynchronizedYesAndNo6();
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("all finished !");
}
@Override
public void run() {
if(Thread.currentThread().getName().equals("Thread-0")){
method1();
}else {
method2();
}
}
public synchronized void method1(){
System.out.println(" I'm synchornized method "+Thread.currentThread().getName());
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+" is run over");
}
public void method2(){
System.out.println(" I'm none synchornized method "+Thread.currentThread().getName());
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+" is run over");
}
}
- 两个线程对同一个对象的不同普通同步方法(非static)——串行输出
解释:同一个对象锁
public class SynchronizedDifferentMethod7 implements Runnable{
static SynchronizedDifferentMethod7 instance = new SynchronizedDifferentMethod7();
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("all finished !");
}
@Override
public void run() {
if(Thread.currentThread().getName().equals("Thread-0")){
method1();
}else {
method2();
}
}
public synchronized void method1(){
System.out.println(" I'm synchornized method "+Thread.currentThread().getName());
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+" is run over");
}
public synchronized void method2(){
System.out.println(" I'm synchornized 2 method "+Thread.currentThread().getName());
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+" is run over");
}
}
- 同时访问一个对象的静态synchronized 和非静态synchronized方法——并行输出
解释:一个class对象锁,一个普通方法锁,不冲突
public class SynchronizedStaticAndNo8 implements Runnable{
static SynchronizedStaticAndNo8 instance =new SynchronizedStaticAndNo8();
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("all finished !");
}
@Override
public void run() {
if(Thread.currentThread().getName().equals("Thread-0")){
method1();
}else {
method2();
}
}
public static synchronized void method1() {
System.out.println("im static Synchronized method ,im"+Thread.currentThread().getName());
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+" finished !");
}
public synchronized void method2(){
System.out.println("im normal Synchronized method ,im"+Thread.currentThread().getName());
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+" finished !");
}
}
- 方法抛出异常后,会释放锁
解释:与Lock对比,Lock需要手动释放,synchronized无论是执行完毕或者方法抛出异常 都会释放锁
//展示不抛出异常和抛出异常后的对比: 一但抛出异常,第二个线程会立刻进入同步方法,意味着锁已经释放
public class SynchronizedException9 implements Runnable{
static SynchronizedException9 instance =new SynchronizedException9();
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("all finished !");
}
@Override
public void run() {
if(Thread.currentThread().getName().equals("Thread-0")){
method1();
}else {
method2();
}
}
public synchronized void method1() {
System.out.println("Im Throw exception method ,im"+Thread.currentThread().getName());
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
throw new RuntimeException();//runtime 异常不会被捕获,下一句不会被执行
// System.out.println(Thread.currentThread().getName()+" finished !");
}
public synchronized void method2(){
System.out.println("Im Synchronized method 2,im"+Thread.currentThread().getName());
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+" finished !");
}
}
总结
1. 一把锁只能同时被一个线程获取,其他线程等待(例子1,5)
2. 每个实例对应自己的一把锁(例外:锁对象(*.class)和static synchronized 方法,所有对象共用同一把类锁)(例子 2,3,4,6)
3. 无论正常执行完毕或者方法抛出异常,都会释放锁(例子 7)
与Lock对比
lock类有中断能力,可中断获得锁的线程,也可以自我中断
synchronized 原理
synchronized关键字 属于递归锁,具有可重入性、不可中断性、可见性
可重入性
可重入:同一线程内 外层函数获得锁后,内层函数可再次获得锁
不可重入:线程需要在释放锁之后重新竞争获取锁
好处:避免死锁,提升封装性
粒度:线程 (pthread的粒度是调用)
//可重入粒度测试,调用父类方法
public class SynchronizedSuperClass12 {
public synchronized void dosome(){
System.out.println("Im Super Class");
}
public static void main(String[] args) {
child child = new child();
child.dosome();
}
}
//同线程中去调用,可用同一把锁
class child extends SynchronizedSuperClass12{
public synchronized void dosome(){
System.out.println("Im child Class");
super.dosome();
}
}
反编译
javap -verbose xxx.class
主要看monitorenter 和 monitorexit
monitor 的lock 锁一次只能被一个线程所获得;
- monitor锁的三种情况:
- 当monitor计数器为0,目前没有线程获得锁,请求锁。monitor+1 成功获得锁;
- 线程获得锁,monitor计数器≠0,当前线程重入,计数器+1;
- 已被其他线程持有,该线程等待释放;
可重入性原理
- Java JVM跟踪对象被加锁的次数:
- 线程第一次给对象加锁,计数变为1;
- 每当这个相同的线程在此对象上再次获得锁时,计数递增;每当任务离开时,计数递减;
- 当计数为零,锁被完全释放。
可见性原理
Java内存模型中线程结构体内放有共享变量的副本(线程结构可看笔记知识碎片-线程)
本地内存B的数据=A放入主内存的数据=本地内存A的数据
缺陷
效率低: 锁的释放情况少,不能设置超时退出,不能中断
不够灵活: 加锁和释放锁的时机单一,每个锁只有单一的条件(某个对象)。无法知道是否成功获取到锁
反观lock类有 lock.lock() lock.unlock()
常见面试问题
1.锁对象不能为空,作用域不宜过大,避免死锁
2. 如何选择lock和synchronized 关键字? blah blah 尽量使用 util.concurrence 包内方法
3. 多线程访问同步方法具体情况 上述7种