`
Strive_sprint
  • 浏览: 21714 次
  • 性别: Icon_minigender_1
  • 来自: 长沙
社区版块
存档分类
最新评论

为多线程当一次锁匠

 
阅读更多

 

      继上一篇分析了java线程各种状态,以线程终止和线程的调度。现在再来看看最重要的线程安全了,也就是线程同步,下面让我们为多线程当一次锁匠吧。。

 

      在单线程程序中,每次只能做一件事情,后面的事情也需要等待前面的事情完成后才可以进行,如果使用多线程程序,虽然能够实现多处理,但是会发生两个或以上的线程抢占资源的问题,在这个时候就要引进线程安全了。

 

先看个例子:

public class Test1 implements Runnable{
	private int num;
	public void run(){
		for(int i = 0;i<5;i++){
			num += i;
			System.out.println(Thread.currentThread().getName()+"===="+num);
			try{
				Thread.sleep(500);
			}catch(InterruptedException e){
				e.printStackTrace();
			}
		}
	}
	
	public static void main(String [] args){
		Test1 t = new Test1();
		Thread t1 = new Thread(t);
		Thread t2 = new Thread(t);
		t1.start();
		t2.start();
	}
}

结果:

Thread-0====0
Thread-1====0
Thread-0====1
Thread-1====2
Thread-0====4
Thread-1====6
Thread-0====9
Thread-1====12
Thread-0====16
Thread-1====20

 

      看上去好像没什么错,和应该是20啊,但是我们看蓝色是线程0执行的,粉色是线程1执行的,很明显在线程0用了一次num后接着线程1又接着用,而不是等线程0用完了再用,这里就出现了资源了抢占。

 

但是如果我们把num放进run()方法中就会发现:

public void run(){
	int num = 0;
	for(int i = 0;i<5;i++){
		num += i;
		System.out.println(Thread.currentThread().getName()+"===="+num);
		try{
			Thread.sleep(500);
		}catch(InterruptedException e){
			e.printStackTrace();
		}
	}
}

 结果:

Thread-0====0
Thread-1====0
Thread-0====1
Thread-1====1
Thread-0====3
Thread-1====3
Thread-0====6
Thread-1====6
Thread-0====10
Thread-1====10


      num放进run方法中,就是run方法中的局部变量,每个线程都有这么一个局部变量,每个线程都用自己的num,而不是两个线程共用这么一个num,所以每个线程从0加到4结果都是10。

 

      如何解决资源共享的问题,基本上多有解决多线程资源冲突问题的方法都是在指定时间段内只允许一个线程访问共享资源,这时就需要给共享资源上一道锁,当访问完毕就释放该锁,让别的进程来使用该资源。

 

      实现线程安全的同步机制有:使用synchronized关键字和Lock关键字。下面我来介绍这2种实现线程同步的方式。

 

1.synchronized

      synchronized可以修饰方法,可以有自己的代码块,也就是修饰对象,synchronized不需要手动释放锁,是自动进行的释放的。还记得我分析过设计模式中的的单例模式,懒汉式的代码中就用到了synchronized关键字来修饰方法。下面用synchronized关键字来解决上例同步问题。

 

修饰方法:

public synchronized void run(){
	for(int i = 0;i<5;i++){
		num = num + i;
		System.out.println(Thread.currentThread().getName()+"===="+num);
		try{
			Thread.sleep(500);
		}catch(InterruptedException e){
			e.printStackTrace();
		}
	}
}

      也可以加在一般方法上,然后在run()方法中调用这个同步方法,说明这时有多个线程来访问这个方法,但是只允许一个线程访问。

 

修饰对象:

public void run(){
	synchronized(this){
		for(int i = 0;i<5;i++){
			num = num + i;
			System.out.println(Thread.currentThread().getName()+"===="+num);
			try{
				Thread.sleep(500);
			}catch(InterruptedException e){
				e.printStackTrace();
			}
		}
	}
}

      this代表当前对象,如果使用当前对象作为锁(也可以用其他对象作为锁),那就说明有很多线程需要得到这个对象所,并且执行该对象的方法或者改变该对象的变量。

 

     如果有两个线程试图进入某个类两个类不同方法中,并且同步的是同一个对象锁,那么他们任然是互斥的:

public class ThreadTest1 {
	public void fun1(){
		System.out.println(Thread.currentThread().getName()+":没有同步在fun1()方法中");
		synchronized(this){
			for(int i = 0;i<3;i++){
				System.out.println(Thread.currentThread().getName()+":同步在fun1()方法中");
				try{
					Thread.sleep(500);
				}catch(InterruptedException e){
					e.printStackTrace();
				}
			}
		}
	}
	public void fun2(){
		System.out.println(Thread.currentThread().getName()+":没有同步在fun2()方法中");
		synchronized(this){
			for(int i = 0;i<3;i++){
				System.out.println(Thread.currentThread().getName()+":同步在fun2()方法中");
				try{
					Thread.sleep(500);
				}catch(InterruptedException e){
					e.printStackTrace();
				}
			}
		}
	}
	public static void main(String [] args){
		final ThreadTest1 r =  new ThreadTest1();
		Thread t1 = new Thread(new Runnable(){
			public void run() {
				r.fun1();
			}
		});
		t1.start();
		Thread t2 = new Thread(new Runnable(){
			public void run() {
				r.fun2();
			}
		});
		t2.start();
	}
}

 结果:

Thread-1:没有同步在fun2()方法中
Thread-1:同步在fun2()方法中
Thread-0:没有同步在fun1()方法中
Thread-1:同步在fun2()方法中
Thread-1:同步在fun2()方法中
Thread-0:同步在fun1()方法中
Thread-0:同步在fun1()方法中
Thread-0:同步在fun1()方法中

      可以看出,在一小段时间内,只有一个线程得到同步锁,因而只有一个线程在执行,当线程1执行完线程0才开始执行。

 

      如果两个方法的同步的不是一个对象锁,那会怎样呢?

private Object syncObject = new Object();	
public void fun1(){
	System.out.println(Thread.currentThread().getName()+":没有同步在fun1()方法中");
	synchronized(this){
		for(int i = 0;i<3;i++){
			System.out.println(Thread.currentThread().getName()+":同步在fun1()方法中");
			try{
				Thread.sleep(500);
			}catch(InterruptedException e){
				e.printStackTrace();
			}
		}
	}
}

public void fun2(){
	System.out.println(Thread.currentThread().getName()+":没有同步在fun2()方法中");
	synchronized(syncObject){
		for(int i = 0;i<3;i++){
			System.out.println(Thread.currentThread().getName()+":同步在fun2()方法中");
			try{
				Thread.sleep(500);
			}catch(InterruptedException e){
				e.printStackTrace();
			}
		}
	}
} 

结果:

Thread-1:没有同步在fun2()方法中
Thread-1:同步在fun2()方法中
Thread-0:没有同步在fun1()方法中
Thread-0:同步在fun1()方法中
Thread-1:同步在fun2()方法中
Thread-0:同步在fun1()方法中
Thread-1:同步在fun2()方法中
Thread-0:同步在fun1()方法中

     fun1()方法中的同步锁是对象this即当前对象,而fun2()方法中的同步锁是对象syncObject对象,两个方法不是用的同一个对象作为锁,从运行结果看出线程0和线程1是交替进行的,说明都得到了各自所需的锁。

 

2.Lock

      lock是在方法中,当方法需要锁的时候就lock,当结束的时候就unlock,unlock需要在finally块中。这里需要手动调用unlock方法释放锁。

 

      当用的同一Lock锁:

public class ThreadTest3 {
	private Lock lock = new ReentrantLock();
	public void fun1(){
		System.out.println(Thread.currentThread().getName()+":没有同步到fun1()方法中");
		lock.lock();
		try{
			for(int i = 0;i<3;i++){
				System.out.println(Thread.currentThread().getName()+":同步到fun1()方法中");
				try{
					Thread.sleep(500);
				}catch(InterruptedException e){
					e.printStackTrace();
				}
			}
		}finally{
			lock.unlock();
		}
	}
	public void fun2(){
		System.out.println(Thread.currentThread().getName()+":没有同步到fun2()方法中");
		lock.lock();
		try{
			for(int i = 0;i<3;i++){
				System.out.println(Thread.currentThread().getName()+":同步到fun2()方法中");
				try{
					Thread.sleep(500);
				}catch(InterruptedException e){
					e.printStackTrace();
				}
			}
		}finally{
			lock.unlock();
		}
	}
	public static void main(String [] args){
		final ThreadTest3 r = new ThreadTest3();
		Thread t1 = new Thread(new Runnable(){
			public void run(){
				r.fun1();
			}
		});
		t1.start();
		Thread t2 = new Thread(new Runnable(){
			public void run() {
				r.fun2();
			}
		});
		t2.start();
	}
}

结果:

Thread-0:没有同步到fun1()方法中
Thread-0:同步到fun1()方法中
Thread-1:没有同步到fun2()方法中
Thread-0:同步到fun1()方法中
Thread-0:同步到fun1()方法中
Thread-1:同步到fun2()方法中
Thread-1:同步到fun2()方法中
Thread-1:同步到fun2()方法中
      和使用synchronized关键字同步一个对象锁的情况是一样的。

 

      再看看使用不一样的同步锁:

private Lock lock1 = new ReentrantLock();
private Lock lock2 = new ReentrantLock();

public void fun1(){
	System.out.println(Thread.currentThread().getName()+":没有同步到fun1()方法中");
	lock1.lock();
	try{
		for(int i = 0;i<3;i++){
			System.out.println(Thread.currentThread().getName()+":同步到fun1()方法中");
			try{
				Thread.sleep(500);
			}catch(InterruptedException e){
				e.printStackTrace();
			}
		}
	}finally{
		lock1.unlock();
	}
}

public void fun2(){
	System.out.println(Thread.currentThread().getName()+":没有同步到fun2()方法中");
	lock2.lock();
	try{
		for(int i = 0;i<3;i++){
			System.out.println(Thread.currentThread().getName()+":同步到fun2()方法中");
			try{
				Thread.sleep(500);
			}catch(InterruptedException e){
				e.printStackTrace();
			}
		}
	}finally{
		lock2.unlock();
	}
}

结果:

Thread-0:没有同步到fun1()方法中
Thread-1:没有同步到fun2()方法中
Thread-0:同步到fun1()方法中
Thread-1:同步到fun2()方法中
Thread-1:同步到fun2()方法中
Thread-0:同步到fun1()方法中
Thread-0:同步到fun1()方法中
Thread-1:同步到fun2()方法中
      也可以看出结果和使用synchronized关键字用不同的同步锁一样,线程0和线程1是交替进行的,也都各自得到所需的锁。

 

      synchronized和Lock都可以实现线程的同步,实现资源的共享,每当出现两个甚至多个可以达到同一目的的方式,我们都会问一个问题:哪个更好?这个我没具体测试过,只是在网上搜搜,都是说Lock比synchronized好,但是在同步线程数量少的时候synchronized效率更高,至于这个临界点也不是很清楚。还有一点不一样的就是:如果使用synchronized ,如果A不释放,B将一直等下去,不能被中断。如果使用ReentrantLock,如果A不释放,可以使B在等待了足够长的时间以后,中断等待,而干别的事情。

 

      好吧。我们也为多线程当一次锁匠,以后面对多线程的同步就不会那么棘手了。

分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics