`

Java 多线程 打印的控制方法

 
阅读更多
我今天要分析的问题很简单,使用三个线程,要求线程1打印“AAA”,然后线程2打印“BBB”,然后线程3打印“CCC”,重复10次。

使用 java api。用尽量多的方式去实现以上功能。

今天先上第一种吧。完全基于块的synchronized。
package learn;

class MyLock{
	int target = 0;
	String strprint[] = {"AAA", "BBB", "CCC"}; 
}


class MethodToPrint implements Runnable{
	private int id, count = 10;
	private MyLock lock;
	MethodToPrint(int id, MyLock lock){
		this.id = id;
		this.lock = lock;
	}
	public void run(){
		while(count > 0){
			synchronized(lock){
				if(id == lock.target){
					count--;
					System.out.println(lock.strprint[id]);
					lock.target = (lock.target +1) % 3;
				}
			}
		}
	}
}

class Go{
	public static void main(String args[]){
		MyLock lock = new MyLock();
		Thread th1 = new Thread( new MethodToPrint(0, lock));
		Thread th2 = new Thread( new MethodToPrint(1, lock));
		Thread th3 = new Thread( new MethodToPrint(2, lock));
		th1.start();
		th2.start();
		th3.start();
	}
}


这种写法在效率上不好的一点是即使thx在 id == lock.target的判断中失败,这个thx还是会在while循环中恬不知耻的继续去试图占有锁,而在 lock.target在被其他线程改写之前,thx的这种尝试是必然失败的,所以一个改进就是在 id = lock.target失败后再写一个分支,让thx在对象lock上wait,同时释放锁,让那些可能会在当前状况下打印的线程去占有锁,这个 right thread 通过了 id == lock.target的判断后,执行完操作,就唤醒在lock上wait的线程,因为现在的lock.target已经改变,其他线程可以去竞争这把锁了。
代码如下
package learn;

class MyLock{
	int target = 0;
	String strprint[] = {"AAA", "BBB", "CCC"}; 
}


class MethodToPrint implements Runnable{
	private int id, count = 10;
	private MyLock lock;
	MethodToPrint(int id, MyLock lock){
		this.id = id;
		this.lock = lock;
	}
	public void run(){
		while(count > 0){
			synchronized(lock){
				if(id == lock.target){
					count--;
					System.out.println(lock.strprint[id]);
					lock.target = (lock.target +1) % 3;
					lock.notifyAll();
				}
				else{
					try{
						lock.wait();
					}catch(InterruptedException e){
						throw new RuntimeException(e);
					}
				}
			}
		}
	}
}

class Go{
	public static void main(String args[]){
		MyLock lock = new MyLock();
		Thread th1 = new Thread( new MethodToPrint(0, lock));
		Thread th2 = new Thread( new MethodToPrint(1, lock));
		Thread th3 = new Thread( new MethodToPrint(2, lock));
		th1.start();
		th2.start();
		th3.start();
	}
}



好了,以上就是synchronized块的使用,它的好处自然是明显的,就是simplicity和safety,synchronized是隐式的获取对象的监视器所以是简单的,并可以主动释放所以是安全的。
当然,synchronized(obj) 有不好的地方,它只有一组java.lang.Object实现的监视器方法,wait(),notify(),natifyAll(),而且释放必须在同一个程序块内。
好了,以上的废话是为了引出java api中另外的同步方式:
java.util.concurrent.locks.Condition
java.util.concurrent.locks.Lock
java.util.concurrent.locks.ReentrantLock
ReentrantLock 可重入锁,Condition是定义在锁上“监视器方法”,被称为条件,类似于wait(),notify,notifyAll,但是Condition可以有多个(在一个锁上)。
详细的可以参照java api文档。


相信大家很容易理解这个部分的api,下面给出一个错误的想实现本文target的代码,如果想检测自己concurrent编程能力的朋友可以自己阅读来差错,不愿意读的可以简略跳过,看说明就好

package learn;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.concurrent.locks.Condition;
import java.util.Scanner;
class PrintA implements Runnable{
	Condition a, b;
	Lock lock;
	int count = 10;
	PrintA(Lock lock, Condition a, Condition b){
		this.lock = lock;
		this.a = a;
		this.b = b;
	}
	public void run(){
		while(count > 0){
			lock.lock();
			try{
				a.await();
				count--;
				System.out.println("AAA");
				b.signal();
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}finally{
				lock.unlock();	
			}
		}
		
	}
}

class PrintB implements Runnable{
	Condition b, c;
	Lock lock;
	int count = 10;
	PrintB(Lock lock, Condition b, Condition c){
		this.b = b;
		this.c = c;
		this.lock = lock;
	}
	public void run(){
		while(count > 0){
			lock.lock();
			try{
				b.await();
				count--;
				System.out.println("BBB");
				c.signal();
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}finally{
				lock.unlock();
			}
		}
	}
}
class PrintC implements Runnable{
	Condition c, a;
	Lock lock;
	int count = 10;
	PrintC(Lock lock, Condition c, Condition a){
		this.c = c;
		this.a = a;
		this.lock = lock;
	}
	public void run(){
		while(count > 0){
			lock.lock();
			try{
				c.await();
				count--;
				System.out.println("CCC");
				a.signal();
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}finally{
				lock.unlock();
			}
		}
	}
}



class Go{
	public static void main(String args[]){
		Lock lock = new ReentrantLock();
		Condition a = lock.newCondition();
		Condition b = lock.newCondition();
		Condition c = lock.newCondition();
		Thread th1 = new Thread(new PrintA(lock, a, b));
		Thread th2 = new Thread(new PrintB(lock, b, c));
		Thread th3 = new Thread(new PrintC(lock, c, a));
		th1.start();
		th2.start();
		th3.start();
		Scanner s = new Scanner(System.in);
		int i = s.nextInt();
		if(i == 0){
			lock.lock();
			try{
				a.signal();
			}finally{
				lock.unlock();
			}
		}
	}
}


说明:以上代码运行的结果实际上是不可控的,(或多或少打印部分内容,然后发生死锁,程序不能正常退出),原因在于什么呢?
请看code中存在a.await(),b,await(),c.await(),想象一下,因为jvm对每个线程调度是随机的,那么如果在某次 x.signal()被调用的时候,x.await()线程还没被jvm所调度到,然后在x.await()被jvm调度到的时候,唯一可以唤醒它的x.signal()已经过去了,那么x.await()就醒不来了,一但jvm随机的发生对本例子的线程灾难性的调度顺序,那么就发生了deadlock。
怎么去避开这个问题呢,请记住一般x.await()要放在条件判断语句中,比如
while(somecondition),if(somecondition),因为signal就像一个流星,它对本进程的影响只在于它被调用的一瞬,因为调度问题如果没有被捕捉到,就永远的走了,所以signal线程还必须去修改一个somecondition,把signal曾经活过的信息保留下来,让await线程可以判断somecondition来决定是不是该await,从而避免了死锁,更细粒化的控制了线程的concurrent。
好了以下就是在x.await()的判断,来作为本文的对“AAABBBCCC....”打印的第三种正确实现。
package learn;
import java.util.concurrent.locks.ReentrantLock;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.Condition;
class ID{
	static int id = 1;
}

class PrintA implements Runnable{
	Lock lock;
	Condition a,b;
	int count = 10;
	PrintA(Lock lock, Condition a, Condition b){
		this.lock = lock;
		this.a = a;
		this.b = b;
	}
	public void run(){
		while(count > 0){
			try{
				lock.lock();
				while(ID.id != 1){
					try{
						a.await();
					}catch(InterruptedException e){
						throw new RuntimeException(e);
					}
				}
				count--;
				ID.id = 2;
				b.signal();
				System.out.println("AAA");
			}finally{
				lock.unlock();
			}
			
		}
	}
}

class PrintB implements Runnable{
	Lock lock;
	Condition b, c;
	int count = 10;
	PrintB(Lock lock, Condition b, Condition c){
		this.lock = lock;
		this.b = b;
		this.c = c;
	}
	public void run(){
		while(count > 0){
			try{
				lock.lock();
				while(ID.id != 2){
					try{
						b.await();
					}catch(InterruptedException e){
						throw new RuntimeException(e);
					}
				}
				count--;
				ID.id = 3;
				c.signal();
				System.out.println("BBB");
			}finally{
				lock.unlock();
			}
			
		}
	}
}

class PrintC implements Runnable{
	Lock lock;
	Condition c, a;
	int count = 10;
	PrintC(Lock lock, Condition c, Condition a){
		this.lock = lock;
		this.c = c;
		this.a = a;
	}
	public void run(){
		while(count > 0){
			try{
				lock.lock();
				while(ID.id != 3){
					try{
						c.await();
					}catch(InterruptedException e){
						throw new RuntimeException(e);
					}
				}
				count--;
				ID.id = 1;
				a.signal();
				System.out.println("CCC");
			}finally{
				lock.unlock();
			}
			
		}
	}
}



class Go{
	public static void main(String args[]){
		Lock lock = new ReentrantLock();
		Condition a = lock.newCondition();
		Condition b = lock.newCondition();
		Condition c = lock.newCondition();
		Thread th1 = new Thread(new PrintA(lock, a, b));
		Thread th2 = new Thread(new PrintB(lock, b, c));
		Thread th3 = new Thread(new PrintC(lock, c, a));
		th1.start();
		th2.start();
		th3.start();
	}
}


以下是第四种方法基于mutex的Semaphore,使用的是 java.util.concurrent.Semaphore API。
package learn;
import java.util.concurrent.Semaphore;


class PrintSome implements Runnable{
	Semaphore mutexNeeded, mutexToRelease;
	String str;
	PrintSome(Semaphore mutexNeeded, Semaphore mutexToRelease, String str){
		this.mutexNeeded = mutexNeeded;
		this.mutexToRelease = mutexToRelease;
		this.str = str;
	}
	public void run(){
		int count = 10;
		while(count-- > 0){
			try{
				mutexNeeded.acquire();
			}catch(InterruptedException e){
				throw new RuntimeException(e);
			}
			System.out.println(str);
			mutexToRelease.release();
		}
	}
}

class Go{
	public static void main(String args[]){
		Semaphore mutexA = new Semaphore(1), mutexB = new Semaphore(0), mutexC = new Semaphore(0);
		new Thread( new PrintSome(mutexA, mutexB, "AAA")).start();
		new Thread( new PrintSome(mutexB, mutexC, "BBB")).start();
		new Thread( new PrintSome(mutexC, mutexA, "CCC")).start();
	}
}


对于Semaphore和线程锁,个人的感觉是别混用,否则很容易出现死锁,原因是sem.acquire()会阻塞线程但是不释放线程锁,使其他sem.release()的线程得不到线程锁,从而发生死锁,个人觉得如果在确定使用Semaphore的java线程要实现“线程锁”的功能可以使用一个Semaphore mutex,mutex的值为0,1。
到此 这个问题应该是要截稿了。。好了 鞠躬 下台。。。

分享到:
评论

相关推荐

    java多线程进度条

    本主题将深入探讨如何在Java多线程环境下实现进度条功能。 首先,理解Java多线程的基本概念至关重要。Java通过Thread类和Runnable接口来支持多线程。创建一个新线程通常有两种方式:继承Thread类并重写run()方法,...

    java10个线程按照顺序打印1-100

    在Java编程中,多线程同步是一个常见的挑战,特别是在需要线程按照特定顺序执行任务时。本主题聚焦于如何使用Java实现10个线程按照顺序打印数字1到100。这种问题通常通过线程间通信和同步机制来解决,如`...

    java多线程和定时器学习

    此外,Java多线程中的同步机制也非常重要,包括`synchronized`关键字、`wait()`/`notify()`、`java.util.concurrent`包下的工具类等,它们用于控制线程间的协作和数据一致性。例如,`synchronized`可以保证同一时刻...

    java多线程应用实现方法

    Java多线程应用实现主要涉及两种方式:继承Thread类和实现Runnable接口。这两种方法都是为了在Java程序中创建并管理多个并发执行的任务,从而提高程序的执行效率。 1. 继承Thread类: 当一个类直接继承Thread类时,...

    实验八:Java多线程

    ### 实验八:Java多线程 #### 一、实验目的与知识点概述 在本实验中,我们将深入了解线程与进程的基本概念、它们之间的区别与联系,并掌握多线程技术在Java中的应用方法。具体包括以下几点: 1. **线程与进程的...

    多线程 打印1-99,100-199

    最后,我们将以上所有组件组合起来,形成完整的多线程打印程序。 ```java public class Main { public static void main(String[] args) { PrintController controller = new PrintController(); Test1 thread1 ...

    深入浅出Java多线程.doc

    本文将深入探讨Java多线程中的`join()`方法,以及它在实际开发中的应用。 `join()`方法是Java线程同步的一种机制,主要用于控制线程的执行顺序。在主线程中调用某个子线程的`join()`方法,主线程会等待该子线程执行...

    Java多线程机制(示例)

    ### Java多线程机制详解与示例 #### 一、Java多线程机制概述 Java中的多线程机制是程序设计中的一个重要概念,它允许在同一个应用程序中并发执行多个线程,有效地提高了程序的执行效率和响应速度。通过Java语言...

    java 多线程小例子 很方便 很实用 适合初学者

    ### Java多线程小例子详解 #### 知识点一:基本多线程示例 在给定的代码示例中,我们首先看到的是一个简单的Java多线程应用实例。这个例子展示了如何创建并启动一个新的线程。在`ThreadDemo`类的`main`方法中,...

    java多线程实例

    Java 多线程实例 Java 多线程是Java编程语言的一个重要特性,它使得程序能够在同一时间执行多个任务,从而提高系统效率和资源利用率。本文将深入探讨Java中的线程概念、创建线程的方式以及如何实现线程的并发执行。...

    java 多线程的一个例子java 多线程的一个例子

    Java多线程是Java编程中一个重要的概念,它允许程序同时执行多个任务,提高了程序的效率和响应速度。在Java中,可以通过继承...理解这个例子有助于深入理解Java多线程的概念,以及如何在实际项目中控制线程的执行流程。

    Java多线程实例 Java的小实验

    Java多线程是Java编程中一个...总的来说,Java多线程实验旨在让学生掌握如何在Java环境中创建和管理线程,理解线程的并发执行、资源竞争和同步控制。这个实验对于培养学生的编程技能和解决问题的能力有着积极的作用。

    Java多线程同步具体实例讲解 .doc

    总结起来,Java多线程同步是通过`synchronized`关键字实现的,它可以应用于方法或代码块,保证同一时刻只有一个线程能够执行特定的代码。通过合理使用同步机制,开发者可以有效地管理并发程序中的资源访问,避免数据...

    java多线程编程

    ### Java多线程编程知识点详解 #### 一、Java多线程编程的优越性 Java的多线程编程相比C或C++具有显著优势,主要归功于Java语言级的原生支持。Java的设计者们深刻理解到多线程对于现代软件开发的重要性,因此将...

    JAVA实现多线程并发机制

    在这个例子中,我们看到一个简单的Java多线程程序,用于模拟售票系统,其中有两个类`SellThread`和`AddThread`分别代表售票和加票的操作。 首先,我们来理解一下`SellThread`类。这个类实现了`Runnable`接口,这...

    Java多线程机制和线程之间的协作

    Java多线程机制是编程中一个重要的概念,它允许程序同时执行多个任务,提升程序的效率和响应性。在Java中,线程是程序执行的基本单元,比进程更细粒度,一个进程可以包含多个线程。每个线程有自己的生命周期,包括...

    Java实验9多线程设计.doc

    Java实验9主要关注多线程的设计与应用,涵盖了线程的基本概念、创建、管理与控制,以及线程同步和互斥。以下是对实验内容的详细解释: 1. **线程概念**: Java中的线程是程序执行的最小单位,一个进程中可以包含多...

    java的thread类重写run方法的双线程从1加到100

    在Java编程语言中,多线程是并发执行任务的关键特性,极大地提高了程序的效率和响应速度。本小练习主要关注如何通过继承`Thread`类来...这只是一个基础的示例,实际的多线程编程会涉及到更复杂的并发控制和同步策略。

    java多线程.pdf

    Java多线程技术是Java编程语言中重要的特性之一,它允许同时执行多个线程,能够提高程序的效率和性能。在Java中,线程可以由两种方式实现:继承Thread类和实现Runnable接口。 继承Thread类是实现线程的最简单方式,...

Global site tag (gtag.js) - Google Analytics