volatile和synchronized的区别

volatile和synchronized的区别
1、概述
在研究并发程序时,我们需要了解java中关键字volatile和synchronized关键字的使⽤以及lock类的⽤法。
供氧器⾸先,了解下java的内存模型:
(1)每个线程都有⾃⼰的本地内存空间(java栈中的帧)。线程执⾏时,先把变量从内存读到线程⾃⼰的本地内存空间,然后对变量进⾏操作。
(2)对该变量操作完成后,在某个时间再把变量刷新回主内存。
那么我们再了解下锁提供的两种特性:互斥(mutual exclusion) 和可见性(visibility):
(1)互斥(mutual exclusion):互斥即⼀次只允许⼀个线程持有某个特定的锁,因此可使⽤该特性实现对共享数据的协调访问协议,这样,⼀次就只有⼀个线程能够使⽤该共享数据;
(2)可见性(visibility):简单来说就是⼀个线程修改了变量,其他线程可以⽴即知道。保证可见性的⽅法:
volatile,synchronized,final(⼀旦初始化完成其他线程就可见)。
2、volatile
volatile是⼀个类型修饰符(type specifier)。它是被设计⽤来修饰被不同线程访问和修改的变量。确保本条指令不会因编译器的优化⽽省略,且要求每次直接读值。
上⾯的话有些拗⼝,简单概括volatile,它能够使变量在值发⽣改变时能尽快地让其他线程知道。
(1)问题来源
⾸先我们要先意识到有这样的现象,编译器为了加快程序运⾏的速度,对⼀些变量的写操作会先在寄存器或者是CPU缓存上进⾏,最后才写⼊内存。⽽在这个过程中,变量的新值对其他线程是不可见的。
在main线程中,thread.setRunning(false);将启动的线程RunThread中的共享变量设置为false,从⽽想让RunThread.java的while循环结束。如果使⽤JVM -server参数执⾏该程序时,RunThread线程并不会终⽌,从⽽出现了死循环。
(2)原因分析
现在有两个线程,⼀个是main线程,另⼀个是RunThread。它们都试图修改isRunning变量。按照JVM内存模型,main线程将isRunning 读取到本地线程内存空间,修改后,再刷新回主内存。
⽽在JVM设置成 -server模式运⾏程序时,线程会⼀直在私有堆栈中读取isRunning变量。因此,RunThread线程⽆法读到main线程改变的isRunning变量。从⽽出现了死循环,导致RunThread⽆法终⽌。
(3)解决⽅法
(4)原理
(4)原理 当对volatile标记的变量进⾏修改时,会将其他缓存中存储的修改前的变量清除,然后重新读取。⼀般来说应该是先在进⾏修改的缓存A中修改为新值,然后通知其他缓存清除掉此变量,当其他缓存B中的线程读取此变量时,会向总线发送消息,这时存储新值的缓存A获取到消息,将新值穿给B。最后将新值写⼊内存。当变量需要更新时都是此步骤,volatile的作⽤是被其修饰的变量,每次更新时,都会刷新上述步骤。
3、synchronized public class RunThread extends Thread {
private boolean isRunning = true;
public boolean isRunning() {
return isRunning;
}
public void setRunning(boolean isRunning) {
this.isRunning = isRunning;
}
@Override
public void run() {
System.out.println("进⼊到run ⽅法中了");
while (isRunning == true) {
}
System.out.println("线程执⾏完成了");
}
}
public class Run {
public static void main(String[] args) {
try {
RunThread thread = new RunThread();
thread.start();
Thread.sleep(1000);
thread.setRunning(false);
} catch (InterruptedException e) {
e.printStackTrace();
}
}}
volatile private boolean isRunning = true;
Java语⾔的关键字,可⽤来给对象和⽅法或者代码块加锁,当它锁定⼀个⽅法或者⼀个代码块的时候,
同⼀时刻最多只有⼀个线程执⾏这段代码。当两个并发线程访问同⼀个对象object中的这个加锁同步代码块时,⼀个时间内只能有⼀个线程得到执⾏。另⼀个线程必须等待当前线程执⾏完这个代码块以后才能执⾏该代码块。然⽽,当⼀个线程访问object的⼀个加锁代码块时,另⼀个线程仍然可以访问该object中的⾮加锁代码块。
(1)synchronized ⽅法
⽅法声明时使⽤,放在范围操作符(public等)之后,返回类型声明(void等)之前.这时,线程获得的是成员锁,即⼀次只能有⼀个线程进⼊该⽅法,其他线程要想在此时调⽤该⽅法,只能排队等候,当前线程(就是在synchronized⽅法内部的线程)执⾏完该⽅法后,别的线程才能进⼊。
⽰例:
public synchronized void synMethod(){
  //⽅法体
  }
