您的位置:首页 > 编程语言 > Java开发

java 并发编程学习之一 ----synchronized

2016-06-24 11:18 381 查看

1、synchronized 关键字

synchronized,我们谓之锁,主要用来给方法、代码块加锁。当某个方法或者代码块使用synchronized时,那么在同一时刻至多仅有有一个线程在执行该段代码。当有多个线程访问同一对象的加锁方法/代码块时,同一时间只有一个线程在执行,其余线程必须要等待当前线程执行完之后才能执行该代码段。但是,其余线程是可以访问该对象中的非加锁代码块的。

synchronized主要包括两种方法:synchronized 方法、synchronized 块。


1.1 synchronized 方法

public synchronized void getResult();

       1.2 synchronized

synchronized(object) {
//允许访问控制的代码
}


对于synchronized(this)有如下理解:

1、当两个并发线程访问同一个对象object中的这个synchronized(this)同步代码块时,一个时间内只能有一个线程得到执行。另一个线程必须等待当前线程执行完这个代码块以后才能执行该代码块。

2、然而,当一个线程访问object的一个synchronized(this)同步代码块时,另一个线程仍然可以访问object中的非synchronized(this)同步代码块。

3、尤其关键的是,当一个线程访问object的一个synchronized(this)同步代码块时,其他线程对object中所有其他synchronized(this)同步代码块得访问将被阻塞。


     1.3 认识锁

对于同步方法,锁是当前实例对象。

对于同步方法块,锁是Synchonized括号里配置的对象。

对于静态同步方法,锁是当前对象的Class对象。

程序走一个:
public class ThreadTest implements Runnable {
public synchronized void  run() {
for(int i=0;i<3;i++){
System.out.println(Thread.currentThread().getName()+"   is running.....");
}
}
public static void main(String[] args) {
for(int i=0;i<5;i++){
new Thread(new ThreadTest(),"Thread_"+i).start();;
}
}
}
output:
Thread_0   is running.....
Thread_2   is running.....
Thread_2   is running.....
Thread_2   is running.....
Thread_0   is running.....
Thread_0   is running.....
Thread_1   is running.....
Thread_1   is running.....
Thread_1   is running.....
Thread_4   is running.....
Thread_4   is running.....
Thread_4   is running.....
Thread_3   is running.....
Thread_3   is running.....
Thread_3   is running.....
这个结果与我们预期的结果有点不同(这些线程在这里乱跑),照理来说,run方法加上synchronized关键字后,会产生同步效果,这些线程应该是一个接着一个执行run方法的。在上面LZ提到,一个成员方法加上synchronized关键字后,实际上就是给这个成员方法加上锁,具体点就是以这个成员方法所在的对象本身作为对象锁。但是在这个实例当中我们一共new了10个ThreadTest对象,那个每个线程都会持有自己线程对象的对象锁,这必定不能产生同步的效果。所以:如果要对这些线程进行同步,那么这些线程所持有的对象锁应当是共享且唯一的!

改进的程序为:
public class ThreadTest implements Runnable {
public synchronized void  run() {
for(int i=0;i<3;i++){
System.out.println(Thread.currentThread().getName()+"   is running.....");
}
}
public static void main(String[] args) {
ThreadTest t  =new  ThreadTest();
for(int i=0;i<5;i++){
new Thread(t,"Thread_"+i).start();;
}
}
}


output:
Thread_0   is running.....
Thread_0   is running.....
Thread_3   is running.....
Thread_3   is running.....
Thread_3   is running.....
Thread_4   is running.....
Thread_4   is running.....
Thread_4   is running.....
Thread_2   is running.....
Thread_2   is running.....
Thread_2   is running.....
Thread_1   is running.....
Thread_1   is running.....
Thread_1   is running.....
perfect!
我们在看看synchronized代码块:
public class ThreadTest extends Thread {
private String lock;
private String name;

public ThreadTest(String lock,String name){//初始化函数
this.lock = lock;
this.name = name;
}
public void run(){
synchronized (lock) {
for(int i=0;i<3;i++){
System.err.println(name+" is running ...");
}
}
}
public static void main(String[] args) {
String lock = new String("test");
for(int i=0;i<5;i++){
new ThreadTest(lock, "Thread_"+i).start();
}
}
}
output:
Thread_0 is running ...
Thread_0 is running ...
Thread_0 is running ...
Thread_1 is running ...
Thread_1 is running ...
Thread_1 is running ...
Thread_2 is running ...
Thread_2 is running ...
Thread_2 is running ...
Thread_3 is running ...
Thread_3 is running ...
Thread_3 is running ...
Thread_4 is running ...
Thread_4 is running ...
Thread_4 is running ...


