java 7 并发 初级 学习记录(2)

1.资源竞争

    并发的难点集中在如何对一个资源进行操作并且保证不出错,如果不采取任何措施,线程可不会乖乖按照你的想法进行执行,他们会产生一些非常奇葩的问题。

    关键字:synchronized,同步关键字,可以修饰整个方法,也可以在方法内部使用同步块。它的作用,就是让多个线程在同时访问某个方法的时候,进行单一处理。每次只允许一个线程进入方法,进入后,对方法加锁,其他线程全部在方法外暂时堵塞,直到进入方法中的线程执行完毕之后,解锁,其他线程才允许进入。很好理解!

    那如果有多个方法被修饰了synchronized呢?可以把多个synchronized修饰的方法想成一个同步组方法,他们就是一个整体,这一个组方法都只允许每次只有一个线程在执行。

    那如果static方法被修饰了synchronized呢?很明显,那所有的线程对这个static方法都会被同步,而不单单只是对某一个对象了。如果有一个static同步方法和一个普通同步方法呢?要注意的是,他们不会被归为一个同步组中,即一个线程可以进入static同步方法中,而在同一时刻,另一个线程可以进入普通同步方法中。


2.同步代码块

    synchronized修饰的方法,线程会被阻塞在方法外面,这样会造成一定程度上的性能损耗。其实很多时候,需要同步的部分只是方法中的某个部分,甚至就几行代码。所以我们可以把同步的规模进行缩小,缩小到只包含这一部分的代码。可以使用synchronized代码块来实现,synchronized代码块会接收一个参数,这个参数通常是某个对象,或者是this自身,其实他的含义就是需要获取谁的对象锁。通常在一个对象中使用synchronized代码块,参数一般设置成this,那么这把锁就是当前对象的锁,不过也有另外一种可能,比如两个同步方法,我需要有多个线程能分别同时进入。上面已经有说明了同一个对象中,synchronized方法组只能有一个线程进入,那怎么还能同时进入两个方法呢?synchronized代码块可以解决,秘诀就是这个参数的设置。要知道一个本质,同步都是根据对象的锁的进行的,不同的锁当然是可以给多个线程去执行的,所以synchronized(obj){}中的obj除了this,当然还可以自定义一个Object类的实例来充当,即在类中定义一个Object类的属性,把他设置为同步代码块的参数,OK,这样就不会和持有this锁的部分同步了。

    以上全是理论,下面做点小例子测试。

public class JavaCurrent {

	public static void main(String[] args) {
		Target target = new Target();
		for (int i = 0; i < 2; i++) {
			Thread t = new Thread(new Task(target));
			t.start();
		}
		Thread thread1 = new Thread(new Task1(target));
		Thread thread2 = new Thread(new Task2(target));		
		thread1.start();
		thread2.start();
	}
}

----------------------------------------------------------------------------------------
/**
 * 竞争目标类
 * @author dlsm-syq
 *
 */
public class Target {

	private Object obj = new Object();//新建加锁对象
	