如在线程t1中有语句obj.synMethod(); 那么由于synMethod被synchronized修饰,在执⾏该语句前, 需
要先获得调⽤者obj的对象锁, 如果其他线程(如t2)已经锁定了obj (可能是通过obj.synMethod,也可能是通过其他被synchronized修饰的⽅法herSynMethod锁定的obj), t1需要等待直到其他线程(t2)释放obj, 然后t1锁定obj, 执⾏synMethod⽅法. 返回之前之前释放obj锁。
(2)synchronized 块高压直流稳压电源
对某⼀代码块使⽤,synchronized后跟括号,括号⾥是变量,这样,⼀次只有⼀个线程进⼊该代码块.此时,线程获得的是成员锁。
(3)synchronized (this)
1. 当两个并发线程访问同⼀个对象object中的这个synchronized(this)同步代码块时,⼀个时间内只能有⼀个线程得到执⾏。另⼀个线程必须等待当前线
程执⾏完这个代码块以后才能执⾏该代码块。
2. 当⼀个线程访问object的⼀个synchronized(this)同步代码块时,其他线程对object中所有其它synchronized(this)同步代码块的访
问将被阻塞。 
3. 然⽽,当⼀个线程访问object的⼀个synchronized(this)同步代码块时,另⼀个线程仍然可以访问该object中的除
synchronized(this)同步代码块以外的部分。 
4. 第三个例⼦同样适⽤其它同步代码块。也就是说,当⼀个线程访问object的⼀个synchronized(this)同步代码块时,它就获得了这个
object的对象锁。结果,其它线程对该object对象所有同步代码部分的访问都被暂时阻塞。 
5. 以上规则对其它对象锁同样适⽤。
第三点举例说明:
含有synchronized同步块的⽅法m4t1被访问时,线程中m4t2()依然可以被访问。
(4)wait() 与notify()/notifyAll()
(4)wait() 与notify()/notifyAll() wait():释放占有的对象锁,线程进⼊等待池,释放cpu,⽽其他正在等待的线程即可抢占此锁,获得锁的线程即可运⾏程序。⽽sleep()不同的是,线程调⽤此⽅法后,会休
眠⼀段时间,休眠期间,会暂时释放cpu,但并不释放对象锁。也就是说,在休眠期间,其他线程依然⽆法进⼊此代码内部。休眠结束,线程重新获得cpu,执⾏代码。wait()和sleep()最⼤的不同在于wait()会释放对象锁,⽽sleep()不会!
notify(): 该⽅法会唤醒因为调⽤对象的wait()⽽等待的线程,其实就是对对象锁的唤醒,从⽽使得wait()的线程可以有机会获取对象锁。调⽤notify()后,并不会⽴即释放锁,⽽是继续执⾏当前代码,直到synchronized中的代码全部执⾏完毕,才会释放对象锁。JVM则会在等待的线程中调度⼀个线程去获得对象锁,执⾏代码。需要注意的是,wait()和notify()必须在synchronized代码块中调⽤。
notifyAll()则是唤醒所有等待的线程。
4、lock
(1)synchronized的缺陷
synchronized是java中的⼀个关键字,也就是说是Java语⾔内置的特性。那么为什么会出现Lock呢?
如果⼀个代码块被synchronized修饰了,当⼀个线程获取了对应的锁,并执⾏该代码块时,其他线程便只能⼀直等待,等待获取锁的线程释放锁,⽽这⾥获取锁的线程释放锁只会有两种情况:
  1)获取锁的线程执⾏完了该代码块,然后线程释放对锁的占有;
低温导电银胶  2)线程执⾏发⽣异常,此时JVM会让线程⾃动释放锁。
  那么如果这个获取锁的线程由于要等待IO或者其他原因(⽐如调⽤sleep⽅法)被阻塞了,但是⼜没有释放锁,其他线程便只能等待,试想⼀下,这多么影响程序执⾏效率。