在main方法中我们创建了一个String对象lock,并将这个对象赋予每一个ThreadTest2线程对象的私有变量lock。我们知道java中存在一个字符串池,那么这些线程的lock私有变量实际上指向的是堆内存中的同一个区域,即存放main函数中的lock变量的区域,所以对象锁是唯一且共享的。线程同步!!

在这里synchronized锁住的就是lock这个String对象。
接下来, 对于同步方法块,锁是Synchonized括号里配置的对象:
public class ThreadTest extends Thread {
public synchronized  static void test(){
for(int i=0;i<3;i++){
System.err.println(Thread.currentThread().getName()+" is running ");
}
}
public void run(){
test();
}
public static void main(String[] args) {
for(int i=0;i<5;i++){
new ThreadTest().start();;
}
}
}
output:
Thread-0 is running
Thread-0 is running
Thread-0 is running
Thread-3 is running
Thread-3 is running
Thread-3 is running
Thread-4 is running
Thread-4 is running
Thread-4 is running
Thread-2 is running
Thread-2 is running
Thread-2 is running
Thread-1 is running
Thread-1 is running
Thread-1 is running
在这个实例中,run方法使用的是一个同步方法,而且是static的同步方法,那么这里synchronized锁的又是什么呢?我们知道static超脱于对象之外,
它属于类级别的。所以,对象锁就是该静态放发所在的类的Class实例。由于在JVM中,
所有被加载的类都有唯一的类对象,在该实例当中就是唯一的
ThreadTest_03.class对象。
不管我们创建了该类的多少实例,但是它的类实例仍然是一个!所以对象锁是唯一且共享的。线程同步!!

对于静态同步方法,锁是当前对象的Class对象。

如果一个类中定义了一个synchronized的static函数A,也定义了一个synchronized的instance函数B,

那么这个类的同一对象Obj,在多线程中分别访问A和B两个方法时,不会构成同步,因为它们的锁都不一样。

A方法的锁是Obj这个对象,而B的锁是Obj所属的那个Class。

2 锁的升级

java中锁一共有四种状态,无锁状态,偏向锁状态,轻量级锁状态和重量级锁状态,它会随着竞争情况逐渐升级。锁可以升级但不能降级,意味着偏向锁升级成轻量级锁后不能降级成偏向锁。这种锁升级却不能降级的策略,目的是为了提高获得锁和释放锁的效率。下面主要部分主要是对博客:聊聊并发(二)Java
SE1.6中的Synchronized的总结。

锁自旋

我们知道在当某个线程在进入同步方法/代码块时若发现该同步方法/代码块被其他现在所占,则它就要等待,进入阻塞状态,这个过程性能是低下的。

在遇到锁的争用或许等待事,线程可以不那么着急进入阻塞状态,而是等一等,看看锁是不是马上就释放了,这就是锁自旋。锁自旋在一定程度上可以对线程进行优化处理。

偏向锁

偏向锁主要为了解决在没有竞争情况下锁的性能问题。在大多数情况下锁锁不仅不存在多线程竞争,而且总是由同一线程多次获得,为了让线程获得锁的代价更低而引入了偏向锁。当某个线程获得锁的情况,该线程是可以多次锁住该对象,但是每次执行这样的操作都会因为CAS(CPU的Compare-And-Swap指令)操作而造成一些开销消耗性能,为了减少这种开销,这个锁会偏向于第一个获得它的线程,如果在接下来的执行过程中,该锁没有被其他的线程获取,则持有偏向锁的线程将永远不需要再进行同步。

当有其他线程在尝试着竞争偏向锁时,持有偏向锁的线程就会释放锁。

锁膨胀

多个或多次调用粒度太小的锁,进行加锁解锁的消耗,反而还不如一次大粒度的锁调用来得高效。

轻量级锁

轻量级锁能提升程序同步性能的依据是“对于绝大部分的锁,在整个同步周期内都是不存在竞争的”,这是一个经验数据。轻量级锁在当前线程的栈帧中建立一个名为锁记录的空间,用于存储锁对象目前的指向和状态。如果没有竞争,轻量级锁使用CAS操作避免了使用互斥量的开销,但如果存在锁竞争,除了互斥量的开销外,还额外发生了CAS操作,因此在有竞争的情况下,轻量级锁会比传统的重量级锁更慢。

基于这个系列,特此感谢chenssy
大神对我的帮助,万分感谢!
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  编程 并发 java