	public synchronized static void methodOfStatic(){
		System.out.println(Thread.currentThread().getName()+"->进入methodOfStatic静态方法中,处理一些事物,需要3秒时间");
		try {
			TimeUnit.SECONDS.sleep(3);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		System.out.println(Thread.currentThread().getName() + " methodOfStatic 处理完成,期间没有其他线程打扰");
	}
	
	
	public void methodOfNormalBlock(){
		synchronized (this) {
			System.out.println(Thread.currentThread().getName() + "-->进入methodOfNormalBlock方法代码块中,处理事物需要3秒钟");
			try {
				TimeUnit.SECONDS.sleep(3);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			System.out.println(Thread.currentThread().getName() + " methodOfNormalBlock 处理完成,期间没有其他线程打扰");
		}
	}
	
	
	public void methodOfNormalBlockObj(){
		synchronized (obj) {
			System.out.println(Thread.currentThread().getName() + "-->进入methodOfNormalBlockObj方法代码块中,处理事物需要3秒钟");
			try {
				TimeUnit.SECONDS.sleep(3);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			System.out.println(Thread.currentThread().getName() + " methodOfNormalBlockObj 处理完成,期间没有其他线程打扰");
		}
	}
}

-----------------------------------------------------------------------------------------
public class Task implements Runnable{

	private Target target;
	
	public Task(Target target) {
		this.target = target;
	}
	
	@Override
	public void run() {
		Target.methodOfStatic();
	}
}

public class Task1 implements Runnable{

	private Target target;
	
	public Task1(Target target) {
		this.target = target;
	}
	
	@Override
	public void run() {
		target.methodOfNormalBlock();
	}
}

public class Task2 implements Runnable{

	private Target target;
	
	public Task2(Target target) {
		this.target = target;
	}
	
	@Override
	public void run() {
		target.methodOfNormalBlockObj();
	}
}

----------------------------------------------执行结果-------------------------------------
Thread-0->进入methodOfStatic静态方法中,处理一些事物,需要3秒时间
Thread-3-->进入methodOfNormalBlockObj方法代码块中,处理事物需要3秒钟
Thread-2-->进入methodOfNormalBlock方法代码块中,处理事物需要3秒钟
Thread-0 methodOfStatic 处理完成,期间没有其他线程打扰
Thread-2 methodOfNormalBlock 处理完成,期间没有其他线程打扰
Thread-1->进入methodOfStatic静态方法中,处理一些事物,需要3秒时间
Thread-3 methodOfNormalBlockObj 处理完成,期间没有其他线程打扰
Thread-1 methodOfStatic 处理完成,期间没有其他线程打扰

    貌似没有问题

3.wait(),notify(),notifyall()

    先说说对象锁。一个线程都是在进入synchronized方法或者代码块中才能获取该对象的锁,在执行完毕之前,都会一直持有锁。所以这里一个非常重要的注意点就是线程必须持有锁,也就是说必须在synchronized方法或者代码块内部情况下,才能继续话题。

    wait(),notify(),notifyall() 三个方法都不是线程相关类中的方法,而是原始类Object中的方法,原因很简单了,就是每个对象都有控制线程的权利。三个方法都必须在synchronized方法或者代码块中使用,不然必定抛出java.lang.IllegalMonitorStateException异常。要注意是调用他们三个的时候,调用notify的对象必须要和synchronized的锁对象保持一致,不然也是IllegalMonitorStateException异常。也许有点混乱,这里举个例子说明:

public class JavaCurrent {

	public static void main(String[] args) {
		B b = new B();
		A a = new A(b);
		
		Thread t1 = new Thread(new ATask(a));
		Thread t2 = new Thread(new BTask(b));
		
		t2.start();
		try {
			TimeUnit.MILLISECONDS.sleep(1000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		t1.start();
	}
	
	
}

class ATask implements Runnable{

	private A a;
	
	public ATask(A a) {
		this.a = a;
	}
	
	@Override
	public void run() {
		a.amethod();
	}
	
}

class BTask implements Runnable{

	private B b;
	
	public BTask(B b) {
		this.b = b;
	}
	
	@Override
	public void run() {
		for (int i = 0; i < 10; i++) {
			b.bmethod();
		}
	}
	
}

------------------------------------------------------------------------------
public class A {

	private B b;
	
	public A(B b) {
		this.b = b;
	}
	
	public synchronized void amethod(){
		System.out.println(Thread.currentThread().getName()+" in amethod doing something");
		b.notifyAll();
	}
	
}

---------------------------------------------------------------------------------
public class B {

	public synchronized void bmethod(){
		System.out.println(Thread.currentThread().getName() +" in bmethod doing somethind");
		try {
			wait();
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}
}


 运行结果:
Thread-1 in bmethod doing somethind
Thread-0 in amethod doing something
Exception in thread "Thread-0" java.lang.IllegalMonitorStateException
	at java.lang.Object.notifyAll(Native Method)
	at syq.think.current7.p70.A.amethod(A.java:13)
	at syq.think.current7.p70.ATask.run(JavaCurrent.java:60)
	at java.lang.Thread.run(Unknown Source)

  例子中,A类中持有一个B类的对象引用,然后在synchronized方法中调用b.notifyall()方法,结果出现异常。也就是说没有获取b的对象锁,但却是在a的synchronized方法中。所以调用notifyall的对象所处的同步块要在该对象的synchronized块中。

    wait()和sleep()的区别,sleep()可以在任何地方使用,wait()必须在同步块中使用,wait()会使当前线程释放对象锁,然后进入一个waitting room的线程等候区,等待notify。notify()会挑waitting room中的一条线程进行唤醒,notifyall()唤醒waitting room中的所有线程,然后线程都去竞争对象锁,建议使用notifyall方法。

    接下来看看具体用法的例子:

public class JavaCurrent {

	public static void main(String[] args) {
		//开8个线程去跑WaitTask
		Target target = new Target();
		for (int i = 0; i < 8; i++) {
			Thread t = new Thread(new WaitTask(target));
			t.start();
		}
		
		try {
			TimeUnit.SECONDS.sleep(2);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		//开1个线程去跑NotifyTask
		Thread thread = new Thread(new NotifyTask(target));
		thread.start();
	}
}

----------------------------------------------------------------------------------
public class Target {

	private int allowIn = 3;//允许4个线程经过setwait()
	
	private int count = 0;
	
	public void setWait(){
		System.out.println(Thread.currentThread().getName()+ " 进入setWait");
		synchronized (this) {
			System.out.println(Thread.currentThread().getName()+ " 进入setWait中的同步块中");
			if(count > allowIn){
				try {
					System.out.println(Thread.currentThread().getName()+ " 发现已经超出最大数值了,只能等待了!");
					wait();
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
			try {
				TimeUnit.SECONDS.sleep(1);//休眠1秒
				System.out.println(Thread.currentThread().getName()+ " 处理setWait完毕,退出方法,释放锁");
				count ++ ;
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
	}
	
	public void setNotify(){
		System.out.println(Thread.currentThread().getName()+ " 进入setNotify");
		synchronized (this) {
			System.out.println(Thread.currentThread().getName()+ " 进入setNotify中的同步块中");
			if(count > allowIn){
				System.out.println(Thread.currentThread().getName()+ " 发现已经超出最大数值了,数值清理!");
				count = 0;
				notifyAll();
			}else{
				try {
					TimeUnit.SECONDS.sleep(2);//休眠2秒
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
				System.out.println(Thread.currentThread().getName()+ " 处理setNotify完毕,退出方法,释放锁");
			}
		}
	}
}

------------------------------------------------------------------------------------
public class NotifyTask implements Runnable{

	private Target target;
	
	public NotifyTask(Target target) {
		this.target = target;
	}
	
	@Override
	public void run() {
		while(true){
			target.setNotify();
		}
	}

}

public class WaitTask implements Runnable{

	private Target target;
	
	public WaitTask(Target target) {
		this.target = target;
	}
	
	@Override
	public void run() {
		target.setWait();
	}

}

执行结果:
Thread-0 进入setWait
Thread-2 进入setWait
Thread-1 进入setWait
Thread-0 进入setWait中的同步块中
Thread-3 进入setWait
Thread-5 进入setWait
Thread-7 进入setWait
Thread-4 进入setWait
Thread-6 进入setWait
Thread-0 处理setWait完毕,退出方法,释放锁
Thread-6 进入setWait中的同步块中
Thread-6 处理setWait完毕,退出方法,释放锁
Thread-4 进入setWait中的同步块中
Thread-8 进入setNotify
Thread-4 处理setWait完毕,退出方法,释放锁
Thread-2 进入setWait中的同步块中
Thread-2 处理setWait完毕,退出方法,释放锁
Thread-7 进入setWait中的同步块中
Thread-7 发现已经超出最大数值了,只能等待了!
Thread-5 进入setWait中的同步块中
Thread-5 发现已经超出最大数值了,只能等待了!
Thread-3 进入setWait中的同步块中
Thread-3 发现已经超出最大数值了,只能等待了!
Thread-1 进入setWait中的同步块中
Thread-1 发现已经超出最大数值了,只能等待了!
Thread-8 进入setNotify中的同步块中
Thread-8 发现已经超出最大数值了,数值清理!
Thread-8 进入setNotify
Thread-1 处理setWait完毕,退出方法,释放锁
Thread-3 处理setWait完毕,退出方法,释放锁
Thread-5 处理setWait完毕,退出方法,释放锁
Thread-7 处理setWait完毕,退出方法,释放锁
Thread-8 进入setNotify中的同步块中
Thread-8 发现已经超出最大数值了,数值清理!
Thread-8 进入setNotify
Thread-8 进入setNotify中的同步块中
Thread-8 处理setNotify完毕,退出方法,释放锁
Thread-8 进入setNotify
Thread-8 进入setNotify中的同步块中

    可以看到同时开了8个线程跑waitTask,当thread0进入同步块之后,其他线程都在外面等待了,知道它处理完毕释放锁了,thread6进入了同步块中进行相同的处理。但thread7进入时,因为前面已经有thread0,6,4,2这四个线程通过了,所以thread7只能wait等待了,同理后面的进入的线程全部都要去wait等待。最后当thread8进入setNotify并执行完毕之后,重置了count属性,并且notifyall所有wait线程,后续的线程又可以继续活动了。注意:被唤醒之后的线程会重新竞争对象锁,然后从wait()处继续向下执行。

    


你可能感兴趣的:(java 7 并发 初级 学习记录(2))