`
Strive_sprint
  • 浏览: 22385 次
  • 性别: 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在等待了足够长的时间以后,中断等待,而干别的事情。

 

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

分享到:
评论

相关推荐

    C#多线程互斥实例 多线程获取同一变量

    2. **共享资源**:在多线程环境中,多个线程可能需要访问同一个数据或对象,这就形成了共享资源。 3. **竞态条件**:当两个或多个线程同时访问并修改共享资源时,如果没有适当的同步措施,可能导致数据的不一致性,...

    易语言多线程多次启动一个子程序

    在多线程环境中,同一个子程序可以被多次启动,每个启动都会创建一个新的线程实例。为了确保线程安全,需要考虑以下几个方面: 1. **资源竞争**:当多个线程访问同一资源时,可能会引发数据不一致问题。易语言提供...

    C# 多线程实例多线程实例多线程实例

    - `Mutex`:互斥量,一次只允许一个线程访问资源。 - `Semaphore`:信号量,限制同时访问资源的线程数量。 - `Monitor`:基于锁的对象,使用`lock`关键字实现。 - `Barrier`:屏障,用于等待一组线程到达特定点...

    Revit二次开发 c# 多线程处理

    为安全地在多线程环境中工作,我们需要利用Revit的`IExternalEvent`接口,这是一种异步事件处理机制,允许我们在后台线程中执行任务,然后在主线程中更新Revit模型。 1. **创建外部事件** 首先,我们需要创建一个`...

    java多线程分页查询

    多线程是指在一个程序中包含多个可以并发执行的线程,这些线程共享相同的内存空间。通过合理利用多线程技术,可以显著提升程序的运行效率和响应速度。在Java中,可以通过继承`Thread`类或者实现`Runnable`接口来创建...

    .NET多线程实例

    在多线程环境中,事件处理通常在引发事件的线程上运行,因此,如果一个线程修改了UI控件的状态,而另一个线程尝试处理相关事件,可能会导致线程不安全。使用适当的同步机制可以解决这个问题。 "资源管理器"可能涉及...

    多线程导入excel 数据

    在导入Excel时,可以为每个Excel文件创建一个线程,或者根据文件大小划分行数,每部分分配一个线程。 - **并发控制**:多线程环境下,数据竞争和资源争抢是常见问题。Java提供`synchronized`关键字、`Lock`接口(如...

    C#多线程 C#多线程

    在C#编程中,多线程是一种允许程序同时执行多个任务的技术,它极大地提高了应用程序的性能和响应速度。本文将深入探讨C#中的多线程概念、线程池的使用以及如何通过实例理解其工作原理。 首先,多线程在C#中是通过`...

    用VB6实现多线程

    在VB6(Visual Basic 6)环境中,多线程是一个重要的技术,它允许程序同时执行多个任务,提高程序的响应性和效率。VB6本身并不直接支持多线程,但可以通过调用Windows API来实现。本篇文章将深入探讨如何在VB6中实现...

    C#多线程读写sqlite

    当多线程环境对SQLite进行读写操作时,可能会引发数据竞争和并发问题,因此必须采取适当的同步策略来确保数据的一致性和完整性。 标题"**C#多线程读写sqlite**"涉及的主要知识点包括: 1. **多线程编程**:C#中的`...

    12.1 Qt5多线程:多线程及简单实例

    Qt5框架提供了一种方便的方式来实现多线程,它允许开发者在不同的线程中执行任务,从而避免主线程(GUI线程)因处理耗时操作而变得卡顿。本知识点将深入探讨Qt5中的多线程以及一个简单的实例——WorkThread。 **1. ...

    PB多线程实现

    PB12.5引入了对多线程的更好支持,它引入了一个名为“Worker Thread”的新概念。开发者可以创建一个工作线程对象,然后在这个对象上执行自定义的代码块。这使得在PB应用中实现多线程变得更加简单。在PB12.5中,还...

    java多线程查询数据库

    多线程并发查询允许我们将一个大任务分解为多个小任务,每个任务在不同的线程上独立运行,从而提高查询效率。在数据库查询中,这通常适用于处理分页数据或执行并行查询。 ### 2. 线程池 Java中的线程池是通过`java....

    C#.NET多线程实例6个(包括多线程基本使用,多线程互斥等全部多线程使用实例),可直接运行

    在.NET框架中,C#语言提供了强大的多线程支持,使得开发者可以充分利用现代多核处理器的优势,实现并行处理和高效能编程。本资源包含六个C#.NET多线程的实例,涵盖了多线程的基本使用到更高级的概念,如线程互斥。...

    java 多线程操作数据库

    7. **性能优化**:为了进一步提高多线程数据库操作的性能,可以考虑使用批处理(Batch Processing),即在一次数据库调用中发送多条SQL语句。此外,合理调整数据库和应用服务器的配置参数,如连接池大小、线程数量等...

    delphi多线程调用dll

    当需要在多线程环境中调用DLL时,情况会变得相对复杂,因为线程安全、资源管理以及同步问题需要额外的关注。 首先,我们需要理解多线程的基本概念。在Delphi中,我们可以使用TThread类来创建和管理线程。每个线程都...

    Qt中利用OpenCV2.4.4多线程打开多摄像机

    模拟了一个16个摄像机的场景,有不开多线程和打开多线程的对比。 可以明显感觉到打开多线程后主界面不卡了。 只是个多线程的小例子,还有很多不足之处,有待各位完善。 注意:pro文件中的include和lib文件目录需要...

    使用三种VC的多线程同步方法编写一个多线程的程序

    1.使用三种VC的多线程同步方法编写一个多线程的程序(要求在屏幕上先显示Hello,再显示World)。 1)基于全局变量的多线程同步程序; 2)基于事件的多线程同步程序; 3)基于临界区的多线程同步程序。

    mybaits 多线程 实现数据批量插入 (运用CountDownLatch实现闭锁)

    MyBatis,作为一个优秀的Java持久层框架,提供了多种优化手段,其中包括多线程和批处理技术。本文将详细介绍如何利用MyBatis结合多线程和CountDownLatch闭锁来实现数据的批量插入。 首先,我们来看`mybatis批处理`...

Global site tag (gtag.js) - Google Analytics