Java 多线程(一)synchronized 的用法详解

synchronized 定义

在并发编程中存在线程安全问题,主要原因有:1.存在共享数据 2.多线程共同操作共享数据。关键字synchronized可以保证在同一时刻,只有一个线程可以执行某个方法或某个代码块,同时synchronized可以保证一个线程的变化可见(可见性),即可以代替volatile。
synchronzied 是 悲观锁

synchronized 关键字 对某个对象加锁
	public void m() {
		synchronized(o) {
			count--;
			System.out.println(Thread.currentThread().getName() + "count = " + count);
		}
	}
synchronized 锁定的是对象
public class T {

	private int count = 0;
	
	public void m() {
		/**
		 * synchronized 锁定的是对象	
		 * 如果三个人去上厕所,但是只有一个厕所坑。 厕所门口有一把锁。 第一个人进去之后,其他两个人需要等待
		 * 第一个人把锁打开才能够进去。
		 */
		synchronized (this) { // 任何线程要执行下面的代码,必须要拿到this的锁
			count++;
			System.out.println(Thread.currentThread().getName() + "count:" + count);
		}
	}
}
synchronized 关键字对某个对象加锁。
	private int count = 0;
	
	public synchronized void m() { // 等同于在方法代码执行开始时候,就执行synchronized(this)  这里直接锁定的就是this对象
		count ++;
		System.out.println(Thread.currentThread().getName()+ "count:" + count);
	}
synchronized 关键字在静态方法上使用
public class T {

	private static int i = 10;
	
	public synchronized static void m() {  // 这里等同于synchronized(T.class)
		i--;
		System.out.println(Thread.currentThread().getName() + "count-:" + i);
	}
	
	public static void n() {
		synchronized(T.class) { // 考虑一下 写this 是否可以  : 不可以   因为静态方法,是不需要创建对象的就可以使用的。!
			i--;
			System.out.println(Thread.currentThread().getName() + "count-:" + i);
		}
	}
}
synchronized 是原子操作 不可以再分的
public class T implements Runnable{

	private int count = 10;
	
	// synchronized 是原子操作 不可以再分的
	@Override
	public /*synchronized*/ void run() {
		count --;
		System.out.println(Thread.currentThread().getName() + "count --: " + count);
	}
	
	public static void main(String[] args) {
		T t = new T();
		for (int i = 0; i < 10; i++) {
			new Thread(t).start();
		}
	}
}

非同步方法和同步方法是否可以一起调用
public class T {

