1-1并发编程(1)

1synchronized

1.1任何线程要执行下面的代码,必须先拿到o的锁

/**
 * synchronized关键字
 * 对某个对象加锁
 * @author mashibing
 */

package yxxy.c_001;

public class T {
	
	private int count = 10;
	private Object o = new Object();
	
	public void m() {
		synchronized(o) { //任何线程要执行下面的代码,必须先拿到o的锁
			count--;
			System.out.println(Thread.currentThread().getName() + " count = " + count);
		}
	}
	
}

o对象new了一个变量。在堆内存里边。即在堆内存new了一个对象。
执行synchronized里边的代码,首先要申请对象的锁。
当第二个线程去执行的时候,也要去申请o这把锁,发现o已经被第一个线程锁定了。第二个线程只能等着,等着这把锁释放了。
第一个线程执行完的时候,就会释放锁。第二个线程申请锁定就能锁定了。
发现这把锁锁定了,别的线程就不能在去锁定了,叫互斥锁。只要有一个线程拿到了,其他线程都拿不到。

1.2任何线程要执行下面的代码,必须先拿到this的锁


/**
 * synchronized关键字
 * 对某个对象加锁
 * @author mashibing
 */

package yxxy.c_002;

public class T {
	
	private int count = 10;
	
	public void m() {
		synchronized(this) { //任何线程要执行下面的代码,必须先拿到this的锁
			count--;
			System.out.println(Thread.currentThread().getName() + " count = " + count);
		}
	}
	
}

synchronized锁定的是一个对象,并不是代码快

/**
 * synchronized关键字
 * 对某个对象加锁
 * @author mashibing
 */

package yxxy.c_003;

public class T {

	private int count = 10;
	
	public synchronized void m() { //等同于在方法的代码执行时要synchronized(this)
		count--;
		System.out.println(Thread.currentThread().getName() + " count = " + count);
	}

}

1.3synchronized 修饰静态

静态的属性不需要new出对象就可以访问,所以没有this。
写synchronized(this)是不可以的。

/**
 * synchronized关键字
 * 对某个对象加锁
 * @author mashibing
 */

package yxxy.c_004;

public class T {

	private static int count = 10;
	
	public synchronized static void m() { //这里等同于synchronized(yxxy.c_004.T.class)
		count--;
		System.out.println(Thread.currentThread().getName() + " count = " + count);
	}
	
	public static void mm() {
		synchronized(T.class) { //考虑一下这里写synchronized(this)是否可以?
			count --;
		}
	}

}

1.4 分析程序

/**
 * 分析一下这个程序的输出
 * @author mashibing
 */

package yxxy.c_005;

public class T implements Runnable {

	private int count = 10;
	
	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<5; i++) {
			new Thread(t, "THREAD" + i).start();
		}
	}
	
}

main方法中T t = new T(); 只 new了一个对象,好多线程共同访问一个对象。
栈内存有个t,指向堆内存的空间,new出来一个 T对象。T对象中有个count值,他的值是10
起了5个线程,每个线程访问的是同一个 T对象。则每个线程run方法里访问的都是一个count。
执行代码,看有什么问题?
在这里插入图片描述
线程重入问题,线程执行一半,另一个线程来来打断来。另一个线程来来,又打断了。
解决:
代码块加把锁。执行这两句代码的时候,不允许其他代码访问。那么这个值一定是正确的。
一个synchronized代码块相当于一个原子操作,原子就是不可分的。即执行这个代码块时是不能打断的,只有代码块执行完,其他线程才能继续执行相同代码

1.4 同步和非同步方法是否可以同时调用?

public class T {

