一、Synchronized关键字加在普通方法上和加在静态方法上有什么区别?
synchronized修饰不加static的方法,锁是加在单个对象上,不同的对象没有竞争关系;
synchronized修饰加了static的方法,锁是加载类上,这个类所有的对象竞争一把锁。
- 情况1:
package com.cx.domain;
public class Thread01 {
public static void main(String[] args) {
for (int i = 0; i < 3; i++) {
Thread thread = new MyThread();
thread.start();
}
}
}
package com.cx.domain;
public class MyThread extends Thread{
@Override
public void run() {
Sync sync = new Sync();
sync.test();
}
}
package com.cx.domain;
public class Sync{
//方法同步
public synchronized void test(){
System.out.println("test1111开始执行。。。。");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("test1111执行结束。。。");
}
}
执行结果:
结论:
以上是不同的对象执行同一个非static方法,锁并没有生效,原因是Synchronized修饰非静态方法,实际上是对调用该方法的对象加锁,此处是三个对象,并非同一个,所以没有生效。
情况1:不同对象在两个线程中调用同一个同步方法
结果:不会产生互斥。
解释:因为是两个对象,锁针对的是对象,并不是方法,所以可以并发执行,不会互斥。形象的来说就是因为我们每个线程在调用方法的时候都是new 一个对象,那么就会出现两个空间,两把钥匙,
- 情况2:
package com.cx.domain;
public class Thread02 {
public static void main(String[] args) {
Sync sync = new Sync();
for (int i = 0; i < 3; i++) {
Thread thread = new MyThread2(sync);
thread.start();
}
}
}
package com.cx.domain;
public class MyThread2 extends Thread{
private Sync sync;
public MyThread2(Sync sync) {
this.sync = sync;
}
public void run() {
sync.test();
}
}
package com.cx.domain;
public class Sync{
//方法同步
public synchronized void test(){
System.out.println("test1111开始执行。。。。");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("test1111执行结束。。。");
}
}
执行结果:
结论:
以上是同一个对象执行同一个非static方法,锁生效了,因为是同一个对象。
- 情况3:
package com.cx.domain;
public class Thread03 {
public static void main(String[] args) {
final Sync sync = new Sync();
//线程1
new Thread(new Runnable() {
public void run() {
sync.test();
}
}).start();
//线程2
new Thread(new Runnable() {
public void run() {
sync.test2();
}
}).start();
//线程3
new Thread(new Runnable() {
public void run() {
sync.test3();
}
}).start();
}
}
package com.cx.domain;
public class Sync{
//方法同步
public synchronized void test(){
System.out.println("test1111开始执行。。。。");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("test1111执行结束。。。");
}
public synchronized void test2(){
System.out.println("test2222开始执行。。。。");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("test2222执行结束。。。");
}
//代码块同步
public void test3(){
// String obj = "";
synchronized(this){
System.out.println(this);
System.out.println("test3333开始执行。。。。");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("test3333执行结束。。。");
}
}
}
执行结果:
结论:
以上是同一个对象执行不同的非static同步方法,锁生效了。因为是同一个对象锁。需要依次等待释放锁。
情况3:同一个对象在三个线程中分别访问该对象的三个同步方法
结果:会产生互斥。
解释:因为锁针对的是对象,当对象调用一个synchronized方法时,其他同步方法需要等待其执行结束并释放锁后才能执行。
- 情况1:
package com.cx.domain;
public class Thread01 {
public static void main(String[] args) {
for (int i = 0; i < 3; i++) {
Thread thread = new MyThread();
thread.start();
}
}
}
package com.cx.domain;
public class MyThread extends Thread{
@Override
public void run() {
Sync sync = new Sync();
sync.test();
}
}
package com.cx.domain;
public class Sync{
//静态方法同步
public synchronized static void test(){
System.out.println("test1111开始执行。。。。");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("test1111执行结束。。。");
}
}
执行结果:
结论:
以上是不同对象执行同一个静态同步方法,锁生效了。因为Synchronized修饰静态方法,实际上是对该类对象加锁,尽管Sync对象不同,但是类对象是同一个,还是得依次等候。
- 情况2:
package com.cx.domain;
public class Thread02 {
public static void main(String[] args) {
Sync sync = new Sync();
for (int i = 0; i < 3; i++) {
Thread thread = new MyThread2(sync);
thread.start();
}
}
}
package com.cx.domain;
public class MyThread2 extends Thread{
private Sync sync;
public MyThread2(Sync sync) {
this.sync = sync;
}
public void run() {
sync.test();
}
}
package com.cx.domain;
public class Sync{
//静态方法同步
public synchronized static void test(){
System.out.println("test1111开始执行。。。。");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("test1111执行结束。。。");
}
}
执行结果:
结论:
以上是同一个对象执行同一个静态同步方法。锁生效了。因为Synchronized修饰静态方法,实际上是对该类对象加锁,类对象是同一个,还是得依次等候。
- 情况3:
package com.cx.domain;
public class Thread03 {
public static void main(String[] args) {
final Sync sync = new Sync();
new Thread(new Runnable() {
public void run() {
sync.test();
}
}).start();
new Thread(new Runnable() {
public void run() {
sync.test2();
}
}).start();
}
package com.cx.domain;
public class Sync{
//方法同步
public synchronized static void test(){
System.out.println("test1111开始执行。。。。");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("test1111执行结束。。。");
}
public synchronized void test2(){
System.out.println("test2222开始执行。。。。");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("test2222执行结束。。。");
}
}
执行结果:
结论:
以上是同一个对象执行一个静态方法一个非静态方法,锁未生效。因为虽然是一个对象调用,但是两个方法的锁类型不同,调用的静态方法实际上是类对象在调用,即这两个方法产生的并不是同一个对象锁,因此不会互斥,会并发执行。
情况3:一个对象在两个线程中分别调用一个静态同步方法和一个非静态同步方法
结果:不会产生互斥。
解释:因为虽然是一个对象调用,但是两个方法的锁类型不同,调用的静态方法实际上是类对象在调用,即这两个方法产生的并不是同一个对象锁,因此不会互斥,会并发执行。
- 情况4:
package com.cx.domain;
public class Thread03 {
public static void main(String[] args) {
final Sync sync = new Sync();
//线程1
new Thread(new Runnable() {
public void run() {
sync.test();
}
}).start();
//线程2
new Thread(new Runnable() {
public void run() {
sync.test2();
}
}).start();
//线程3
new Thread(new Runnable() {
public void run() {
sync.test3();
}
}).start();
}
}
package com.cx.domain;
public class Sync{
//方法同步
public synchronized static void test(){
System.out.println("test1111开始执行。。。。");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("test1111执行结束。。。");
}
public synchronized static void test2(){
System.out.println("test2222开始执行。。。。");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("test2222执行结束。。。");
}
//代码块同步
public static void test3(){
synchronized(Sync.class){
System.out.println("test3333开始执行。。。。");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("test3333执行结束。。。");
}
}
}
执行结果:
结论:
执行三个不同的静态方法,锁生效,因为类对象是同一个(Sync.class)。
-
总结:
对于类锁synchronized static,是通过该类直接调用加类锁的方法,而对象锁是创建对象调用加对象锁的方法,两者访问是不冲突的,对于同一类型锁锁住的方法,同一对象是无法同时访问的,因为调用类锁锁住的方法无法创建实例对象,所以同一个类中只有加了对象锁的两个方法可以被同时访问,前提是new两个对象。
对象锁:
- 同一个对象访问同一个非static方法,对象锁一致,锁生效。
- 同一个对象访问不同非static方法,对象锁一致,锁生效。
- 同一个对象访问static方法和非static方法,锁失效。锁类型不一致。
- 不同对象访问同一个非static方法,对象锁不一致,锁失效。
类锁:
- 不同对象访问同一个static方法,锁生效,同一个类锁。类锁一致,对象锁不一致。
- 同一个对象访问不同的static方法,锁生效,同一个类锁。类锁一致,对象锁一致。
- 同一个对象访问static方法和非static方法,锁失效。锁类型不一致。
二、Synchronized和Lock的比较
ava多线程可以通过:
- synchronized关键字
- Java.util.concurrent包中的lock接口和ReentrantLock实现类
这两种方式实现加锁.
如果一个代码块被synchronized修饰了,当一个线程获取了对应的锁,并执行该代码块时,其他线程便只能一直等待,等待获取锁的线程释放锁,而这里获取锁的线程释放锁只会有两种情况:
- 获取锁的线程执行完了该代码块,然后线程释放对锁的占有;
- 线程执行发生异常,此时JVM会让线程自动释放锁。
那么如果这个获取锁的线程由于要等待IO或者其他原因(比如调用sleep方法)被阻塞了,但是又没有释放锁,其他线程便只能干巴巴地等待,试想一下,这多么影响程序执行效率。因此就需要有一种机制可以不让等待的线程一直无期限地等待下去(比如只等待一定的时间或者能够响应中断),通过Lock就可以办到。
再举个例子:当有多个线程读写文件时,读操作和写操作会发生冲突现象,写操作和写操作会发生冲突现象,但是读操作和读操作不会发生冲突现象。
但是采用synchronized关键字来实现同步的话,就会导致一个问题:
如果多个线程都只是进行读操作,所以当一个线程在进行读操作时,其他线程只能等待无法进行读操作。因此就需要一种机制来使得多个线程都只是进行读操作时,线程之间不会发生冲突,通过Lock就可以办到。
另外,通过Lock可以知道线程有没有成功获取到锁。这个是synchronized无法办到的。
对比:
- Lock不是Java语言内置的,synchronized是Java语言的关键字,因此是内置特性。Lock是一个类,通过这个类可以实现同步访问;
- Lock和synchronized有一点非常大的不同,采用synchronized不需要用户去手动释放锁,当synchronized方法或者synchronized代码块执行完之后,系统会自动让线程释放对锁的占用;而Lock则必须要用户去手动释放锁,如果没有主动释放锁,就有可能导致出现死锁现象。
总结来说,Lock和synchronized有以下几点不同:
- Lock是一个接口,而synchronized是Java中的关键字,synchronized是内置的语言实现;
- synchronized在发生异常时,会自动释放线程占有的锁,因此不会导致死锁现象发生;而Lock在发生异常时,如果没有主动通过unLock()去释放锁,则很可能造成死锁现象,因此使用Lock时需要在finally块中释放锁;
- Lock可以让等待锁的线程响应中断,而synchronized却不行,使用synchronized时,等待的线程会一直等待下去,不能够响应中断;(I/O和Synchronized都能相应中断,即不需要处理interruptionException异常)
- 通过Lock可以知道有没有成功获取锁,而synchronized却无法办到。
- Lock可以提高多个线程进行读操作的效率。
- 在性能上来说,如果竞争资源不激烈,两者的性能是差不多的,而当竞争资源非常激烈时(即有大量线程同时竞争),此时Lock的性能要远远优于synchronized。所以说,在具体使用时要根据适当情况选择。