	public synchronized void m1(){
		System.out.println(Thread.currentThread().getName() + "m1.start");
		try {
			Thread.sleep(10000);
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		System.out.println(Thread.currentThread().getName() + "m1.end");
	}
	
	public void m2() {
		System.out.println(Thread.currentThread().getName() + "m2.start");
		try {
			Thread.sleep(1000);
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		System.out.println(Thread.currentThread().getName() + "m12.end");
	}
	
	public static void main(String[] args) {
		// 结论是可以一起调用
		T t = new T();
		new Thread(()->t.m1()).start();
		new Thread(()->t.m2()).start();
	}	
}
解决脏读问题
/**
 * 对业务写方法加锁
 * 对业务读方法不加锁
 * 容易产生脏读问题
 * @author gssznb
 *
 */
public class Account {

	String name;
	double balance;
	
	/**
	 *  设置姓名和金钱余额的时候。 给金钱赋值在线程沉睡十秒时候赋值。
	 *  使用多线程在沉睡过程中模拟人为操作对这个金钱余额的读取。
	 *  在这个时候就会产生脏读现象。
	 * @param name
	 * @param balance
	 */
	public synchronized void set(String name,double balance) {
		this.name = name;
		try {
			Thread.sleep(10000);
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		this.balance = balance;
	}
	
	public double get(String name) {
		return this.balance;
	}
	
	public static void main(String[] args) {
		Account a = new Account();
		new Thread(()->a.set("gss", 100.0)).start();
		
		try {
			TimeUnit.SECONDS.sleep(1);
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		System.out.println(a.get("gss"));
		try {
			TimeUnit.SECONDS.sleep(11);
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		System.out.println(a.get("gss"));
	}
}

synchronized是可以重入的

一个同步方法可以调用另外一个同步方法,一个线程已经拥有其对象的锁。再次申请的时候仍然会得到该对象的锁。 也就是说synchronized是可以重入的

public class T {

	synchronized void m1() {
		System.out.println("m1.start");
		try {
			TimeUnit.SECONDS.sleep(1);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		System.out.println("m1.end");
		m2();
	}
	synchronized void m2() {
		System.out.println("m2.start");
		try {
			TimeUnit.SECONDS.sleep(2);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		System.out.println("m2.end");
	}
	
	public static void main(String[] args) {
		T t = new T();
		new Thread(()->t.m1()).start();
	}
}

一个同步方法可以调用另一个同步方法,一个线程已经拥有某个对象的锁。再次申请的时候仍然会得到该对象的锁,也就是说synchronized获得的锁是可重入的。这里是继承中可能发生的事情。子类调用父类的同步方法。 子类的重入锁调用父类的重入锁

public class T {

	synchronized void m() {
		System.out.println("m.start");
		try {
			TimeUnit.SECONDS.sleep(1);
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		System.out.println("m.end");
	}
	public static void main(String[] args) {
		TT tt = new TT();
		tt.m2();
	}
}
class TT extends T{
	synchronized void m2() {
		System.out.println("m2.start");
		super.m();
		System.out.println("m2.end");
	}
}

程序执行过程中。synchronized同步方法如果出现异常,默认情况下就会被释放。 所以,在并发处理的过程中。有异常要多加小心,不然可能出现不一致的情况。 比如在一个Web App处理过程中。多个servlet线程处理同一个资源。如果异常如理不合适。 在第一个线程抛出异常,其他线程就会进入同步代码区。有可能会访问到异常产生时的数据。 因此要非常小心处理同步业务逻辑中的异常

public class T {
	
	private int count = 0;
	
	synchronized void m() {
		System.out.println(Thread.currentThread().getName() + "start");
		while (true) {
			System.out.println(Thread.currentThread().getName()+ "count = " + count);
			count++;
			try {
				TimeUnit.SECONDS.sleep(1);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			if (count == 5) {
				int c = 1/0;  // 这里抛出异常。如果想要锁释放就不要进行try()catch{}.然后让循环继续。
			}
		}
	}
	public static void main(String[] args) {
		T t = new T();
		Runnable r = new Runnable() {
			
			@Override
			public void run() {
				t.m();
			}
		};
		new Thread(r,"t1").start();
		try {
			TimeUnit.SECONDS.sleep(3);
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		new Thread(r,"t2").start();
	}
}

锁定某对象o , 如果 o 的属性发生变化。不影响锁的使用,但是如果 o 变成另外一个对象。则锁定的对象发生改变。 应该避免将锁定的对象引用变成另外的对象。

public class T {

	Object o = new Object();
	
	void m() {
		synchronized (o) {
			while (true) {
				try {
					TimeUnit.SECONDS.sleep(1);
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
				System.out.println(Thread.currentThread().getName());
			}
		}
	}
	
	public static void main(String[] args) {
		T t = new T();
		// 启动第一个线程
		new Thread(t::m,"t1").start();
		
		try {
			TimeUnit.SECONDS.sleep(2);
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		// 修改 o 对象的引用  你就会发现,第二个线程在第一个线程没有释放锁的情况下运行了
		// 证明了synchronized锁定的是堆内存中new出的对象,锁的内存是记录在堆内存中的
		t.o = new Object();
		// 创建第二个线程
		new Thread(t::m,"t2").start();
	}	
}

不要以字符串常量作为锁定对象
在下面的例子中,m1和m2其实锁定的是同一个对象。
== 这种情况还会发生比较诡异的现象。 比如你用到了一个类库,该类库中代码锁定了字符串hello==
但是你读不到源码,所以你在自己的代码中也锁定了“hello”,这时候就有可能发生非常诡异的死锁阻塞。
== 因为你得程序和你用到的类库不经意间使用到了同一把锁。==

public class T {
	
	String s1 = "hello";
	
	String s2 = "hello";
	
	void m1() {
		synchronized (s1) {
			
		}
	}
	void m2() {
		synchronized (s2) {
			
		}
	}
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值