java多线程总结-同步之synchronized关键字

1.为何要使用synchronized?

在并发编程中存在线程安全问题,主要缘由有:
1.存在共享数据
2.多线程共同操做共享数据。关键字synchronized能够保证在同一时刻,只有一个线程能够执行某个方法或某个代码块,同时synchronized能够保证一个线程的变化可见(可见性)java

2.synchronized锁什么,加锁的目的是什么?

synchronized锁的是对象,锁的的对象多是this、临界资源对象、class类对象
加锁的目的是保证操做的原子性编程

3.代码示例

3.1锁this和临界资源对象

本例中:安全

  • testSync1是synchronized代码块,锁的是object,临界资源对象
  • testSync2和testSync3都是锁的是当前对象this
/**
 * synchronized关键字
 * 锁对象。synchronized(this)和synchronized方法都是锁当前对象。
 */
package com.bernardlowe.concurrent.t01;

import java.util.concurrent.TimeUnit;

public class Test_01 {
	private int count = 0;
	private Object object = new Object();

	// 1.synchronized代码块,锁的是object,临界资源对象
	public void testSync1(){
		synchronized(object){
			System.out.println(Thread.currentThread().getName() 
					+ " count = " + count++);
		}
	}

	// 2.synchronized代码块,锁的是当前对象this
	public void testSync2(){
		synchronized(this){
			System.out.println(Thread.currentThread().getName() 
					+ " count = " + count++);
		}
	}

	// 2.synchronized代码,锁的也是this
 	public synchronized void testSync3(){
		System.out.println(Thread.currentThread().getName() 
				+ " count = " + count++);
		try {
			TimeUnit.SECONDS.sleep(3);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}
	
	public static void main(String[] args) {
		final Test_01 t = new Test_01();
		new Thread(new Runnable() {
			@Override
			public void run() {
				t.testSync3();
			}
		}).start();
		new Thread(new Runnable() {
			@Override
			public void run() {
				t.testSync3();
			}
		}).start();
	}
	
}

复制代码

3.2锁class类对象

/**
 * synchronized关键字
 * 同步方法 - static
 * 静态同步方法,锁的是当前类型的类对象。在本代码中就是Test_02.class
 */
package com.bernardlowe.concurrent.t01;

import java.util.concurrent.TimeUnit;

public class Test_02 {
	private static int staticCount = 0;
	
