Java多线程学习(二)synchronized关键字(1)

Java面试通关手册(Java学习指南,欢迎Star,会一直完善下去,欢迎建议和指导):https://github.com/Snailclimb/Java_Guide

Java多线程学习(二)将分为两篇文章介绍synchronized同步方法另一篇介绍synchronized同步语句块

系列文章传送门:

(1) synchronized同步方法

本节思维导图:

本节思维导图

一 简介

Java并发编程这个领域中synchronized关键字一直都是元老级的角色,很久之前很多人都会称它为“重量级锁”。但是,在JavaSE 1.6之后进行了主要包括为了减少获得锁和释放锁带来的性能消耗而引入的偏向锁和轻量级锁以及其它各种优化之后变得在某些情况下并不是那么重了。

这一篇文章不会介绍synchronized关键字的实现原理,更多的是synchronized关键字的使用。如果想要了解的可以看看方腾飞的《Java并发编程的艺术》。

二 变量安全性

“非线程安全”问题存在于“实例变量”中,如果是方法内部的私有变量,则不存在“非线程安全”问题,所得结果也就是“线程安全”的了。

如果两个线程同时操作对象中的实例变量,则会出现“非线程安全”,解决办法就是在方法前加上synchronized关键字即可。前面一篇文章我们已经讲过,而且贴过相应代码,所以这里就不再贴代码了。

三 多个对象多个锁

先看例子:

HasSelfPrivateNum.java

public class HasSelfPrivateNum {

	private int num = 0;

	synchronized public void addI(String username) {
		try {
			if (username.equals("a")) {
				num = 100;
				System.out.println("a set over!");
				//如果去掉hread.sleep(2000),那么运行结果就会显示为同步的效果
				Thread.sleep(2000);
			} else {
				num = 200;
				System.out.println("b set over!");
			}
			System.out.println(username + " num=" + num);
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}

}

ThreadA.java

public class ThreadA extends Thread {

	private HasSelfPrivateNum numRef;

	public ThreadA(HasSelfPrivateNum numRef) {
		super();
		this.numRef = numRef;
	}

	@Override
	public void run() {
		super.run();
		numRef.addI("a");
	}

}

ThreadB.java

public class ThreadB extends Thread {

	private HasSelfPrivateNum numRef;

	public ThreadB(HasSelfPrivateNum numRef) {
		super();
		this.numRef = numRef;
	}

	@Override
	public void run() {
		super.run();
		numRef.addI("b");
	}

}

Run.java

public class Run {

	public static void main(String[] args) {

		HasSelfPrivateNum numRef1 = new HasSelfPrivateNum();
		HasSelfPrivateNum numRef2 = new HasSelfPrivateNum();

		ThreadA athread = new ThreadA(numRef1);
		athread.start();

		ThreadB bthread = new ThreadB(numRef2);
		bthread.start();

	}

}

运行结果:

a num=100停顿一会才执行

多个对象多个锁

上面实例中两个线程ThreadA和ThreadB分别访问同一个类的不同实例的相同名称的同步方法,但是效果确实异步执行。

为什么会这样呢?

这是因为synchronized取得的锁都是对象锁,而不是把一段代码或方法当做锁。所以在上面的实例中,哪个线程先执行带synchronized关键字的方法,则哪个线程就持有该方法所属对象的锁Lock,那么其他线程只能呈等待状态,前提是多个线程访问的是同一个对象。本例中很显然是两个对象。

在本例中创建了两个HasSelfPrivateNum类对象,所以就产生了两个锁。当ThreadA的引用执行到addI方法中的runThread.sleep(2000)语句时,ThreadB就会“乘机执行”。所以才会导致执行结果如上图所示(备注:由于runThread.sleep(2000),“a num=100”停顿了两秒才输出)

四 synchronized方法与锁对象

通过上面我们知道synchronized取得的锁都是对象锁,而不是把一段代码或方法当做锁。如果多个线程访问的是同一个对象,哪个线程先执行带synchronized关键字的方法,则哪个线程就持有该方法,那么其他线程只能呈等待状态。如果多个线程访问的是多个对象则不一定,因为多个对象会产生多个锁。

那么我们思考一下当多个线程访问的是同一个对象中的非synchronized类型方法会是什么效果?

答案是:会异步调用非synchronized类型方法,解决办法也很简单在非synchronized类型方法前加上synchronized关键字即可。

五 脏读

发生脏读的情况实在读取实例变量时,此值已经被其他线程更改过。

PublicVar.java

public class PublicVar {

	public String username = "A";
	public String password = "AA";

	synchronized public void setValue(String username, String password) {
		try {
			this.username = username;
			Thread.sleep(5000);
			this.password = password;

			System.out.println("setValue method thread name="
					+ Thread.currentThread().getName() + " username="
					+ username + " password=" + password);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}
     //该方法前加上synchronized关键字就同步了
	 public void getValue() {
		System.out.println("getValue method thread name="
				+ Thread.currentThread().getName() + " username=" + username
				+ " password=" + password);
	}
}

ThreadA.java

public class ThreadA extends Thread {

	private PublicVar publicVar;

	public ThreadA(PublicVar publicVar) {
		super();
		this.publicVar = publicVar;
	}

