博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Java 多线程/并发 Synchronized学习笔记
阅读量:3904 次
发布时间:2019-05-23

本文共 9547 字,大约阅读时间需要 31 分钟。

synchronized关键字可以添加在方法的声明上,也可以添加在代码块中

添加在方法上时分两种情况,当为静态方法时,表示的是对该类的.class对象上锁
当不为静态方法时,表示的是对该类的对象上锁。
添加在代码块时,需要指定上锁的对象。

public class Synchonizedd {
static Long start,end; static {
start = System.currentTimeMillis(); } //锁定this对象 public synchronized void m1() {
try {
TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) {
e.printStackTrace(); } end =System.currentTimeMillis(); System.out.println(end-start+" m1"); } //锁定Synchonizedd.class 对象 public static synchronized void m2(){
try {
TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) {
e.printStackTrace(); } end =System.currentTimeMillis(); System.out.println(end-start+" m2"); } public void m3() {
//锁定this对象 synchronized (this) {
try {
TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) {
e.printStackTrace(); } end =System.currentTimeMillis(); System.out.println(end-start+" m3"); } } public static void main(String[] args) {
Synchonizedd synchonizedd = new Synchonizedd(); new Thread(()->synchonizedd.m1()).start(); new Thread(()->Synchonizedd.m2()).start(); new Thread(()->synchonizedd.m3()).start(); }}

运行结果:

在这里插入图片描述
可以看到,因为m1和m2方法锁定的不是同一对象,所以调用m1方法的线程和调用m2方法的线程能够并行执行。
而m1和m3中的synchronized代码块锁定的是同一对象,调用两个方法的线程不能并行运行,等到其中一个释放该对象锁之后另一个线程才会运行。
synchnized 操作是原子操作,不可分。
一个synchnized 方法运行中,一个非同步方法是可以运行的

public class T2 {
static long start,end; static {
start = System.currentTimeMillis(); } public synchronized void m1() {
System.out.println(Thread.currentThread().getName()+" m1"); try {
Thread.sleep(2000); } catch (InterruptedException e) {
e.printStackTrace(); } end=System.currentTimeMillis(); System.out.println("当前时间为:"+(end-start) +" m1 to end"); } public void m2() {
try {
Thread.sleep(1000); } catch (InterruptedException e) {
e.printStackTrace(); } end=System.currentTimeMillis(); System.out.println("当前时间为:"+(end-start)+" "+Thread.currentThread().getName()+" m2"); } public static void main(String[] args) {
T2 t2 = new T2(); new Thread(()->t2.m1(),"Thread1").start(); new Thread(()->t2.m2(),"Thread2").start(); }}

运行结果:

在这里插入图片描述
上述代码中,我们开启了两个线程,一个线程执行m1方法,另一个执行m2方法,m1方法执行是需要获得锁的,m2方法不需要获得锁的,所以当m1执行的过程中,m2方法是可以同时执行的。
一个同步方法可以调用另一个同步方法,一个线程已经拥有某个对象的锁,再次申请时仍然会得到该对象的锁,也就是说synchronized获得的锁是可重入的。

public class T3 {
public synchronized void m1() {
try {
Thread.sleep(1000); } catch (InterruptedException e) {
e.printStackTrace(); } //执行同一个需要获得锁的方法 m2(); System.out.println(Thread.currentThread().getName()+" m1"); } public synchronized void m2() {
try {
Thread.sleep(1000); } catch (InterruptedException e) {
e.printStackTrace(); } System.out.println(Thread.currentThread().getName()+" m2"); } public static void main(String[] args) {
T3 t3 = new T3(); new Thread(()->t3.m1(),"th1").start(); }}

运行结果:

在这里插入图片描述
上述代码中,我们开启了一个线程,这个线程去执行m1方法,m1方法是需要获得锁的,m1方法中又需要调用m2方法,而m2方法也需要获得锁,这就形成了需要获得T3类对象的两次锁,而实验结果表明m2方法是可以执行的,这说明synchronized获得的锁是可重入的。
子类调用父类的同步方法,锁定的是同一对象