	public static synchronized void testSync4(){
		System.out.println(Thread.currentThread().getName() 
				+ " staticCount = " + staticCount++);
		try {
			TimeUnit.SECONDS.sleep(3);
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
	
	public static void testSync5(){
		synchronized(Test_02.class){
			System.out.println(Thread.currentThread().getName() 
					+ " staticCount = " + staticCount++);
		}
	}
	
}

复制代码

3.3 何时锁临界资源,何时锁当前对象?

若是在加锁的时候须要对当前对象的访问限定,建议锁临界资源(即锁一个临界资源),若是对当前锁级别比较高的话,就锁当前对象。建议都以同步代码块的方式进行开发,这样能够避免锁的范围过高bash

同步方法和非同步方法 提问:同步方法是否影响其余线程调用非同步方法,或调用其余锁资源的同步方法?多线程

代码示例 m1是非同步方法,m2是同步方法,m3同步代码块,锁的临界资源,这段代码的目的是为了证实在调用同步方法m1时,m2,m3是否可以执行并发

/**
 * synchronized关键字
 * 同步方法 - 同步方法和非同步方法的调用
 * 同步方法只影响锁定同一个锁对象的同步方法。不影响其余线程调用非同步方法,或调用其余锁资源的同步方法。
 */
package com.bernardlowe.concurrent.t01;

public class Test_04 {
	Object o = new Object();
	public synchronized void m1(){ // 重量级的访问操做。
		System.out.println("public synchronized void m1() start");
		try {
			Thread.sleep(3000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		System.out.println("public synchronized void m1() end");
	}
	
	public void m3(){
		synchronized(o){
			System.out.println("public void m3() start");
			try {
				Thread.sleep(1500);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			System.out.println("public void m3() end");
		}
	}
	
	public void m2(){
		System.out.println("public void m2() start");
		try {
			Thread.sleep(1500);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		System.out.println("public void m2() end");
	}
	
	public static class MyThread01 implements Runnable{
		public MyThread01(int i, Test_04 t){
			this.i = i;
			this.t = t;
		}
		int i ;
		Test_04 t;
		public void run(){
			if(i == 0){
				t.m1();
			}else if (i > 0){
				t.m2();
			}else {
				t.m3();
			}
		}
	}
	
	public static void main(String[] args) {
		Test_04 t = new Test_04();
		new Thread(new MyThread01(0, t)).start();
		new Thread(new MyThread01(1, t)).start();
		new Thread(new MyThread01(-1, t)).start();
	}
	
}

复制代码

执行结果 ide

能够看出,在执行同步方法m1时,m2,m3不受影响,依然能够执行
结论:同步方法只影响锁定同一个锁对象的同步方法。不影响其余线程调用非同步方法,或调用其余锁资源的同步方法。
但有一点须要注意,尽可能在商业开发中避免同步方法。使用同步代码块。 细粒度解决同步问题。

3.3 重入锁

这里重入锁分为两类:this

  • 1.在同步方法里面调用其余同步方法
  • 2.子类同步方法覆盖父类同步方法

下面来看第一种:在同步方法里面调用其余同步方法
思考:调用m1()方法,m2()方法是否会执行?spa

/**
 *synchronized关键字
 *同步方法 - 调用其余同步方法
 */
package com.bernardlowe.concurrent.t01;

import java.util.concurrent.TimeUnit;

public class Test_06 {
	
	synchronized void m1(){ // 锁this
		System.out.println("m1 start");
		try {
			TimeUnit.SECONDS.sleep(2);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		m2();
		System.out.println("m1 end");
	}
	synchronized void m2(){ // 锁this
		System.out.println("m2 start");
		try {
			TimeUnit.SECONDS.sleep(1);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		System.out.println("m2 end");
	}
	
	public static void main(String[] args) {
		
		new Test_06().m1();
		
	}
	
}  
复制代码

结果:在m1()方法调用时,m2()方法依然可执行。屡次调用同步代码,锁定同一个锁对象,是可重入的

第二种状况:子类同步方法覆盖父类同步方法
思考:子类同步方法m()中,调用父类同步方法m(),是否可重入?线程

/**
 * synchronized关键字
 * 同步方法 - 继承
 * 子类同步方法覆盖父类同步方法。能够指定调用父类的同步方法。
 * 至关于锁的重入。
 */
package com.bernardlowe.concurrent.t01;

import java.util.concurrent.TimeUnit;

public class Test_07 {
	
	synchronized void m(){
		System.out.println("Super Class m start");
		try {
			TimeUnit.SECONDS.sleep(1);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		System.out.println("Super Class m end");
	}
	
	public static void main(String[] args) {
		new Sub_Test_07().m();
	}
	
}

class Sub_Test_07 extends Test_07{
	synchronized void m(){
		System.out.println("Sub Class m start");
		super.m();
		System.out.println("Sub Class m end");
	}
}

复制代码

结果:子类同步方法m()中,调用了父类同步方法m(),能够重入

3.4 锁与异常

思考:当同步方法或同步代码块中发生异常,是否会影响其余线程的执行?
下面来看一段代码

/**
 * synchronized关键字
 * 同步方法 - 锁与异常
 */
package com.bernardlowe.concurrent.t01;

import java.util.concurrent.TimeUnit;

public class Test_08 {
	int i = 0;
	synchronized void m(){
		System.out.println(Thread.currentThread().getName() + " - start");
		while(true){
			i++;
			System.out.println(Thread.currentThread().getName() + " - " + i);
			try {
				TimeUnit.SECONDS.sleep(1);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			// 当i==5时会抛出异常
			if(i == 5){
				i = 1/0;
			}
		}
	}
	
	public static void main(String[] args) {
		final Test_08 t = new Test_08();
		new Thread(new Runnable() {
			@Override
			public void run() {
				t.m();
			}
		}, "t1").start();
		
		new Thread(new Runnable() {
			@Override
			public void run() {
				t.m();
			}
		}, "t2").start();
	}
	
}

复制代码

这段代码中先运行了两个线程t一、t2,当其中一个线程发生异常时,另一个线程是否能继续执行

结果:当同步方法中发生异常的时候,自动释放锁资源。不会影响其余线程的执行。

思考: 同步业务逻辑中,若是发生异常如何处理?
好比上面会发生异常的代码中,能够这样

if(i == 5){
				try {
					i = 1/0;
				} catch (Exception e) {
					i = 0;
				}
			}
复制代码

3.5 锁对象变动问题

代码示例:8 思考:当一个线程执行同步方法时,另外一个线程修改了锁对象,是否还能执行同步代码块

/**
 * synchronized关键字
 * 锁对象变动问题
 */
package com.bernardlowe.concurrent.t01;

import java.util.concurrent.TimeUnit;

public class Test_13 {
	Object o = new Object();
	
	void m(){
		System.out.println(Thread.currentThread().getName() + " start");
		synchronized (o) {
			while(true){
				try {
					TimeUnit.SECONDS.sleep(1);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
				System.out.println(Thread.currentThread().getName() + " - " + o);
			}
		}
	}
	
	public static void main(String[] args) {
		final Test_13 t = new Test_13();
		new Thread(new Runnable() {
			@Override
			public void run() {
				t.m();
			}
		}, "thread1").start();
		try {
			TimeUnit.SECONDS.sleep(3);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		Thread thread2 = new Thread(new Runnable() {
			@Override
			public void run() {
				t.m();
			}
		}, "thread2");
		t.o = new Object();
		thread2.start();
	}
	
}

复制代码

结论:能够看出,其余线程依然能够执行同步方法
由于同步代码一旦加锁后,那么会有一个临时的锁引用执行锁对象,和真实的引用无直接关联。在锁未释放以前,修改锁对象引用,不会影响同步代码的执行。

注意:不要使用静态常量做为锁对象
以下代码,由于String常量池的问题,s1,s1是同一个对象,因此m1,m2方法锁的是也同一个对象,m1同步方法被执行后,m2方法不会被执行

/**
 * synchronized关键字
 * 常量问题
 */
package com.bernardlowe.concurrent.t01;

public class Test_14 {
	String s1 = "hello";
	String s2 = "hello";
	// String s2 = new String("hello"); // new关键字,必定是在堆中建立一个新的对象。
	void m1(){
		synchronized (s1) {
			System.out.println("m1()");
			while(true){
				
			}
		}
	}
	
	void m2(){
		synchronized (s2) {
			System.out.println("m2()");
			while(true){
				
			}
		}
	}
	
	public static void main(String[] args) {
		final Test_14 t = new Test_14();
		new Thread(new Runnable() {
			@Override
			public void run() {
				t.m1();
			}
		}).start();
		
		new Thread(new Runnable() {
			@Override
			public void run() {
				t.m2();
			}
		}).start();
	}
	
}
复制代码
相关文章
相关标签/搜索