	@Override
	public void run() {
		super.run();
		publicVar.setValue("B", "BB");
	}
}

Test.java

public class Test {

	public static void main(String[] args) {
		try {
			PublicVar publicVarRef = new PublicVar();
			ThreadA thread = new ThreadA(publicVarRef);
			thread.start();

			Thread.sleep(200);//打印结果受此值大小影响

			publicVarRef.getValue();
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}

	}
}

运行结果:

运行结果

解决办法:getValue()方法前加上synchronized关键字即可。

加上synchronized关键字后的运行结果:

运行结果

六 synchronized锁重入

“可重入锁” 概念是:自己可以再次获取自己的内部锁。比如一个线程获得了某个对象的锁,此时这个对象锁还没有释放,当其再次想要获取这个对象的锁的时候还是可以获取的,如果不可锁重入的话,就会造成死锁。

Service.java

public class Service {

	synchronized public void service1() {
		System.out.println("service1");
		service2();
	}

	synchronized public void service2() {
		System.out.println("service2");
		service3();
	}

	synchronized public void service3() {
		System.out.println("service3");
	}

}

MyThread.java

public class MyThread extends Thread {
	@Override
	public void run() {
		Service service = new Service();
		service.service1();
	}

}

Run.java

public class Run {
	public static void main(String[] args) {
		MyThread t = new MyThread();
		t.start();
	}
}

运行结果:

运行结果

另外<font color="red">可重入锁也支持在父子类继承的环境中</font>

Main.java:

public class Main {

	public int i = 10;

	synchronized public void operateIMainMethod() {
		try {
			i--;
			System.out.println("main print i=" + i);
			Thread.sleep(100);
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
}

Sub.java:

public class Sub extends Main {

	synchronized public void operateISubMethod() {
		try {
			while (i > 0) {
				i--;
				System.out.println("sub print i=" + i);
				Thread.sleep(100);
				this.operateIMainMethod();
			}
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}
}

MyThread.java:

public class MyThread extends Thread {
	@Override
	public void run() {
		Sub sub = new Sub();
		sub.operateISubMethod();
	}
}

Run.java:

public class Run {
	public static void main(String[] args) {
		MyThread t = new MyThread();
		t.start();
	}
}

运行结果:

运行结果

说明当存在父子类继承关系时,子类是完全可以通过“可重入锁”调用父类的同步方法。

另外出现异常时,其锁持有的锁会自动释放。

七 同步不具有继承性

如果父类有一个带synchronized关键字的方法,子类继承并重写了这个方法。

但是同步不能继承,所以还是需要在子类方法中添加synchronized关键字。

参考:

《Java多线程编程核心技术》

欢迎关注我的微信公众号:"Java面试通关手册"(一个有温度的微信公众号,无广告,单纯技术分享,期待与你共同进步~~~坚持原创,分享美文,分享各种Java学习资源。)

我的公众号

原创声明,本文系作者授权云+社区发表,未经许可,不得转载。

如有侵权,请联系 yunjia_community@tencent.com 删除。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏me的随笔

Python知识梳理

我们可以使用type()函数类获取对象的类型,Python3中内置数据类型包括:None,int,float,complex,str,list,dict,tup...

1512
来自专栏Coding迪斯尼

自制Monkey语言编译器:解释执行哈希表对象

我们在上节完成了对哈希表对象的解析,这一节我们给编译器添加执行哈希表对象的功能,完成本节代码后,编译器能执行以下代码:

882
来自专栏JackieZheng

并发和多线程-八面玲珑的synchronized

上篇《并发和多线程-说说面试常考平时少用的volatile》主要介绍的是volatile的可见性、原子性等特性,同时也通过一些实例简单与synchronized...

1133
来自专栏Jerry的SAP技术分享

使用JavaScript ES6的新特性计算Fibonacci(非波拉契数列)

Java程序员面试系列-什么是Java Marker Interface(标记接口)

1053
来自专栏我是攻城师

Apache Pig学习笔记之内置函数(三)

4414
来自专栏Java Web

《编写高质量代码》学习笔记(2)

写着写着发现简书提醒我文章接近字数极限,建议我换一篇写了。 ---- 建议52:推荐使用String直接量赋值 一般对象都是通过new关键字生成的,但是Str...

3684
来自专栏IMWeb前端团队

走进Sass殿堂

最近在学习sass,从sass新手的角度做一个简单的总结,总结的不对的地方期望各位大大们能多多指点,本文是针对sass3.4做的一个总结~ 一、变量篇 1.1 ...

20110
来自专栏分布式系统和大数据处理

ES6新特性速览

ES6引入了很多新的语言特性和能力,这篇文章仅快速地做一个概览。包括let、解构、箭头函数、模块化、Spread运算符 等。ES6还有很多更深入的内容,有时间再...

1287
来自专栏WindCoder

JSON中关于对双向关联的支持

本文原文:Bidirectional Relationship Support in JSON

1852
来自专栏飞雪无情的博客

从Java到Golang快速入门

Golang从09年发布,中间经历了多个版本的演进,已经渐渐趋于成熟,并且出现了很多优秀的开源项目,比如我们熟知的docker,etcd,kubernetes等...

1133

扫码关注云+社区

领取腾讯云代金券