多线程开发离不开锁机制,现在的Java语言中,提供了2种锁,一种是语言特性提供的内置锁,还有一种是 java.util.concurrent.locks 包中的锁。对于synchronized的一些基本概念这里就不重复了,很多博客都写到了。这篇文章针对一个例子,讲一下我的理解。
这是一个调用同步方法的同时,调用该对象非同步方法(但是内部含有同步块)的例子
package tmp;
/**
* Created by Gracecoder on 2017/11/24.
*/
public class Service {
private String anyString = new String();
public void a(){
try {
synchronized (anyString)
{
System.out.println("a begin");
Thread.sleep(3000);
System.out.println("a end");
}
}catch (Exception e)
{
e.printStackTrace();
}
}
synchronized public void b()
{
System.out.println("b begin");
// synchronized (anyString) {
// anyString = new String("hello");
// System.out.println(anyString);
// }
System.out.println("b end");
}
}
package tmp;
/**
* Created by Gracecoder on 2017/11/24.
*/
public class ThreadA extends Thread {
private Service service;
public ThreadA(Service service) {
super();
this.service = service;
}
@Override
public void run() {
service.a();
}
}
package tmp;
/**
* Created by Gracecoder on 2017/11/24.
*/
public class ThreadB extends Thread{
private Service service;
public ThreadB(Service service) {
super();
this.service = service;
}
@Override
public void run() {
service.b();
}
}
package tmp;
/**
* Created by Gracecoder on 2017/11/24.
*/
public class Run {
public static void main(String[] args){
Service service = new Service();
ThreadA a = new ThreadA(service);
ThreadB b = new ThreadB(service);
a.setName("A");
a.start();
b.setName("B");
b.start();
}
}
运行结果:
a begin
b begin
b end
a end
运行这段代码首先线程A先获取成员变量anyString的内置锁,然后线程B访问同步方法,获取当前对象service的锁,这里anyString是对象service的成员变量,结果显示它们异步调用了。
按照我一开始的想法,如果反一下,由线程B先访问同步方法,获取当前对象service的锁之后,其他线程是否能获取其对象成员的锁? 我觉得 不能!但是通过运行代码验证,发现就算B先获得service的锁,A依旧能获得其成员anyString的锁,它们仍然是异步调用!
就很疑惑,我们获取一个对象的锁,但是不代表我们拥有其成员域对象的锁!
小结论:
每个对象都可以用作一个实现同步的锁,这些锁被称为内置锁!
对象的内置锁和对象的状态之间没有内在的关联,虽然大多数类都讲内置锁用作一种有效的加锁机制,但对象的域并不一定通过内置锁来保护。当获取到与对象关联的内置锁时,并不能阻止其他线程访问该对象,只能阻止其他线程获取同一个锁!
那显然每个对象都有自己的内置锁,尽管anyString是service的成员域,但它们两各自的内置锁也是不同的。所以就算反过来,B先获取this(synchronized方法相当于同步块synchronized(this))对象的锁,A还是可以获取anyString的锁。除非我们在B的同步方法中,再次请求获取anyString对象的锁,那就会同步,阻塞到A完成了。
这里还有一个类锁的概念,在synchronized同步方法前面加个static,那么访问该方法,就要获取当前类的锁。
//同步的类方法
synchronized public static void printA()
{
//todo
}
注意:同样的,类锁和对象锁也不存在关联,它们是不同的锁。类锁并不包括该类具体对象的锁,如同对象锁并不包括其对象域的锁。在具体代码中,如果线程A先调用同步类方法(获取了类锁),线程B这时候调用同步方法,调用同步方法的前提是获得当前对象锁,而此时当前对象锁空闲,所以可以异步执行。如果还有一个线程C要调用另一个同步类方法或者同步方法,则必须等A或B执行完毕后,才能获得锁,同步执行!
而类锁对类的所有对象实例起作用的本质是:
实例对象如果要调用synchronized类方法,那么就要获取类锁,此时不同对象获取的类锁都是同一个,那么就会产生同步!
总结:
synchronized 同步的本质就是:是否使用同一个对象监视器
任何对象都有一个自己的内置锁,同步或者异步,主要就是看多线程竞争的是否是同一把锁。区分这一点才是关键,当获取到与对象关联的内置锁时,并不能阻止其他线程访问该对象,只能阻止其他线程获取同一个锁!
synchronized(非this对象x)格式的写法是将x对象本身作为“对象监视器”,这样就可以得出以下3个结论:
- 当多个线程同时执行synchronized(x){}同步代码块时呈同步效果(竞争this当前对象的锁)
- 当其他线程执行x对象中synchronized同步方法时呈同步效果(竞争x对象)
- 当其他线程执行x对象方法里面的synchronized(this)代码块时也是同步
需要注意的是:如果其他线程调用不加synchronized关键字的方法时,还是异步调用,因为其他线程没有去竞争锁。
线程安全包括可见性和原子性,synchronized实现了原子性,同时间接现实可见性。而volatile只有可见性,没有原子性。
1770

被折叠的 条评论
为什么被折叠?