public class T4 {
synchronized void m() {
System.out.println("m start"); try {
Thread.sleep(1000); } catch (InterruptedException e) {
e.printStackTrace(); } System.out.println("锁定的对象是 "+this); System.out.println("m end"); } public static void main(String[] args) {
TT4 tt4 = new TT4(); new Thread(()->tt4.m(),"Thread").start(); }}class TT4 extends T4{
public synchronized void m() {
System.out.println("child m start"); super.m(); System.out.println("锁定的对象是 "+this); System.out.println("child m end"); }}

运行结果:

在这里插入图片描述
通过运行结果发现,锁定的确实是同一对象。
程序在执行过程中,如果发现异常,默认情况下锁是会被释放。
所以,在并发处理的过程中,有异常要多加小心,不然可能会发生不一致的情况。
比如,在一个web app处理过程中,多个servlet线程共同访问一个资源,这时如果异常处理不合适,在一个线程中抛出异常,其他线程就会进入同步代码区,有可能访问到异常产生时的数据。
因此要非常小心的处理同步业务逻辑中的异常。

public class T5 {
int count = 0; public synchronized void m() {
while (true) {
count++; try {
Thread.sleep(1000); } catch (InterruptedException e) {
e.printStackTrace(); } if (count == 5) {
//加上try catch块则不会释放锁 /*try { int i = 1 / 0; }catch (Exception e) { e.printStackTrace(); }*/ //抛出异常,并释放锁 int i = 1 / 0; } System.out.println(Thread.currentThread().getName()+" count="+count); } } public static void main(String[] args) {
T5 t5 = new T5(); new Thread(()->t5.m(),"t1").start(); try {
Thread.sleep(3000); } catch (InterruptedException e) {
e.printStackTrace(); } new Thread(()->t5.m(),"t2").start(); }}

当不对异常进行处理时的运行结果(这里截取出现异常后的运行结果):

在这里插入图片描述
当对异常进行trycatch处理时的运行结果:
在这里插入图片描述
上述代码中,当抛出异常时,正在执行的方法会释放锁,而用trycatch处理之后,正在执行的方法不会释放锁。
synchronize 优化
同步代码块中的语句越少越好

public class T10 {
int count = 0; synchronized void m1() {
try {
Thread.sleep(2000); for (int i=0;i<1000000;i++) count++; Thread.sleep(2000); } catch (InterruptedException e) {
e.printStackTrace(); } } void m2() {
try {
Thread.sleep(2000); //业务逻辑中只有这句话需要sync,这时不应该给整个方法上锁 //采用细粒度的锁,可以使线程争用时间变短,从而提高效率 synchronized (this) {
for (int i=0;i<1000000;i++) count++; } Thread.sleep(2000); } catch (InterruptedException e) {
e.printStackTrace(); } }}

锁定某个对象o,如果o的属性发生改变,不影响锁的使用。

但是如果o引用另外一个对象,则锁定的对象改变。
应避免将锁定对象的引用变成另外的对象

public class T11 {
Object o = new Object(); void m1() {
// 锁定o引用的对象 synchronized (o) {
while (true) {
try {
Thread.sleep(1000); } catch (InterruptedException e) {
e.printStackTrace(); } System.out.println("运行的线程:"+Thread.currentThread().getName() ); } } } public static void main(String[] args) {
T11 t11 = new T11(); new Thread(()->t11.m1(),"t1").start(); try {
Thread.sleep(2000); } catch (InterruptedException e) {
e.printStackTrace(); } Thread t2= new Thread(()->t11.m1(),"t2"); // 改变引用对象 t11.o = new Object(); t2.start(); }}

运行结果:

在这里插入图片描述
上述代码中,先开启线程t1,然后t1执行m1方法,之后改变o的引用,然后开启一个线程t2,可以看到t1和t2同时运行,这是因为t2和t1获得的锁的对象是不一样的。
就像下面图片一样:
在这里插入图片描述
不要以字符串常量作为锁定对象。
在下面的例子中,m1和m2其实锁定的是同一个对象。
这种情况还会发生比较隐退的现象,比如你用到一个类库,在该类库中代码锁定了字符串"Hello"。
但是你读不到源码,所以在自己的代码中也锁定了"Hello",这时候就有可能发生非常诡异的死锁阻塞。
因为你的程序和你用到的类库不经意间使用了同一把锁

public class T12 {
String s1 = "Hello"; String s2 = "Hello"; void m1() {
synchronized (s1) {
while(true) {
try {
Thread.sleep(1000); } catch (InterruptedException e) {
e.printStackTrace(); } System.out.println("运行的线程:"+Thread.currentThread().getName()); } } } void m2() {
synchronized (s2) {
while(true) {
try {
Thread.sleep(1000); } catch (InterruptedException e) {
e.printStackTrace(); } System.out.println("运行的线程:"+Thread.currentThread().getName()); } } } public static void main(String[] args) {
T12 t12 = new T12(); new Thread(()->t12.m1(),"t1").start(); new Thread(()->t12.m2(),"t2").start(); }}

运行结果:

在这里插入图片描述
使用Synchronized实现死锁
死锁形成的原理就是线程A需要线程B所占用的锁来执行程序,而线程B同时也需要线程A锁占用的锁来执行程序,这就导致了两个线程无休止的等待,等待对方释放占用的锁。

public class DeadLock {
public static void main(String[] args) {
D1 d1 = new D1(); D2 d2 = new D2(); new Thread(()->d1.m1(d2),"Thread1").start(); new Thread(()->d2.m1(d1),"Thread2").start(); }}class D1{
public synchronized void m1(D2 d2) {
try {
Thread.sleep(3000); } catch (InterruptedException e) {
e.printStackTrace(); } System.out.println(Thread.currentThread().getName()); d2.m2(); } public synchronized void m2() {
System.out.println("D1 m2"); }}class D2{
public synchronized void m1(D1 d1) {
try {
Thread.sleep(3000); } catch (InterruptedException e) {
e.printStackTrace(); } System.out.println(Thread.currentThread().getName()); d1.m2(); } public synchronized void m2() {
System.out.println("D2 m2"); }}

运行结果:

在这里插入图片描述
可见线程1和线程2都在等待对方释放锁,而自己需要等待对方释放锁之后才释放自己的锁,这就导致了死锁的产生。

转载地址:http://rdaen.baihongyu.com/

你可能感兴趣的文章
前期准备:了解下Open JDK与Oracle JDK的区别
查看>>
android:layout_gravity 和 android:gravity 的区别
查看>>
工欲善其事,必先利其器之—使用ImageMagick处理图片
查看>>
工欲善其事,必先利其器之—使用PlantUML画UML图
查看>>
Android开发填坑之setUseWideViewPort
查看>>
前期准备:搭建代码阅读环境(Mac上搭建OpenGrok查看JDK源码)
查看>>
有关使用xsl输出csv格式文档的实践小结
查看>>
在Ubuntu 12.04 为 Eclipse 添加快速启动项
查看>>
GCC强大背后
查看>>
Android x86模拟器Intel Atom x86 System Image配置与使用方法
查看>>
shell脚本兼容linux/unix与windows/cygwin的基础(注意处理好CR, LF, CR/LF 回车 换行的问题)
查看>>
【分享】手把手教你使用U盘安装Ubuntu系统
查看>>
Ubuntu下adb无法识别android设备的解决方法
查看>>
使用U盘安装Ubuntu系统的实践小结
查看>>
编译cscope-15.8a遇到的问题与解决方案
查看>>
ubuntu下海信Hisense E920 usb连接不上的处理与adb的连接
查看>>
findbugs的ant脚本实践
查看>>
Ubuntu 12.04 安装 Subversion 1.7
查看>>
scp port 22: Connection refused
查看>>
ubuntu12.04命令行下安装RabbitVCS
查看>>