	public synchronized void m1() { 
		System.out.println(Thread.currentThread().getName() + " m1 start...");
		try {
			Thread.sleep(10000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		System.out.println(Thread.currentThread().getName() + " m1 end");
	}
	
	public void m2() {
		try {
			Thread.sleep(5000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		System.out.println(Thread.currentThread().getName() + " m2 ");
	}
	
	public static void main(String[] args) {
		T t = new T();
		
		/*new Thread(()->t.m1(), "t1").start();
		new Thread(()->t.m2(), "t2").start();*/
		
		new Thread(t::m1, "t1").start();
		new Thread(t::m2, "t2").start();
		
		/*
		new Thread(new Runnable() {
			@Override
			public void run() {
				t.m1();
			}
			
		});
		*/
		
	}
	
}

同步和非同步方法是否可以同时调用?即
执行一个m1方法需要锁定对象,执行第二个方法m2不需要锁定当前对象。请问在执行m1的过程中,m2能否被执行?

可以
如果m1首先启动,假如5秒后m2能打印出来,则说明在m1执行过程之中
m2 是可以运行的。
在这里插入图片描述
原因:
只用synchronized 方法需要申请这个锁,其他方法是不需要这把锁的。所以可以执行

1.5 银行账户,对写进行加锁,对读不用加锁,这样行不行?

/**
 * 对业务写方法加锁
 * 对业务读方法不加锁
 * 容易产生脏读问题(dirtyRead)
 */

package yxxy.c_008;

import java.util.concurrent.TimeUnit;

public class Account {
	String name;
	double balance;
	
	public synchronized void set(String name, double balance) {
		this.name = name;
		//睡眠这两秒,意味这又其他业务执行,可能造成数据不一致。
		
		try {
			Thread.sleep(2000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		
		
		this.balance = balance;
	}
	
	public /*synchronized*/ double getBalance(String name) {
		return this.balance;
	}
	
	
	public static void main(String[] args) {
		Account a = new Account();
		new Thread(()->a.set("zhangsan", 100.0)).start();
		
		try {
			TimeUnit.SECONDS.sleep(1);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		
		System.out.println(a.getBalance("zhangsan"));
		
		try {
			TimeUnit.SECONDS.sleep(2);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		
		System.out.println(a.getBalance("zhangsan"));
	}
}

在这里插入图片描述
这个线程在执行过程之中,尽管在写的方法加了synchronized,锁定了这个对象,但是在锁定的过程之中,可能会被非锁定的方法去访问。
第一次访问的时候获取到了0 ,即balance的默认值,此时产生了脏读。
脏读为什么会产生?因为只对写进行了加锁,没有对读进行加锁。就读到了写还没有完成的数据。

解决: 在读的时候在锁

1.6 一个同步方法可不可以调用另外一个同步方法?一个线程已经拥有某个对象的锁,再次申请的时候是不是还可以得到该对象的锁?

/**
 * 一个同步方法可以调用另外一个同步方法,一个线程已经拥有某个对象的锁,再次申请的时候仍然会得到该对象的锁.
 * 也就是说synchronized获得的锁是可重入的
 */
package yxxy.c_009;

import java.util.concurrent.TimeUnit;

public class T {
	synchronized void m1() {
		System.out.println("m1 start");
		try {
			TimeUnit.SECONDS.sleep(1);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		m2();
	}
	
	synchronized void m2() {
		try {
			TimeUnit.SECONDS.sleep(2);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		System.out.println("m2");
	}
}

执行m1时,对T加了把锁,执行m1过程中调用了m2。调用m2过程中发现也需要加锁,而申请的锁,是自己已经持有的这把锁。同一线程是可以的,时锁上了两次。一个线程已经拥有某个对象的锁,再次申请的时候仍然会得到该对象的锁.也就是说synchronized获得的锁是可重入的

1.7 子类调用父类的同步方法

/**
 * 一个同步方法可以调用另外一个同步方法,一个线程已经拥有某个对象的锁,再次申请的时候仍然会得到该对象的锁.
 * 也就是说synchronized获得的锁是可重入的
 * 这里是继承中有可能发生的情形,子类调用父类的同步方法
 * @author mashibing
 */
package yxxy.c_010;

import java.util.concurrent.TimeUnit;

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

class TT extends T {
	@Override
	synchronized void m() {
		System.out.println("child m start");
		super.m();
		System.out.println("child m end");
	}
}

1.8 程序在执行过程中,如果出现异常,默认情况锁会被释放

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

import java.util.concurrent.TimeUnit;

public class T {
	int count = 0;
	synchronized void m() {
		System.out.println(Thread.currentThread().getName() + " start");
		while(true) {
			count ++;
			System.out.println(Thread.currentThread().getName() + " count = " + count);
			try {
				TimeUnit.SECONDS.sleep(1);
				
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			
			if(count == 5) {
				int i = 1/0; //此处抛出异常,锁将被释放,要想不被释放,可以在这里进行catch,然后让循环继续
				System.out.println(i);
			}
		}
	}
	
	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) {
			e.printStackTrace();
		}
		
		new Thread(r, "t2").start();
	}
	
}

好多线程访问一块数据,其中有一个线程持有锁正在运行,有两个属性,当改了一个属性时,抱了一个异常抛出,锁被释放了。另外的线程就进去了,读到的数据 是只改了一半的数据。

在这里插入图片描述
t1 发生异常,把锁释放了,t2 才能开始执行。
如果不想释放锁,加上try catch 再进行事物的回滚。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值