public class Thread2 {
public void m4t1() {铁盒制作
synchronized(this) {
int i = 5;
while( i-- > 0) {
System.out.println(Thread.currentThread().getName() + " : " + i);
try {
Thread.sleep(500);
} catch (InterruptedException ie) {
}
}
}
}
public void m4t2() {
int i = 5;
while( i-- > 0) {
System.out.println(Thread.currentThread().getName() + " : " + i);
马德保半球实验try {
Thread.sleep(500);
} catch (InterruptedException ie) {
}
}
}
public static void main(String[] args) {
final Thread2 myt2 = new Thread2();
Thread t1 = new Thread(  new Runnable() {  public void run() {  myt2.m4t1();  }  }, "t1"  );
Thread t2 = new Thread(  new Runnable() {  public void run() { myt2.m4t2();  }  }, "t2"  );
t1.start();
t2.start();
}
}
  因此就需要有⼀种机制可以不让等待的线程⼀直⽆期限地等待下去(⽐如只等待⼀定的时间或者能够响应中断),通过Lock就可以办
到。
再举个例⼦:当有多个线程读写⽂件时,读操作和写操作会发⽣冲突现象,写操作和写操作会发⽣冲突现象,但是读操作和读操作不会发⽣
冲突现象。
  但是采⽤synchronized关键字来实现同步的话,就会导致⼀个问题:
  如果多个线程都只是进⾏读操作,所以当⼀个线程在进⾏读操作时,其他线程只能等待⽆法进⾏读操作。
  因此就需要⼀种机制来使得多个线程都只是进⾏读操作时,线程之间不会发⽣冲突,通过Lock就可以办到。
  另外,通过Lock可以知道线程有没有成功获取到锁。这个是synchronized⽆法办到的。
  总结⼀下,也就是说Lock提供了⽐synchronized更多的功能。但是要注意以下⼏点:
  1)Lock不是Java语⾔内置的,synchronized是Java语⾔的关键字,因此是内置特性。Lock是⼀个类,通过这个类可以实现同步访暖风炉
问;
  2)Lock和synchronized有⼀点⾮常⼤的不同,采⽤synchronized不需要⽤户去⼿动释放锁,当synchronized⽅法或者
synchronized代码块执⾏完之后,系统会⾃动让线程释放对锁的占⽤;⽽Lock则必须要⽤户去⼿动释放锁,如果没有主动释放锁,就有可
能导致出现死锁现象。
(2)urrent.locks包下常⽤的类
public interface Lock {
//获取锁,如果锁被其他线程获取,则进⾏等待
void lock();
/
/当通过这个⽅法去获取锁时,如果线程正在等待获取锁,则这个线程能够响应中断,即中断线程的等待状态。也就使说,当两个线程同时通过lock.lockInte    void lockInterruptibly() throws InterruptedException;
/**tryLock()⽅法是有返回值的,它表⽰⽤来尝试获取锁,如果获取成
*功,则返回true,如果获取失败(即锁已被其他线程获取),则返回
*false,也就说这个⽅法⽆论如何都会⽴即返回。在拿不到锁时不会⼀直在那等待。*/
boolean tryLock();
//tryLock(long time, TimeUnit unit)⽅法和tryLock()⽅法是类似的,只不过区别在于这个⽅法在拿不到锁时会等待⼀定的时间,在时间期限之内如果还拿不到    boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
void unlock(); //释放锁
Condition newCondition();
}
通常使⽤lock进⾏同步:
Lock lock = ...;
lock.lock();
try{
//处理任务
}catch(Exception ex){
}finally{
lock.unlock();  //释放锁
}
trylock使⽤⽅法:

本文发布于:2024-09-21 17:34:09,感谢您对本站的认可!

本文链接:https://www.17tex.com/tex/1/100063.html

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。

标签:线程   变量   等待   代码
留言与评论(共有 0 条评论)
   
验证码:
Copyright ©2019-2024 Comsenz Inc.Powered by © 易纺专利技术学习网 豫ICP备2022007602号 豫公网安备41160202000603 站长QQ:729038198 关于我们 投诉建议