java并发编程学习(八) synchronized详解
synchronized实现同步的基础:java中的每一个对象都可以作为锁,具体表现为三种形式
1> 对于普通的同步方法,锁是当前实例
2> 对于静态同步方法,锁是当前类的class对象
3> 对于同步方法块, 锁是synchronized ,synchronize(obj) {} ,obj 是锁对象
当一个线程试图访问同步代码块时候,它首先必须得到锁,退出或者抛出异常必须释放锁。
从jvm中得知synchronized的实现原理,jvm是基于进入和退出monitor来实现方法同步和代码块同步的。在多线程并发中,非线程安全存在实例变量和静态变量的访问,方法中的变量不会存在线程安全问题
1. synchronize同步方法
多个线程修改同一个实例变量,存在交叉,就是线程不安全
public class ParentDemo {
private int i = 0;
public void test(){
if(Thread.currentThread().getName().equals("A")){
i = 100;
System.out.println("A set over");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}else {
i = 200;
System.out.println("B set over ");
}
System.out.println("Thread name "+Thread.currentThread().getName()+",i="+i);
}
public static void main(String[] args) {
final ParentDemo d = new ParentDemo();
Thread t = new Thread(new Runnable() {
@Override
public void run() {
d.test();
}
},"A");
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
d.test();
}
},"B");
t.start();
t1.start();
}
}
方法前,加上synchronize,保证任意时刻只有一个线程执行方法每个线程都有自己独立的栈空间,如果线程间需要通信,就需要借助jmm内存模型,构建线程间的happens-before原则,实现共享变量的内存可见性。
synchronized作用:修饰方法和同步块来使用,确保多个线程在同一个时刻,只能有一个线程在同步块和方法中,保证线程访问的可见性和排他性
synchronized方法的锁是对象的锁
public class ParentDemo {
private int i = 0;
public synchronized void test(){
if(Thread.currentThread().getName().equals("A")){
i = 100;
System.out.println("A set over");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}else {
i = 200;
System.out.println("B set over ");
}
System.out.println("Thread name "+Thread.currentThread().getName()+",i="+i);
}
public static void main(String[] args) {
final ParentDemo d = new ParentDemo();
Thread t = new Thread(new Runnable() {
@Override
public void run() {
d.test();
}
},"A");
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
d.test();
}
},"B");
t.start();
t1.start();
}
}
synchronized中的脏读问题
public class PublicVar {
private String username="A";
private String password="AA";
synchronized public void setValue(String username,String password){
this.username = username;
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
this.password = password;
System.out.println(Thread.currentThread().getName()+" set value username = "+username+",password="+password);
}
public void getValue (){
System.out.println(Thread.currentThread().getName()+" get value username = "+this.username+",password="+this.password);
}
public static void main(String[] args) {
final PublicVar pv = new PublicVar();
Thread t = new Thread(new Runnable() {
@Override
public void run() {
pv.setValue("B", "BB");
}
},"B");
t.start();
try {
Thread.sleep(20);
} catch (InterruptedException e) {
e.printStackTrace();
}
pv.getValue();
}
}
执行结果:main get value username = B,password=AA
B set value username = B,password=BB
主线程读取到的值,部分值,已经被线程B改过出现了脏读,解决脏读方案,在getvalue上加上synchronized线程访问一个对象的synchronized方法块中,可以再次调用本类的其他synchronized方法,也就是再次请求这个对象锁,是可行的,也就是自己能够再次获取自己的内部锁,可重入锁也支持父子类继承的环境中。
/**
* 重入锁特性:调用本类的synchronize方法
* @author zhouy
*
*/
public class ParentDemo {
public synchronized void service1(){
System.out.println("service1");
service2();
}
public synchronized void service2(){
System.out.println("service2");
}
public static void main(String[] args) {
ParentDemo d = new ParentDemo();
d.service1();
}
}
说明: 一个线程执行service1时候,在去获取service2时候,此时对象的锁还没有释放,当再次取得这个对象的锁,
还是可以获取到,并不会死锁,说明锁是可以重入的
/**
* 重入锁:父子继承中的重入锁
* @author zhouy
*
*/
public class Main {
public int i = 10;
synchronized public void operateTMainMethod(){
try {
i--;
System.out.println("main print i = "+i);
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
Sub sub = new Sub();
sub.operateIsSubMethod();
}
}
class Sub extends Main{
synchronized public void operateIsSubMethod(){
try{
while(i>0){
i--;
System.out.println("sub print i = "+i);
Thread.sleep(1000);
operateTMainMethod();
}
}catch(InterruptedException e){
e.printStackTrace();
}
}
}
说明:当存在父子继承关系时,子类完全可以通过"重入锁"调用父类的同步方法
出现异常,锁自动释放
public class Service {
synchronized public void testMethod(){
if(Thread.currentThread().getName().equals("a")){
System.out.println("ThreadName = "+Thread.currentThread().getName()+" run beginTime = "+
System.currentTimeMillis());
int i=1;
while(i==1){
if((Math.random()+"").substring(0, 8).equals("0.123456")){
System.out.println("ThreadName = "+Thread.currentThread().getName()+"exception run beginTime = "+System.currentTimeMillis());
Integer.parseInt("a");
}
}
}else{
System.out.println("ThreadB run Time = "+System.currentTimeMillis());
}
}
public static void main(String[] args) {
Service service = new Service();
Thread1 t1 =new Thread1(service);
t1.setName("a");
t1.start();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
Thread2 t2 = new Thread2(service);
t2.setName("b");
t2.start();
}
}
class Thread1 extends Thread{
private Service service;
public Thread1(Service service){
super();
this.service = service;
}
@Override
public void run() {
service.testMethod();
}
}
class Thread2 extends Thread{
private Service service;
public Thread2(Service service){
super();
this.service = service;
}
@Override
public void run() {
service.testMethod();
}
}
打印:ThreadName = a run beginTime = 1514860827406
ThreadName = aexception run beginTime = 1514860827797
Exception in thread "a" java.lang.NumberFormatException: For input string: "a"
at java.lang.NumberFormatException.forInputString(NumberFormatException.java:65)
at java.lang.Integer.parseInt(Integer.java:492)
at java.lang.Integer.parseInt(Integer.java:527)
at cn.itcast.test4.Service.testMethod(Service.java:12)
at cn.itcast.test4.Thread1.run(Service.java:43)
ThreadB run Time = 1514860828407
说明: 线程a抛出异常,锁释放,线程b拿到锁继续执行同步不具有继承性
/**
* 同步不可以继承
* @author zhouy
*
*/
public class Main {
synchronized public void serviceMethod(){
System.out.println("in main 下一步 sleep begin threadname = "+Thread.currentThread().getName()+" time = "+System.currentTimeMillis());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("in main 下一步 sleep end threadname = "+Thread.currentThread().getName()+" time = "+System.currentTimeMillis());
}
public static void main(String[] args) {
final Sub sub = new Sub();
Thread A = new Thread(new Runnable() {
@Override
public void run() {
sub.serviceMethod();
}
},"A");
A.start();
Thread B = new Thread(new Runnable() {
@Override
public void run() {
sub.serviceMethod();
}
},"B");
B.start();
}
}
class Sub extends Main{
public void serviceMethod (){
System.out.println("in sub 下一步 sleep begin threadname = "+Thread.currentThread().getName()+" time = "+System.currentTimeMillis());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("in sub 下一步 sleep end threadname = "+Thread.currentThread().getName()+" time = "+System.currentTimeMillis());
super.serviceMethod();
}
}
执行结果: sub中打印 sub的 输出是异步的,只有main是同步的,说明同步并没有继承
in sub 下一步 sleep begin threadname = A time = 1514862243251
in sub 下一步 sleep begin threadname = B time = 1514862243251
in sub 下一步 sleep end threadname = B time = 1514862244257
in main 下一步 sleep begin threadname = B time = 1514862244257
in sub 下一步 sleep end threadname = A time = 1514862244257
in main 下一步 sleep end threadname = B time = 1514862245272
in main 下一步 sleep begin threadname = A time = 1514862245272
in main 下一步 sleep end threadname = A time = 1514862246275
2.synchronized同步语句块
synchronized方法的弊端
public class ObjectService {
public void serviceMethod(){
Object object = new Object();
synchronized (object) {
System.out.println(Thread.currentThread().getName()
+" begin time " +System.currentTimeMillis());
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+" end time "+
System.currentTimeMillis());
}
}
public static void main(String[] args) {
final ObjectService os = new ObjectService();
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
os.serviceMethod();
}
},"A");
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
os.serviceMethod();
}
},"B");
t1.start();
t2.start();
}
}
如何使用同步块解决同步方法的弊端:一半同步,一半异步
public class Task {
private String getData1;
private String getData2;
public void dolongTask(){
System.out.println(Thread.currentThread().getName()+" begin ....");
String privateData1 = "执行任务1 获取值:"+Thread.currentThread().getName();
String privateData2 = "执行任务2获取值:"+Thread.currentThread().getName();
synchronized (this) {
this.getData1 = privateData1;
this.getData2 = privateData2;
}
System.out.println(privateData1);
System.out.println(privateData2);
System.out.println(Thread.currentThread().getName()+" end....");
}
B begin ....
A begin ....
执行任务1 获取值:B
执行任务2获取值:B
执行任务1 获取值:A
执行任务2获取值:A
B end....
A end....
note: 多个线程访问这个方法的效率增加了
synchronized代码的同步性常见的使用场景
public class Task {
public void doServiceA(){
synchronized (this) {
System.out.println("A begin ....");
System.out.println("A end ......");
}
}
public void doServiceB(){
synchronized (this) {
System.out.println("B begin ....");
System.out.println("B end ......");
}
}
public static void main(String[] args) {
final Task os = new Task();
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
os.doServiceA();
}
},"A");
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
os.doServiceB();
}
},"B");
t1.start();
t2.start();
}
}
执行结果:A begin ....
A end ......
B begin ....
B end ......
synchronized方法一样,synchronized(this){}同步块也是锁定当前对象的,doserviceB方法加上synchronized修饰,,将同步语句块去除,执行结果也是一样的.
A begin ....
A end ......
B begin ....
B end ......
2.同步块中将任对象作为对象监视器
同步块中使用非this对象,这样就避免和synchronized方法抢锁,效率高
public class SService {
private String username;
private String password;
private String anything = new String();
public void setUserNamePassword(String username,String password){
synchronized (anything) {
System.out.println(Thread.currentThread().getName()+"进入同步方法块。。。。。。。");
this.username = username;
this.password = password;
System.out.println(Thread.currentThread().getName()+"离开同步方法块。。。。。。。。");
}
}
}
注意非this同步块和synchronized方法持有的对象锁不同,两个线程调用不同的方法,由于锁不同,执行时异步的
同步语句块中的脏读问题
非同步方法中使用synchronized同步方法块,并不能保证线程调用方法的顺序,虽然同步块中是有序的,也会出现脏读问题。
class MyOneList{
private List<String> list = new ArrayList<>();
synchronized public void add(String data){list.add(data);}
synchronized public int getSize(){return list.size();}
}
public class MyService {
public MyOneList addServiceMethod(MyOneList list,String data){
if(list.getSize()<1){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
list.add(data);
}
return list;
}
public static void main(String[] args) throws InterruptedException {
final MyOneList list = new MyOneList();
final MyService ms = new MyService();
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
ms.addServiceMethod(list, "A");
}
});
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
ms.addServiceMethod(list, "B");
}
});
t1.start();t2.start();
Thread.sleep(3000);
System.out.println("list size = "+list.getSize());
}
}
执行结果 : list size =2 ;这就出现脏读,解决,addServiceMethod 访问 list加上同步块关于synchronize的结论: