- 某一段代码中的私有变量,不存在线程安全问题
- 脏读:无效数据的读出
- 如果多个线程共同访问一个对象那个中的实例变量,则有可能出现线程安全的问题。
synchronized关键字
- 关键字
synchronized
使方法成为线程安全的,且取得的锁是对象锁,不是一段代码或者方法当做锁 - 调用关键字
synchronized
声明的方法一定是排队运行的,只有共享的资源读写才需要同步化 - 一个线程拿到了一个
synchronized
声明的方法的锁,那么这个类中的其它synchronized
声明的方法都不能被其他线程访问(当然非synchronized
声明的方法还可以被访问),直到该线程释放这个锁 synchronized
具有锁重入功能,当一个线程得到一个对象锁后,再次请求此对象锁是可以再次得到该对象锁的。- 当存在父子类继承关系时,子类是完全可以通过可重入锁的机制调用父类中的同步方法。
- 一个线程中执行的代码出现异常时,其所持有的锁会自动释放
- 同步不具有继承性
synchronized
关键字相当于sychronized(this){整个方法的代码块}
synchronized
同步代码块:
-
在
synchronized
块中就是异步执行,在synchrinized
块中就是同步执行。 -
当一个线程访问object的一个
synchronized(this)
同步代码块时,其他线程对同一个object中所有其他synchronized(this)
同步代码块的访问将被阻 -
synchronized同步代码块可以使用任意对象作为对象监视器,实现同步。任意对象 大多数是实例变量以及方法的参数
-
同步代码块放在非同步的synchronized方法中进行声明,并不能保证调用方法的线程的同步性,可能出现脏读;
细化的3个结论
-
当多个线程同时执行synchronized(x){}同步代码块时呈现同步效果
-
当其他线程执行x对象中synchronized同步方法时呈现同步效果
-
其他线程执行x对象方法里面的synchronized(this)代码块时也呈现同一个效果
这三个结论有点绕,本质上是一个结论:
本质上是因为synchronized
中是同一把锁,这把锁被一个线程持有时不能被另一个线程持有
package com.company;
public class Thread1 extends Thread {
private Service service;
private MyObject object;
public Thread1(Service service,MyObject object){
this.service = service;
this.object = object;
}
public void run(){
try {
service.testMethod1(object);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
package com.company;
public class Thread2 extends Thread {
private Service service;
private MyObject object;
public Thread2(MyObject object){
this.object = object;
}
public void run(){
object.speedPrintString();
}
}
package com.company;
public class Service {
public void testMethod1(MyObject object) throws InterruptedException {
synchronized (object) {
System.out.println("testMethod1____getLock time="+System.currentTimeMillis()+"run ThreadName="+Thread.currentThread().getName());
Thread.sleep(5000);
System.out.println("testMethod1____releaseLock time="+System.currentTimeMillis()+"run ThreadName="+Thread.currentThread().getName());
}
}
}
package com.company;
public class MyObject
{
public void speedPrintString(){
synchronized (this) {
System.out.println("speedPrintString_____getLock time=" + System.currentTimeMillis() + "run ThreadName=" + Thread.currentThread().getName());
System.out.println("-------------------");
System.out.println("speedPrintString_____releaseLock time=" + System.currentTimeMillis() + "run ThreadName=" + Thread.currentThread().getName());
}
}
}
package com.company;
public class Main {
public static void main(String[] args) {
// write your code here
Service service = new Service();
MyObject object = new MyObject();
MyObject object1 =new MyObject();
Thread1 a = new Thread1(service,object);
a.setName("a");
a.start();
Thread2 b = new Thread2(object1);//传入不同的obejct对象作为锁就不会同步了
b.setName("b");
b.start();
}
}
- 静态同步synchronized方法,是对当前的*.java文件对应的Class类进行持锁。 但是Class锁并不影响非Class锁的类。
- 锁对象尽量不要选择
String
,因为String
常量池可能造成问题 - 锁对象不变,对象的属性改变还是同一把锁
volatile关键字
JVM设置为Server服务器的环境中,为了线程的运行效率,线程一直在私有堆栈中取得变量的值,很有可能私有堆栈和公共堆栈的值不同步,从而造成问题。
volatile
关键字主要作用就是强制当前线程从公共堆栈中取值。
volatile
可以阻止JVM的指令重排,可以解决单例模式用的线程安全问题
volatile和synchronized比较
volatile
是线程同步的轻量级实现,且只能修饰于变量,不会引起线程上下文的切换和调度;synchronized
可以修饰方法和代码块- 多线程访问
volatile
不会发生阻塞,而synchronized
会产生阻塞 volatile
能保证数据的可见性,但不能保证原子性;synchronized
能保证原子性,也可以间接保证可见性,它会把私有内存和公有内存的数据进行同步(原子性:要么完全执行,要么完全不执行)volatile
解决的是变量在多个线程之间的可见性;synchronized
解决的是多个线程之间访问资源的同步性
使用原子类atomic
来实现i++
之类的操作,能保证操作的原子性,但是并不能保证方法调用的是同步的。
Thread.sleep()
不会释放当前线程持有的锁
类锁与对象锁
- 当我们对
statitc
方法或者变量加锁时,或者sychronized(ClassName.class)
,产生的是类锁; - 类锁的作用范围是所有类实例
- 而对象锁的作用范围是同一个对象,即多个线程访问不同对象的
synchronized
方法是互不冲突的