Java中多线程

主要内容

详细内容

概念

与进程的比较

(1)在操作系统中,所有运行中的任务通常对应一个进程,每个运行中程序就是一个进程。当一个程序运行时,内部可能包含了多个顺序执行流,每个顺序执行流就是一个线程;

(2)线程包括三个特性:独立性;动态性以及并发性。

(3)线程也被称作轻量级进程(Lightweight Process),线程是进程的执行单元;线程是独立运行的并且线程的执行是抢占式的;

(4)一个线程可以创建和撤销另一个线程,同一个进程中的多个线程之间可以并发执行;

(5)一个程序运行后至少有一个进程,一个进程里可以包含多个线程,但是至少包含一个线程。

多线程优缺点

(1)进程之间不可以共享内存,但线程之间易于共享内存;

(2)使用多线程来实现多任务并发比多进程的效率高;

(3)Java语言内置了多线程功能支持;

(4)线程太多会导致效率的降低,因为线程的执行依靠的是CPU的来回切换。

(5)多线程是指一个进程中有多个线程。

线程的创建和启动

继承Thread

线程是程序中的执行线程,Java虚拟机允许应用程序并发的运行多个执行线程;Thread类的语法格式如下所示:

public class Thread extends Object 
implements Runnable

通过继承Thread类创建并启动多线程的步骤有三步:

(1)定义Thread类的子类,并重写该类的run方法,该run方法的方法体就代表了线程需要完成的任务,run方法格式如下所示:

public void run()

(2)创建Thread子类的实例,即创建了线程对象;

(3)调用线程对象的start()方法来启动该线程;start方法格式如下所示:

public void start()

示例代码如下所示:

public class FirstThread extends Thread
{
	private int i;
	// 重写run方法,run方法的方法体就是线程执行体
	public void run()
	{
		for (; i < 100; i++)
		{
			//当线程类继承Thread类时,直接使用this即可获取当前线程,Thread对象的getName()返回当前线程的对象;
			//因此可以调用getName()直接返回当前线程的名字
			System.out.println(getName() + "" + i);
		}
	}

	public static void main(String[] args)
	{
		for (int i = 0; i < 100; i++)
		{
			System.out.println(Thread.currentThread().getName() + "" + i);
			if (i == 20)
			{
				// 创建并启动第一个线程
				new FirstThread().start();
				// 创建并启动第二个线程
				new FirstThread().start();
			}
		}
	}
}

以上程序中使用了Thread类中重要的两个方法:

public static Thread currentThread()

返回对当前正在执行的线程对象的引用;

public final String getName()

返回该线程的名称。

实现Runnable接口

Runnable接口应该有那些打算通过某一线程执行其实例的类来创建,类必须定义并实现run方法,Runnable接口语法格式如下所示:

public interface Runnable

通过实现Runnable接口创建并启动线程的步骤有三步,如下所示:

(1)定义Runnable接口的实现类,并重写该接口的run方法,该方法的方法体是线程的执行体;run方法的格式如下所示:

void run()

(2)创建Runnable实现类的实例,并以此实例创建Thread对象;

(3)调用线程对象的start方法来启动该线程;Java虚拟机调用线程的run方法;start方法的格式如下所示:

public void start()

示例代码如下所示:

public class SecondThread implements Runnable
{
	private int i;
	public void run()
	{
		for (; i < 100; i++)
		{
			//当线程类实现Runnable接口时,如果想要获取当前线程,只能使用Thread.currentThread()方法
			System.out.println(Thread.currentThread().getName() + " " + i);
		}
	}
	public static void main(String[] args)
	{
		for (int i = 0; i < 100; i++)
		{
			System.out.println(Thread.currentThread().getName() + "" + i);
			if (i == 20)
			{
				SecondThread st = new SecondThread();
				// 创建并启动第一个线程
				new Thread(st , "新线程1").start();
				new Thread(st , "新线程2").start();
			}
		}
	}
}

使用CallableFuture创建

Callable接口返回接口并且可能抛出异常的任务,实现者定义了一个不带任何参数的call方法;Callable接口语法格式如下所示:

public interface Callable<V>

Future接口表示异步计算的结果;Future接口语法格式如下所示:

public interface Future<V>

FutureTask类表示可取消的异步计算,利用开始和取消计算的方法、查询计算是否实现的方法和获取计算结果的方法,此类提供了对Future接口的基本实现。FutureTask类语法格式如下所示:

public class FutureTask<V>
extends Object
implements RunnableFuture<V>

使用CallableFuture创建并启动有返回值的步骤有四步,如下所示:

(1)创建Callable接口的实现类,并实现call方法,该call方法将作为线程的执行体,且该call方法有返回值;call方法的格式如下所示:

V call() throws Exception

(2)创建Callable实现类的实例,使用FutureTask类来包装Calllable对象,该FutureTask对象封装了该Callable对象的call()方法的返回值;

(3)使用FutureTask对象作为Thread对象的target创建并启动新线程;

(4)调用FutureTask对象的get方法来获得子线程执行结束后的返回值;get方法的格式如下所示:

V get(long timeout, TimeUnit unit)
throws InterruptedException,ExecutionException,TimeoutException

示例代码如下所示:

public class ThirdThread implements Callable<Integer>
{
	public static void main(String[] args)
	{
		// 创建Callable对象
		ThirdThread tt = new ThirdThread();
		// 使用FutureTask来包装Callable对象
		FutureTask<Integer> task = new FutureTask<Integer>(tt);
		for (int i = 0; i < 100; i++)
		{
			System.out.println(Thread.currentThread().getName() + " 的循环变量值为: " + i);
			if (i == 20)
			{
				// 实质是以Callable对象来创建并启动线程
				new Thread(task, "有返回值线程").start();
			}
		}
		try
		{
			// 获取线程返回值
			System.out.println("子线程的返回值: " + task.get());
		}
		catch (InterruptedException e)
		{
			e.printStackTrace();
		}
		catch (ExecutionException e)
		{
			e.printStackTrace();
		}
	}
	// 实现call方法,作为线程执行体
	public Integer call() throws Exception
	{
		int i = 0;
		for (; i < 100; i++)
		{
		System.out.println(Thread.currentThread().getName() + "的循环变量值为:" + i);
		}
		return i;
	}
}

三者的比较

(1)通过这三种方式都可以实现多线程,而通过实现接口的两种方式基本相同,只是Callable接口里定义的方法有返回值,可以声明抛出异常;

(2)使用实现RunnableCallable接口的方式创建多线程时,可以除实现这两个接口,还可以继承其他类;

更好的体现面向对象思想,但是如果需要访问当前线程,则必须使用Thread.currentThread()方法;

(3)使用继承Thread类创建多线程时,不可以再继承其它父类;编写简单,如果需要访问当前线程,不需要使用Thread.currentThread()方法,而是直接使用this关键字便可获得当前线程。

线程的生命周期

当线程被创建并启动时,便表示是开始了线程的生命周期,它要经过新建、就绪、运行、阻塞以及死亡5种状态,具体的生命周期如下图所示:

新建和就绪状态

当程序使用new关键字创建了一个线程以后,该线程处于新建状态;当线程对象调用了start方法后,该线程就是处于就绪状态,Java虚拟机会为其创建方法调用站和程序计数器,处于这个状态中的线程并没有开始运行,只是表示该线程可以运行了。

如果使用run方法,则run方法就会立即被执行,而且在run方法返回之前其他线程五福并发执行。

注意:startrun方法的区别?

(1)调用start方法可以启动线程,而run方法只是Thread类中的一个普通方法,调用run方法不能实现多线程;

(2)start方法:

a) Start方法用来启动线程,实现多线程运行,这时无需等待run方法体代码执行完毕而直接继续执行下面的代码;

b) 通过调用Thread类的start()方法来启动一个线程,这时此线程处于就绪(可运行)状态,并没有运行;

c) 一旦得到cpu的执行权,就开始执行run方法,这里run方法称为县城提,它包含了要执行的这个线程的内容,run方法运行结束,此线程随即终止;

(3)Run方法:

a) Run方法只是Thread类的一个普通方法,如果直接调用run方法,程序中依然只有主线程这个线程,其程序执行路径还是只有一条,要鞥带run方法体执行完毕后才可继续执行下面的代码,这样就没有达到多线程的目的。

运行和阻塞状态

当发生如下几种情况,线程将会进入阻塞状态:

(1)线程调用sleep方法主动放弃所占用的处理器资源;

(2)线程调用了一个阻塞式IO方法,在该方法返回之前,该线程被阻塞;

(3)线程试图获得一个同步监测器,但该同步监测器正被其它线程所持有;

(4)线程在等待某个通知(notify);

(5)程序调用了线程的suspend方法将线程挂起了。

当前正在执行的线程被阻塞之后,其他线程便可以获得执行的机会了;被阻塞的进程会在合适的时候重新进入到就绪状态,注意是就绪状态而不是运行状态,也就是说被阻塞线程的阻塞解除后,必须等待线程调度器再次调度它。

与以上五种情况相对应,当发生如下五种特定的情况将可以解除上面的阻塞,让该线程重新进入到就绪状态:

(1)调用sleep方法的线程经过了指定时间;

(2)线程调用的阻塞式IO方法已经返回;

(3)线程成功地获得了失去取得同步监视器;

(4)线程正在等待某个通知时,其他线程发出了一个通知;

(5)处于关起状态的线程被调用了resume恢复方法。

线程死亡

线程会以如下三种方式来结束,结束后便处于死亡状态。

(1)run()方法执行完成,线程正常结束;

(2)线程抛出一个未捕获的Exception或者Error

(3)直接调用该线程的stop方法来结束该线程——该方法容易导致死锁。

通过调用线程对象的isAlive方法,方法格式如下所示:

public final boolean isAlive()

可以判断线程是否处于活动状态,当线程处于就绪、运行、阻塞三种状态时,该方法将返回true,当线程处于新建、死亡两种状态时,该方法将返回false

控制线程方法

join线程

Thread类提供了join方法实现了让一个线程等待另一个线程完成的方法,join方法一共有三种重载的形式:

public final void join() throws InterruptedException

等待被join的线程执行完成;

public final void join(long millis) throws InterruptedException

等待被join的线程的时间最长为millis毫秒,如果在millis毫秒内,被join的线程还没有执行结束则不再等待;

public final void join(long millis, int nanos) throws InterruptedException

等待被join的线程的时间最长为millis毫秒加上nanos微秒(千分之一微秒)。

示例代码如下所示:

public class JoinThread extends Thread
{
	// 提供一个有参数的构造器,用于设置该线程的名字
	public JoinThread(String name)
	{
		super(name);
	}
	// 重写run方法,定义线程执行体
	public void run()
	{
		for (int i = 0; i < 100; i++)
		{
			System.out.println(getName() + "-->" + i);
		}
	}
	public static void main(String[] args) throws InterruptedException
	{
		// 启动子线程
		new JoinThread("新线程").start();
		for (int i = 0; i < 100; i++)
		{
			if (i == 20)
			{
				JoinThread jt = new JoinThread("被Join的线程");
				jt.start();
			// main线程调用了jt线程的join方法,main线程必须等jt执行结束才会向下执行
				jt.join();
			}
			System.out.println(Thread.currentThread().getName() + "-->" + i);
		}
	}
}

后台线程

当一个线程是在后台运行的,并且它的任务是为其他的线程提供服务,这种线程被称为“后台线程”,其特征是如果所有的前台线程都死亡,那么后台线程会自动死亡。

可以通过调用Thread对象的isDaemon方法来测试该线程是否为后台线程,其方法格式如下所示:

public final boolean isDaemon()

可以通过调用Thread对象的setDaemon()方法可将指定的线程设置为后台线程或取消;当正在运行的线程都是后台线程时,Java虚拟机退出。其方法格式如下所示:

public final void setDaemon(boolean on)

注意:当前台线程死亡后,JVM会通知后台线程死亡。将某个线程设置为后台线程,必须在该线程启动之前设置,也就是说setDaemon(true)方发必须在start()方法之前调用。否则将会引发IllegalThreadStateException异常。

 线程睡眠

如果需要将正在执行的线程暂停一段时间,并进入阻塞状态,则可以调用Thread对象的sleep方法,该方法有两种重载形式:

public static void sleep(long millis)  throws InterruptedException

让当前正在执行的线程暂停millis毫秒,并进入阻塞状态,该方法收到系统计时器和线程调度器的精度和准确度的影响;

public static void sleep(long millis,int nanos)
throws InterruptedException

让当前正在执行的线程暂停millis毫秒加nanos毫秒,并进入阻塞状态,该方法收到系统计时器和线程调度器的精度和准确度的影响;

示例代码如下所示:

public class TestSleep
{
	public static void main(String[] args) throws InterruptedException
	{
		for (int i = 0; i < 10; i++)
		{
			System.out.println("当前系统时间:" + new Date());
			//调用sleep方法让当前线程暂停1s
			Thread.sleep(1000);
		}
	}
}

注意:sleepwait方法的区别?

(1)这两个方法来自不同的类,sleep方法来自Thread类,而wait方法来自Object类;

(2)SleepThread类的静态类方法,水滴起哦用谁就去睡觉,即使在a线程里调用了bsleep方法,实际上还是a去睡觉,要让b线程睡觉就要在b的代码块中调用sleep

wait()Object类的非静态方法;

(3)Sleep释放资源不释放锁,而wait释放资源释放锁;

(4)使用范围:waitnotifynotifyAll只能在同步控制方法或者同步控制块里面使用,而sleep可以在任何地方使用。

线程让步

sleep方法相似,它可以让当前执行的线程暂停,并执行其它线程,但他不会阻塞给线程。其方法格式如下所示:

public static void yield()

示例代码如下所示:

public class TestYield extends Thread
{
	public TestYield()
	{}
	public TestYield(String name)
	{
		super(name);
	}
	//定义run方法作为线程执行体
	public void run()
	{
		for (int i = 0; i < 30; i++)
		{
			System.out.println(getName() + "-->" + i);
			//当i等于20时候,使用yield方法让当前线程让步
			if(i == 20)
			{
				Thread.yield();
			}
		}
	}
	public static void main(String[] args)
	{
		//启动两条并发线程
		TestYield ty1 = new TestYield("high");
		ty1.start();
		TestYield ty2 = new TestYield("low");
		ty2.start();
	}
}

注意:sleep方法和yield方法的区别如下所示:

(1)sleep方法暂停当前线程后,会给其他线程执行机会,不需要理会其他线程的优先级,但yield方法只会给优先级相同或者优先级更高的线程执行机会;

(2)Sleep方法将线程转入阻塞状态,直到经过阻塞时间才会转入就绪状态。而yield方法不会将线程转入阻塞状态,它只是强制当前线程进入就绪状态;

(3)Sleep方法声明抛出了IterruptedException异常,因此调用sleep方法时需要捕捉该异常或者抛出该异常。而yield方法则没有声明抛出任何异常。

(4)Sleep方法比yield方法有更好的可移植性,通常不要依靠yield来控制并发线程的执行。

线程优先级

Thread提供了setPriority()getPriority()来设置和返回指定线程的优先级,其中setPriority方法的参数可以是一个整数(010之间)也可以是Thread类的三个静态常量之一,这两个方法的格式以及三个静态常量如下所示:

public final void setPriority(int newPriority)
public final int getPriority()
public static final int MIN_PRIORITY	线程可以具有的最低优先级
public static final int NORM_PRIORITY	分配给线程的默认优先级
public static final int NORM_PRIORITY	线程可以具有的最高优先级

线程同步

线程安全问题

当多个线程使用同一个数据的时候,容易会出现线程安全问题。最经典的案例便是银行取钱问题,示例代码如下所示:

public class Account
{
	//封装账户编号、账户余额两个属性
	private String accountNo;
	private double balance;
	public Account(){}
	//有参构造器
	public Account(String accountNo, double balance)
	{
		super();
		this.accountNo = accountNo;
		this.balance = balance;
	}
	//重新些Account的hashCode和equals方法
	public int hashCode()
	{
		return accountNo.hashCode();
	}
	public boolean equals(Object obj)
	{
		if (obj !=null && obj.getClass() == Account.class)
		{
			Account target = (Account) obj;
			return target.getAccountNo().equals(accountNo);
		}
		return false;
	}
	//accountNo和balance两个属性的setter和getter方法
	......
}
public class DrawThread extends Thread
{
	//模拟用户账户
	private Account account;
	//当前取钱线程所取得的签署
	private double drawAmount;
	public DrawThread(String name,Account account,double drawAmount)
	{
		super(name);
		this.account = account;
		this.drawAmount = drawAmount;
	}
	public void run()
	{
		//当账户余额大于取钱数目
		if (account.getBalance() >= drawAmount)
		{
			System.out.println(getName() + "成功取出: " + drawAmount  + " 元!");
			//修改余额
			account.setBalance(account.getBalance() - drawAmount);
			System.out.println("账户余额为:" + account.getBalance() + " 元!");
		}
		else
		{
			System.out.println(getName() + "账户余额不足!");
		}
	}
}
public class TestDraw
{
	public static void main(String[] args)
	{
		//创建一个账户
		Account account = new Account("itheima",10000);
		new DrawThread("老方",account,8000).start();
		new DrawThread("老毕",account,6000).start();
	}
}

以上程序运行会出现如下情况:

由上图可以清晰的看出当多个线程共享同一个数据时,程序会运行出错!

synchronized

为了解决如上的问题,Java的多线程引入了同步监视器来解决这个问题,同步监视器分为同步代码块和同步方法两种。

同步的前提:

1)必须保证有两个以上线程;

2)必须是多个线程使用同一个锁,即多条语句在操作线程共享数据;

3)必须保证同步中只有一个线程在执行。

同步的好处和弊端:

1)好处:同步解决了多线程的安全问题;

2)弊端:多线程都是需要判断的,比较消耗资源。

(1)同步代码块

语法格式如下所示:

synchronized(obj)
{
		//代码
}

任何时刻只能有一条线程可以获得对同步监视器的锁定,当同步代码块执行结束后,该线程自然释放了对该同步监视器的锁定。

对上例修改代码如下所示:

public void run()
	{
		// 使用Account作为同步监视器,任何线程进入下面同步代码块之前都得鲜活的对Account账户的锁定,
		//这种做法符合:加锁-->修改完成-->释放锁的逻辑
		synchronized (account)
		{
			// 当账户余额大于取钱数目
			if (account.getBalance() >= drawAmount)
			{
				System.out.println(getName() + "成功取出: " + drawAmount + " 元!");
				// 修改余额
				account.setBalance(account.getBalance() - drawAmount);
				System.out.println("账户余额为:" + account.getBalance() + " 元!");
			}
			else
			{
				System.out.println(getName() + "账户余额不足!");
			}
		}
	}

(1)同步方法

使用synchronized关键字来修饰某一种方法,则该种方法称为同步方法。对于同步方法而言,无需显式指定同步监视器,同步方法的同步监视器是this,即该对象本身。

使用同步方法可以将某个类编程线程安全的类,线程安全的类具有以下特征:

1) 该类的对象可以被多个线程安全的访问;

2) 每个线程调用该对象的任意方法之后都将得到正确的结果;

3) 每个线程调用该对象的任意方法之后,该对象状态依然保持合理的状态。

对上例修改代码如下所示:

public class Account
{
	// 封装账户编号、账户余额两个属性
	private String accountNo;
	private double balance;
	public Account()
	{
	}
	// 有参构造器
	public Account(String accountNo, double balance)
	{
		super();
		this.accountNo = accountNo;
		this.balance = balance;
	}
	// balance属性的getter方法
	public double getBalance()
	{
		return this.balance;
	}
	// 提供一个线程安全draw方法来完成取钱操作
	public synchronized void draw(double drawAmount)
	{
		// 当账户余额大于取钱数目
		if (balance >= drawAmount)
		{
			System.out.println(Thread.currentThread().getName() + "成功取出: " + drawAmount + " 元!");
			try
			{
				Thread.sleep(1);
			}
			catch (InterruptedException e)
			{
				e.printStackTrace();
			}
			// 修改余额
			balance = balance - drawAmount;
			System.out.println("账户余额为:" + balance + " 元!");
		}
		else
		{
			System.out.println(Thread.currentThread().getName() + "账户余额不足!");
		}
	}

	// accountNo属性的setter和getter方法
    ......
	// 重新Account的hashCode和equals方法
	......
}
public class DrawThread extends Thread
{
	//模拟用户账户
	private Account account;
	private double drawAmount;
	public DrawThread(String name,Account account,double drawAmount)
	{
		super(name);
		this.account = account;
		this.drawAmount = drawAmount;
	}
	public void run()
	{
		//直接调用account对象draw方法执行取钱
		account.draw(drawAmount);
	}
}

程序运行结果如下所示:

在以下四种情况下线程会释放对同步监视器的锁定:

(1)当前线程的同步方法或者同步代码块执行结束,当前线程即释放同步监视器;

(2)当线程在同步代码块或者同步方法中遇到breakreturn终止了该代码块或者该方法的执行,当前线程将会释放同步监视器;

(3)当线程在同步代码块或者同步方法中出现未处理的Error或者Exception时,导致了该代码块或者该方法异常结束时后将会释放同步监视器;

(4)当线程执行同步代码块或者同步方法时,程序执行了同步监视器对象的wait方法,则当前线程暂停,并释放同步监视器;

在以下两种情况下线程不会释放同步监视器:

(1)线程执行同步代码块或者同步方法时,程序调用Thread.sleep()或者Thread.yield()方法来暂停当前线程的执行,当前线程不会释放同步监视器;

(2)线程执行同步代码块时,其他线程调用了该线程的suspend方法将该线程挂起,该线程不会释放同步监视器。

同步锁

JDK1.5之后,Java提供了另外一种线程同步机制:它通过显式定义同步锁对象来实现同步,同步锁应该使用Lock对象来充当。Lock接口的语法格式如下所示:

public interface Lock

Lock实现提供了比使用synchronized方法和语句可获得更广泛的锁定操作。通常,锁提供了对共享资源的独占访问,每次只能有一个线程对Lock对象加锁,线程开始访问共享资源先获得Lock对象。在实现线程安全的控制当中,通常使用ReentrantLock(可重入锁),该类的语法格式如下所示:

public class ReentrantLock
extends Object
implements Lock, Serializable

使用Lock对象可以显示加锁和释放锁,通常使用Lock对象的格式如下所示:

class X
{
	//定义锁对象
	private final ReentrantLock lock = new ReentrantLock();
	//定义需要保证线程安全的方法
	public void m()
	{
		//加锁
		lock.lock();
		try
		{
			//需要保证线程安全的代码
		}
		catch (Exception e)
		{
			// 需要处理的异常
		}
		//使用finally块来保证释放锁
		finally
		{
			lock.unlock();
		}
	}
}

将上述案例修改后代码如下所示:

public class Account
{
	//定义所对象
	private final ReentrantLock lock = new ReentrantLock();
	// 封装账户编号、账户余额两个属性
	......
	// 有参构造器
     ......
	// balance属性的getter方法
	......
	// 提供一个线程安全draw方法来完成取钱操作
	public  void draw(double drawAmount)
	{
		//对同步锁进行加锁
		lock.lock();
		try
		{
			// 当账户余额大于取钱数目
			if (balance >= drawAmount)
			{
				System.out.println(Thread.currentThread().getName() + "成功取出: " + drawAmount + " 元!");
				try
				{
					Thread.sleep(1);
				}
				catch (InterruptedException e)
				{
					e.printStackTrace();
				}
				// 修改余额
				balance = balance - drawAmount;
				System.out.println("账户余额为:" + balance + " 元!");
			}
			else
			{
				System.out.println(Thread.currentThread().getName() + "账户余额不足!");
			}
		}
		//使用finally块来保证释放锁
		finally
		{
			lock.unlock();
		}	
	}
	// accountNo属性的setter和getter方法
    ......
	// 重新Account的hashCode和equals方法
    .......
}

线程通信

传统的线程通信 

关于创建线程的三种方式请参阅2.2线程的创建与启动。

在定时器的应用中需要用到两个类,其中Timer类的语法格式如下所示:

public class Timer  extends Object

Timer类代表着一种工具,线程用其安排在后台线程中执行的任务,可安排任务执行一次,或这定义重复执行。

Timer类中关于schedule方法有三种重载方式:

public void schedule(TimerTask task,Date time) 表示安排在指定的时间指定指定的任务。如果此时间已经过去,则安排立即执行任务;
public void schedule(TimerTask task,long delay,long period)表示安排指定的任务从指定的延迟后开始进行重复的固定延迟执行;
public void schedule(TimerTask task,Date firstTime,long period)表示安排指定的任务在指定的时间开始进行重复的固定延迟执行。

TimerTask类表示由Timer安排为一次执行或者重复执行的任务。TimerTask类的语法格式如下所示:

public abstract class TimerTask
extends Object
implements Runnable  

在程序中需要创建类实现TimerTask接口,并且重写run方法,其中run方法格式如下所示:

public abstract void run():此计时器任务要执行的操作。

示例代码如下所示:

public class TraditionalTimerTest
{
	private static int count = 0;

	public static void main(String[] args)
	{
		class MyTimerTask extends TimerTask
		{
			@Override
			public void run()
			{
				count = (count + 1) % 2;
				System.out.println("bombing !");
				new Timer().schedule(new MyTimerTask(), 2000 + 2000 * count);
			}
		}
		new Timer().schedule(new MyTimerTask(), 2000);

		while (true)
		{
			System.out.println(new Date().getSeconds());
			try
			{
				Thread.sleep(1000);
			}
			catch (InterruptedException e)
			{
				e.printStackTrace();
			}
		}
	}
}

读写锁

读写锁维护了一对相关的锁,一个用于只读操作,一个用于写入操作。只要没有Writer,读取锁可以由多个Reader线程同时保持。而且写入锁是独占的。读写锁的语法格式如下所示:

public interface ReadWriteLock

在该接口中有两个重要的方法,如下所示:

Lock readLock() 表示返回用于读取操作的锁;
Lock writeLock() 表示用于写入操作的锁。

示例代码如下所示:

public class ReadWriteLockTest
{
	public static void main(String[] args)
	{
		final Queue3 q3 = new Queue3();
		for (int i = 0; i < 3; i++)
		{
			new Thread()
			{
				public void run()
				{
					while (true)
					{
						q3.get();
					}
				}
			}.start();

			new Thread()
			{
				public void run()
				{
					while (true)
					{
						q3.put(new Random().nextInt(10000));
					}
				}
			}.start();
		}
	}
}

class Queue3
{
	private Object data = null;// 共享数据,只能有一个线程能写该数据,但可以有多个线程同时读该数据。
	ReadWriteLock rwl = new ReentrantReadWriteLock();
	public void get()
	{
          //给读取操作的锁加锁
		rwl.readLock().lock();
		try
		{
			System.out.println(Thread.currentThread().getName() + " be ready to read data!");
			Thread.sleep((long) (Math.random() * 1000));
			System.out.println(Thread.currentThread().getName() + "have read data :" + data);
		}
		catch (InterruptedException e)
		{
			e.printStackTrace();
		}
		finally
		{
              //给读取操作的锁解除锁
			rwl.readLock().unlock();
		}
	}
	public void put(Object data)
	{
		//写锁
		rwl.writeLock().lock();
		try
		{
			System.out.println(Thread.currentThread().getName() + " be ready to write data!");
			Thread.sleep((long) (Math.random() * 1000));
			this.data = data;
			System.out.println(Thread.currentThread().getName() + " have write data: " + data);
		}
		catch (InterruptedException e)
		{
			e.printStackTrace();
		}
		finally
		{
			rwl.writeLock().unlock();
		}
	}
}

使用Condition控制

Condition将同步监视器方法分解成截然不同的对象,以便通过这些对象与Lock对象组合使用,为每个对象提供多个等待集。Condition接口的语法格式如下所示:

public interface Condition

Condition实例被绑定在一个Lock对象上。可以通过调用Lock对象的newCondition()方法即可。Condition类提供如下三个方法:

void await() throws InterruptedException
  表示当前线程等待,知道其他线程调用该Condition的signal()方法或者signalAll()方法来唤醒该线程。
void signal()
  表示在此Lock对象上等待的单个线程。
void signalAll()
  表示唤醒在此Lock对象上等待的所有线程。

示例代码如下所示:

public class ConditionCommunication
{
	public static void main(String[] args)
	{
		final Business business = new Business();
		new Thread(new Runnable()
		{
			@Override
			public void run()
			{
				for (int i = 0; i <= 50; i++)
				{
					business.sub(i);
				}
			}
		}).start();
		for (int i = 0; i <= 50; i++)
		{
			business.main(i);
		}
	}

	static class Business
	{
		private boolean bShouldSub = true;
		Lock lock = new ReentrantLock();
		Condition condition = lock.newCondition();

		public void sub(int i)
		{
			lock.lock();
			try
			{
				while (!bShouldSub)
				{
					try
					{
						condition.await();
					}
					catch (InterruptedException e)
					{
						e.printStackTrace();
					}

				}
				for (int j = 0; j <= 10; j++)
				{
					System.out.println("sub thread sequence of " + j + ",loop of " + i);
				}
				bShouldSub = false;
				// 通知主线程
				condition.signal();
			}
			finally
			{
				lock.unlock();
			}

		}

		public void main(int i)
		{
			lock.lock();
			try
			{
				while (bShouldSub)
				{
					try
					{
						condition.await();
					}
					catch (InterruptedException e)
					{
						e.printStackTrace();
					}

				}
				for (int j = 0; j <= 10; j++)
				{
					System.out.println("main thread sequence of " + j + ",loop of " + i);
				}
				bShouldSub = true;
				// 通知子线程
				condition.signal();
			}
			finally
			{
				lock.unlock();
			}
		}
	}
}

使用阻塞队列控制

BlockingQueue具有一个特征:当生产者线程试图向BlockingQueue中放入元素,如果该队列已经满了,则该线程被阻塞;当消费者线程试图同BlockingQueue中取出元素时候,如果该队列已经空了,则该线程被阻塞。BlockingQueue接口语法格式如下所示:

public interface BlockingQueue<E> extends Queue<E>

以下代码是基于典型的生产者-使用者场景的一个用力。注意,BlockingQueue可以安全地与多个生产者和多个使用者一起使用。

class Producer implements Runnable
{
	private final BlockingQueue queue;
	Producer(BlockingQueue q)
	{
		queue = q;
	}
	public void run() {
     try {
       while(true) { queue.put(produce()); }
     } catch (InterruptedException ex) { ... handle ...}
   }
	Object produce() { ... }
}

class Consumer implements Runnable
{
	private final BlockingQueue queue;

	Consumer(BlockingQueue q)
	{
		queue = q;
	}

	public void run() {
     try {
       while(true) { consume(queue.take()); }
     } catch (InterruptedException ex) { ... handle ...}
   }
	void consume(Object x) { ... }
}

class Setup
{
	void main()
	{
		BlockingQueue q = new SomeQueueImplementation();
		Producer p = new Producer(q);
		Consumer c1 = new Consumer(q);
		Consumer c2 = new Consumer(q);
		new Thread(p).start();
		new Thread(c1).start();
		new Thread(c2).start();
	}
}

从以上的代码中可以看出该接口提供两个支持阻塞的方法:

void put(E e) throws InterruptedException
  表示尝试把E元素放入BlockingQueue中,如果该队列的元素已满,则阻塞该线程;
E take() throws InterruptedException
  表示尝试从BlockingQueue的头部取出元素,如果该队列的元素已空,则阻塞该线程。

以下代表表示用3个空间的队列来演示阻塞队列的功能和效果。

public class BlockingQueueTest
{
	public static void main(String[] args)
	{
		final BlockingQueue queue = new ArrayBlockingQueue(3);
		for (int i = 0; i < 2; i++)
		{
			new Thread()
			{
				public void run()
				{
					while (true)
					{
						try
						{
							Thread.sleep((long) (Math.random() * 1000));
							System.out.println(Thread.currentThread().getName() + "准备放数据!");
							queue.put(1);
							System.out.println(Thread.currentThread().getName() + "已经放了数据," + "队列目前有" + queue.size()
									+ "个数据");
						}
						catch (InterruptedException e)
						{
							e.printStackTrace();
						}

					}
				}

			}.start();
		}
		new Thread()
		{
			public void run()
			{
				while (true)
				{
					try
					{
						// 将此处的睡眠时间分别改为100和1000,观察运行结果
						Thread.sleep(1000);
						System.out.println(Thread.currentThread().getName() + "准备取数据!");
						queue.take();
						System.out.println(Thread.currentThread().getName() + "已经取走数据," + "队列目前有" + queue.size()
								+ "个数据");
					}
					catch (InterruptedException e)
					{
						e.printStackTrace();
					}
				}
			}
		}.start();
	}
}

多线程的单例设计模式

保证某个类中只有一个对象。

(1)饿汉式:

class Single 
{
	//将构造函数私有化,不让别的类建立该类对象
	private Single(){}
	//自己建议一个对象
	private static final Single s = new Single();
	//提供一个公共访问方式
	public static Single getInstance()
	{
		return s;
	}
}

(2)懒汉式:

class Single 
{
	//将构造函数私有化,不让别的类建立该类对象
	private Single(){}
	//提供一个公共访问方式
	public static Single getInstance()
	{
		if (s == null)
		{
			s = new Single();
		}
		return s;
	}
}

(3)饿汉式和懒汉式之间的区别:

a) 懒汉式是类一加载进内存就创建好对象;而懒汉式则是在类加载进内存的时候,对象还没有存在,只是在调用了getInstance()方法时,对象才开始创建。

b) 懒汉式是延迟加载,如果多个线程同时操作懒汉式时就有可能出现线程安全问题,解决线程安全问题可以加同步来解决。但是加了同步之后,每一次都要比较锁,效率因此降低,所以可以加双重判断来提高程序的效率。

将上述懒汉式的getInstance函数改写成同步:

class Single 
{
	//将构造函数私有化,不让别的类建立该类对象
	private Single(){}
	//提供一个公共访问方式
	public static Single getInstance()
	{
		if (s == null)
		{
			synchronized(Single.class)
			{
				if (s == null)
				{
					s = new Single();
				}
			}
		}
		return s;
	}
}

线程池&线程相关类

Java5实现的线程池

当程序中需要创建大量生存期很短暂的线程是,应该使用线程池,这样可以很好的提高性能。除此之外,使用线程池可以有效的控制系统中并发线程的数量。在Java 5以后提供了Executors类产生线程池,Executors类的语法格式如下所示:

public class Executors extends Object

该工厂类包含了如下几个静态工厂方法来创建线程池:

public static ExecutorService newCachedThreadPool()
  表示创建一个具有缓存功能的线程池,系统根据需要创建线程,这些线程将会被缓存在线程池中;
public static ExecutorService newFixedThreadPool(int nThreads)
  表示创建一个可重用的、具有固定线程数的线程池。
public static ExecutorService newSingleThreadExecutor()
  表示创建一个只有单线程的线程池,相当于调用newFixedThreadPool(int nThreads)时nThreads的值为1;
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize)
  创建具有指定线程数的线程池,它可以在指定言辞后执行线程任务。corePoolSize指池中所保存的线程数,即使线程是空闲的也是保存在线程池内。
public static ScheduledExecutorService newSingleThreadScheduledExecutor()
  表示创建只有一个线程的线程池,它可以在指定延迟后执行线程任务。

使用线程池来执行线程任务的步骤如下所示:

(1)调用Executors类的静态工厂方法创建一个ExecutorService对象,该对象代表一个线程池。

(2)创建Runnable实现类或者Callable实现类的实例,作为线程执行任务。

(3)调用ExecutorService对象的submit方法来提交Runnable实例或者Callable实例;

(4)当不想提交任何任务时,调用ExecutorService对象所代表的任务。

以下程序代表使用线程池来执行指定Runnable对象所代表的任务。

//实现Runnable接口来定义一个简单的线程类
class MyThread implements Runnable
{
	public void run()
	{
		for (int i = 0; i < 100; i++)
		{
			System.out.println(Thread.currentThread().getName() + "的i值为:" + i);
		}
	}
}

public class ThreadPoolTest
{
	public static void main(String[] args) throws Exception
	{
		// 创建一个具有固定线程数的线程池
		ExecutorService pool = Executors.newFixedThreadPool(6);
		// 想线程池中提交两个线程
		pool.submit(new MyThread());
		pool.submit(new MyThread());
		// 关闭线程池
		pool.shutdown();
	}
}

以下代码实现固定大小的线程池和缓存线程池,实现步骤如下所示:

1)用3个大小的固定线程池去执行10个内部循环10次就结束的任务,打印出正在执行的线程名、任务序号以及任务内部的循环次数,这10个任务要用个子独立的Runnable对象,才能看到任务的序号;

2)该为缓存线程池,可以看到当前有多少个任务,就会分配多少个线程为之服务。

示例代码如下所示:

public class ThreadPoolTest
{
	public static void main(String[] args)
	{
		ExecutorService threadPool = Executors.newSingleThreadExecutor();
		for (int i = 0; i < 10; i++)
		{
			final int task = i;
			threadPool.execute(new Runnable()
			{
				@Override
				public void run()
				{
					for (int j = 0; j < 10; j++)
					{
						try
						{
							Thread.sleep(20);
						}
						catch (InterruptedException e)
						{
							e.printStackTrace();
						}
						System.out.println(Thread.currentThread().getName() + " is looping of " + j + " for task of "
								+ task);
					}

				}
			});
		}
		System.out.println("all of 3 tasks have committed!");
		Executors.newScheduledThreadPool(3).scheduleAtFixedRate(new Runnable()
		{
			@Override
			public void run()
			{
				System.out.println("bombing");
			}
		}, 6, 2, TimeUnit.SECONDS);
	}
}

Java7实现ForkJoinPool

为了充分利用利用多CPU的性能优势,可以考虑把一个任务拆分成多个“小任务”,然后将多个“小任务”放到多个处理器核心上并行执行。Java 7中提供了ForkJoin类实现此功能,ForkJoinPoolExecutorService类的实现类,具体语法格式如下所示:

public class ForkJoinPool
extends AbstractExecutorService

ForkJoin中提供了如下两个常用的构造器:

public ForkJoinPool(int parallelism)
  表示创建一个包含parallelism个并行线程的ForkJoinPool。
public ForkJoinPool()
  表示以Runtime.availableProcessors()方法的返回值作为parallelism参数来创建ForkJoinPool。

创建了ForkJoinPool实例之后,就可以调用ForkJoinPoolsubmit(ForkJoinTask<T> task)或者invoke(ForkJoinTask<T> task)方法来执行指定的任务。其中ForkJoinTask代表一个可以并行、合并的任务。

上图显示了ForkJoinPoolForkJoinTask等类的类图。

public class ForkJoinPoolTest
{
	public static void main(String[] args) throws InterruptedException
	{
		ForkJoinPool pool = new ForkJoinPool();
		// 提交可分解的PrintTask任务
		pool.submit(new PrintTask(0, 100));
		pool.awaitTermination(2, TimeUnit.SECONDS);
		// 关闭线程池
		pool.shutdown();
	}
}

class PrintTask extends RecursiveAction
{
	// 每个小任务最多打印50个数
	private static final int THRESHOLD = 50;
	private int start;
	private int end;

	// 打印从start到end的任务
	public PrintTask(int start, int end)
	{
		this.start = start;
		this.end = end;
	}

	@Override
	protected void compute()
	{
		// 当end与start之间的差小于THRESHOLD,开始 打印
		if (end - start < THRESHOLD)
		{
			for (int i = start; i < end; i++)
			{
				System.out.println(Thread.currentThread().getName() + " 的i值为:  " + i);
			}
		}
		else
		{
			// 当end与start之间的差大于THRESHOLD;即要打印的数超过50个时,将大任务分解成两个小任务
			int middle = (start + end) / 2;
			PrintTask left = new PrintTask(start, middle);
			PrintTask right = new PrintTask(middle, end);
			// 并行执行两个小任务
			left.fork();
			right.fork();
		}
	}
}

在本人计算机上运行结果如下所示:

上述程序定义的任务是一个没有返回值的打印任务,如果大任务有返回值的任务,可以让任务继承RecursiveTask<T>,其中个泛型参数T代表了该任务的返回值类型。

//继承RecursiveTask来实现可分解的任务
class CalTask extends RecursiveTask<Integer>
{
	//每个“小任务”最多只累加20个数
	private static final int THRESHOLD = 20;
	private int arr[];
	private int start;
	private int end;
	//累加从start到end的数组元素
	public CalTask(int []arr,int start,int end)
	{
		this.arr = arr;
		this.start = start;
		this.end = end;
	}
	@Override
	protected Integer compute()
	{
		int sum = 0;
		//当end 与start之间的差小于THRESHOLD时,开始进行实际累加
		if(end - start < THRESHOLD)
		{
			for(int i = start;i<end;i++)
			{
				sum += arr[i];
			}
			return sum;
		}
		else
		{
			// 当end与start之间的差大于THRESHOLD;即要打印的数超过20个时,将大任务分解成两个小任务
			int middle = (start + end) / 2;
			CalTask left = new CalTask(arr, start, middle);
			CalTask right = new CalTask(arr, middle, end);
			//并行执行两个小任务
			left.fork();
			right.fork();
			//把两个小任务类家的结果合并起来
			return left.join() + right.join();
		}
	}
	
}
public class Sum
{
	public static void main(String[] args) throws InterruptedException, ExecutionException
	{
		int[] arr = new int[100];
		Random rand = new Random();
		int total = 0;
		//初始化100个数字的元素
		for (int i = 0; i < arr.length; i++)
		{
			int tmp = rand.nextInt(20);
			//对数组元素赋值,并讲数组元素的值添加到total总和中
			total += (arr[i] = tmp);
		}
		System.out.println("total = " + total);
		ForkJoinPool pool = new ForkJoinPool();
		//提交可分解的CalTask任务
		Future<Integer> future = pool.submit(new CalTask(arr, 0, arr.length));
		System.out.println("future get = " + future.get());
		//关闭线程池
		pool.shutdown();
	}
}

以上程序运行时结果如下所示:

ThreadLocal

线程范围内共享数据的示意图如下所示:


以下代码可以解释线程范围内共享数据的概念。

public class ThreadScopeShareData
{
	//全局的静态变量
	private static int data = 0;
	private static Map<Thread,Integer> threadData = new HashMap<Thread,Integer>();
	public static void main(String[] args)
	{
		for (int i = 0; i < 2; i++)
		{
			new Thread(new Runnable()
			{
				@Override
				public void run()
				{
					//为data赋值
					int data = new Random().nextInt();
					System.out.println(Thread.currentThread().getName() + " has put data :" + data);
					//将data数据放入线程共享中
					threadData.put(Thread.currentThread(), data);
					//A、B模块获取共享数据
					new A().get();
					new B().get();
				}
			}).start();
		}
	}
	/**
	 * A模块
	 * */
	static class A
	{
		public void get()
		{
			int data = threadData.get(Thread.currentThread());
			System.out.println("A from " + Thread.currentThread().getName() + " has put data :" + data);
		}
	}
	/**
	 * B模块
	 * */
	static class B
	{
		public void get()
		{
			int data = threadData.get(Thread.currentThread());
			System.out.println("B from " + Thread.currentThread().getName() + " has put data :" + data);
		}
	}
}

运行结果如下所示:

public class ThreadLocal<T>
extends Object

ThreadLocal类是用于实现线程内的数据共享,即对于相同的程序代码,多个模块在同一个线程中运行时要共享一份数据,而在另外线程中运行时又共享另外一份数据。ThreadLocal类一共提供了以下三种方法:

public T get()表示返回此线程局部变量中当前线程副本中的值。
public void set(T value)表示删除此线程局部变量中当前线程的值。
public void set(T value)表示设置此线程局部变量中当前线程副本中的值。

ThreadLocal类的应用举例

public class ThreadLocalTest
{
	private static ThreadLocal<Integer> x = new ThreadLocal<Integer>();
	private static ThreadLocal<MyThreadScopeData> myThreadScopeData = new ThreadLocal<MyThreadScopeData>();
	public static void main(String[] args)
	{
		for (int i = 0; i < 2; i++)
		{
			new Thread(new Runnable()
			{
				@Override
				public void run()
				{
					int data = new Random().nextInt();
					System.out.println(Thread.currentThread().getName() + " has put data :" + data);
					x.set(data);
					MyThreadScopeData.getThreadInstance().setName("name" + data);
					MyThreadScopeData.getThreadInstance().setAge(data);
					//A、B模块获取共享数据
					new A().get();
					new B().get();
				}
			}).start();
		}
	}
	/**
	 * A模块
	 * */
	static class A
	{
		public void get()
		{
			int data = x.get();
			System.out.println("A from " + Thread.currentThread().getName() + " has put data :" + data);
			MyThreadScopeData myData = MyThreadScopeData.getThreadInstance();
			System.out.println("A from " + Thread.currentThread().getName() + " get MyData :" + myData.getName() + ","
					+ myData.getAge());
		}
	}
	/**
	 * B模块
	 * */
	static class B
	{
		public void get()
		{
			int data = x.get();
			System.out.println("B from " + Thread.currentThread().getName() + " has put data :" + data);
			MyThreadScopeData myData = MyThreadScopeData.getThreadInstance();
			System.out.println("B from " + Thread.currentThread().getName() + " get MyData :" + myData.getName() + ","
					+ myData.getAge());
		}
	}
}

class MyThreadScopeData
{
	private MyThreadScopeData()
	{
	}
	public static MyThreadScopeData getThreadInstance()
	{
		MyThreadScopeData instance = map.get();
		if (instance == null)
		{
			instance = new MyThreadScopeData();
			map.set(instance);
		}
		return instance;
	}

	private static MyThreadScopeData instance = null;
	private static ThreadLocal<MyThreadScopeData> map = new ThreadLocal<MyThreadScopeData>();
	private String name;
	private int age;
	//name属性的getter和setter
	......
	//age属性的getter和setter
     ......
}

运行结果如下所示:

总结:一个ThreadLocal代表一个变来那个,因此其中只能放一个数据。如果有两个变量都要线程范围内共享,则要定义两个ThreadLocal对象。但如果有一百个变量需要线程共享,则先定义一个对象来封装这一百个对象,然后在ThreadLocal中存储这个对象。

Semaphore实现信号灯

public class Semaphore 
extends Object
implements Serializable

Semaphore类代表一个计数信号量。通常用于限制可以访问某些资源(物理或逻辑的)的线程数目。例如,下面的类使用信号量控制对内容池的访问。

class Pool
{
	private static final int MAX_AVAILABLE = 100;
	private final Semaphore available = new Semaphore(MAX_AVAILABLE, true);

	public Object getItem() throws InterruptedException
	{
		available.acquire();
		return getNextAvailableItem();
	}

	public void putItem(Object x)
	{
		if (markAsUnused(x)) available.release();
	}

	// Not a particularly efficient data structure; just for demo
	protected Object[] items = ... whatever kinds of items being managed
	protected boolean[] used = new boolean[MAX_AVAILABLE];

	protected synchronized Object getNextAvailableItem()
	{
		for (int i = 0; i < MAX_AVAILABLE; ++i)
		{
			if (!used[i])
			{
				used[i] = true;
				return items[i];
			}
		}
		return null; // not reached
	}

	protected synchronized boolean markAsUnused(Object item)
	{
		for (int i = 0; i < MAX_AVAILABLE; ++i)
		{
			if (item == items[i])
			{
				if (used[i])
				{
					used[i] = false;
					return true;
				}
				else return false;
			}
		}
		return false;
	}
}

Semaphore类有两个构造函数,格式如下所示:

public Semaphore(int permits)
  表示创建具有给定的许可数和非公平设置的Semaphore。
public Semaphore(int permits,boolean fair)
  表示创建具有给定的许可数和给定的公平设置的Semaphore。

该类有两个重要的方法,如下所示:

public void acquire(int permits) throws InterruptedException
  表示从此信号量获取给定数目的认可,在提供这些许可前一直将线程阻塞,或者线程已经被中断。获取给定数目的许可之后立即返回,将可用的许可数减去给定的量。
public void release(int permits)
  表示释放给定数目的许可,将其返回到信号量。

以下程序表示创建5个线程,使用Semaphore来控制可以访问的资源的线程个数为3个。

public class SemaphoreTest//信号灯测试
{
	public static void main(String[] args)
	{
		ExecutorService service = Executors.newCachedThreadPool();
		final Semaphore sp = new Semaphore(3);
		for (int i = 0; i < 5; i++)
		{
			Runnable runnable = new Runnable()
			{
				public void run()
				{
					try
					{
						sp.acquire();
					}
					catch (InterruptedException e1)
					{
						e1.printStackTrace();
					}
					System.out.println("线程" + Thread.currentThread().getName() + "进入,当前已有"
							+ (3 - sp.availablePermits()) + "个并发");
					try
					{
						Thread.sleep((long) (Math.random() * 10000));
					}
					catch (InterruptedException e)
					{
						e.printStackTrace();
					}
					System.out.println("线程" + Thread.currentThread().getName() + "即将离开");
					sp.release();
					// 下面代码有时候执行不准确,因为其没有和上面的代码合成原子单元
					System.out.println("线程" + Thread.currentThread().getName() + "已离开,当前已有"
							+ (3 - sp.availablePermits()) + "个并发");
				}
			};
			service.execute(runnable);
		}
	}
}

运行的结果如下所示:


同步集合

Java5中提供了如下一些同步集合类:部分类如下图所示:

其余请查阅文档。

其它同步工具类

(1)CyclicBarrier

public class CyclicBarrier
extends Object

表示大家彼此等待,大家集合好后才开始出发。分散活动后又在指定的地点集合碰面。

以下程序表示用三个线程来实现以上示例:

public class CyclicBarrierTest
{
	public static void main(String[] args)
	{
		ExecutorService service = Executors.newCachedThreadPool();
		final CyclicBarrier cb = new CyclicBarrier(3);
		for (int i = 0; i < 3; i++)
		{
			Runnable runnable = new Runnable()
			{
				public void run()
				{
					try
					{
						Thread.sleep((long) (Math.random() * 10000));
						System.out.println("线程" + Thread.currentThread().getName() + "即将到达集合地点1,当前已有"
								+ (cb.getNumberWaiting() + 1) + "个已经到达,"
								+ (cb.getNumberWaiting() == 2 ? "都到齐了,继续走啊" : "正在等候"));
						cb.await();

						Thread.sleep((long) (Math.random() * 10000));
						System.out.println("线程" + Thread.currentThread().getName() + "即将到达集合地点2,当前已有"
								+ (cb.getNumberWaiting() + 1) + "个已经到达,"
								+ (cb.getNumberWaiting() == 2 ? "都到齐了,继续走啊" : "正在等候"));
						cb.await();
						Thread.sleep((long) (Math.random() * 10000));
						System.out.println("线程" + Thread.currentThread().getName() + "即将到达集合地点3,当前已有"
								+ (cb.getNumberWaiting() + 1) + "个已经到达,"
								+ (cb.getNumberWaiting() == 2 ? "都到齐了,继续走啊" : "正在等候"));
						cb.await();
					}
					catch (Exception e)
					{
						e.printStackTrace();
					}
				}
			};
			service.execute(runnable);
		}
		service.shutdown();
	}
}

运行结果如下所示:

(2)CountDownLatch

public class CountDownLatch extends Object

犹如倒计时计数器,调用CountDownLatch对象的countDown方法就将计数器减1,当计数到达0时,则所有等待者或单个等待者开始执行。

以下代表实现了如下功能:实现一个人(也可以是多个人)等待其他所有人都来通知他,可以实现一个人通知多个人的效果。具体代码如下所示:

public class CountdownLatchTest
{
	public static void main(String[] args)
	{
		ExecutorService service = Executors.newCachedThreadPool();
		final CountDownLatch cdOrder = new CountDownLatch(1);
		final CountDownLatch cdAnswer = new CountDownLatch(3);
		for (int i = 0; i < 3; i++)
		{
			Runnable runnable = new Runnable()
			{
				public void run()
				{
					try
					{
						System.out.println("线程" + Thread.currentThread().getName() + "正准备接受命令");
						cdOrder.await();
						System.out.println("线程" + Thread.currentThread().getName() + "已接受命令");
						Thread.sleep((long) (Math.random() * 10000));
						System.out.println("线程" + Thread.currentThread().getName() + "回应命令处理结果");
						cdAnswer.countDown();
					}
					catch (Exception e)
					{
						e.printStackTrace();
					}
				}
			};
			service.execute(runnable);
		}
		try
		{
			Thread.sleep((long) (Math.random() * 10000));

			System.out.println("线程" + Thread.currentThread().getName() + "即将发布命令");
			cdOrder.countDown();
			System.out.println("线程" + Thread.currentThread().getName() + "已发送命令,正在等待结果");
			cdAnswer.await();
			System.out.println("线程" + Thread.currentThread().getName() + "已收到所有响应结果");
		}
		catch (Exception e)
		{
			e.printStackTrace();
		}
		service.shutdown();
	}
}

运行结果如下所示:

(3)Exchange

public class Exchanger<V>extends Object

表示用于实现两个人之间的数据交换,每个人在完成一定的事务后想与对方交换数据,第一个先拿出数据的人将一直等待第二个人拿着数据到来时,才能彼此交换数据。

public class ExchangerTest
{
	public static void main(String[] args)
	{
		ExecutorService service = Executors.newCachedThreadPool();
		final Exchanger exchanger = new Exchanger();
		service.execute(new Runnable()
		{
			public void run()
			{
				try
				{
					String data1 = "zxx";
					System.out.println("线程" + Thread.currentThread().getName() + "正在把数据" + data1 + "换出去");
					Thread.sleep((long) (Math.random() * 10000));
					String data2 = (String) exchanger.exchange(data1);
					System.out.println("线程" + Thread.currentThread().getName() + "换回的数据为" + data2);
				}
				catch (Exception e)
				{
					e.printStackTrace();
				}
			}
		});
		service.execute(new Runnable()
		{
			public void run()
			{
				try
				{
					String data1 = "lhm";
					System.out.println("线程" + Thread.currentThread().getName() + "正在把数据" + data1 + "换出去");
					Thread.sleep((long) (Math.random() * 10000));
					String data2 = (String) exchanger.exchange(data1);
					System.out.println("线程" + Thread.currentThread().getName() + "换回的数据为" + data2);
				}
				catch (Exception e)
				{
					e.printStackTrace();
				}
			}
		});
	}
}

运行结果如下所示:

### 回答1: Java 多线程可以通过创建 Thread 类的实例来实现。具体步骤如下: 1. 创建一个实现 Runnable 接口的类,该类实现 run() 方法。 ```java public class MyRunnable implements Runnable { public void run() { // 执行线程的操作 } } ``` 2. 创建一个 Thread 实例,并将实现了 Runnable 接口的类作为构造函数的参数。 ```java MyRunnable myRunnable = new MyRunnable(); Thread thread = new Thread(myRunnable); ``` 3. 启动线程。 ```java thread.start(); ``` 此外,还可以通过继承 Thread 类来实现多线程。具体步骤如下: 1. 创建一个继承自 Thread 类的类,重写 run() 方法。 ```java public class MyThread extends Thread { public void run() { // 执行线程的操作 } } ``` 2. 创建 MyThread 的实例。 ```java MyThread myThread = new MyThread(); ``` 3. 启动线程。 ```java myThread.start(); ``` 在多线程的使用,需要注意线程安全的问题,比如共享变量的访问、同步操作等。可以使用 synchronized 关键字或者 Lock 接口来保证线程安全。 ### 回答2: 在Java多线程的使用是指程序同时运行多个线程,每个线程执行自己的任务。Java多线程的使用可以带来以下几点好处: 1. 提高程序的效率:多线程可以对多个任务进行并发处理,提高程序的运行效率。例如,可以将网络请求和UI界面分别放在两个不同的线程,这样即使网络请求比较耗时,UI界面也能进行响应,不会出现界面假死的情况。 2. 充分利用系统资源:多线程可以充分利用系统的处理器资源,提高系统的利用率。在多核处理器上运行多个线程,可以让每个核心都得到充分利用,提高系统的整体性能。 3. 实现异步编程:多线程可以实现异步编程,即一个线程执行后续操作,不需要等待另一个线程的完成。这样可以提高程序的响应速度。例如,可以使用多线程来进行文件下载,下载过程可以同时进行其他操作。 4. 处理复杂的并发情况:在一些需要处理多个并发操作的场景多线程可以提供更好的解决方案。例如,在并发访问共享资源的情况下,使用线程锁可以保证对共享资源的安全访问,避免数据冲突和一致性问题。 Java使用多线程可以通过创建Thread类的实例或者实现Runnable接口来实现。通过继承Thread类来创建线程,需要重写run方法,在run方法定义线程要执行的任务。通过实现Runnable接口来创建线程,需要实现run方法,并将实现了Runnable接口的对象作为参数传递给Thread类的构造方法。 总之,Java多线程的使用使得程序可以同时执行多个任务,提高了程序的效率和用户体验,并且能够处理复杂的并发情况。但需要注意多线程的安全性和线程之间的协作,避免出现数据冲突和一致性问题。 ### 回答3: Java多线程的使用是指在一个程序同时执行多个任务或者同时处理多个请求。多线程可以提高程序的并发性和响应性,可以将耗时的操作和任务分配给不同的线程来执行,从而提高程序的运行效率。 在Java多线程的使用主要依靠Thread类或者实现Runnable接口来创建线程。可以通过继承Thread类创建一个线程类,并重写run方法,在run方法定义需要执行的任务;也可以实现Runnable接口,创建一个Runnable对象,然后将该对象作为参数传递给Thread类的构造方法,创建一个线程对象。 使用多线程的好处是可以充分利用处理器的多核特性,同时进行多个任务,提高程序的运行效率。多线程还可以提高程序的响应性,当程序有耗时的操作时,可以将其放在一个独立的线程执行,防止主线程被阻塞,提高用户体验。 然而,多线程的使用也存在一些问题。首先是线程安全问题,多个线程同时访问共享资源可能导致数据不一致或者数据损坏。为解决这个问题,可以使用同步机制,如synchronized关键字或Lock接口,保证在同一时间只有一个线程能够访问共享资源。其次,多线程的创建和销毁会消耗系统资源,如果线程数量过多,可能会影响系统性能。因此,在使用多线程时应该合理控制线程的数量。另外,线程之间的协调和通信也是一个值得关注的问题,可以使用wait、notify、join等方法来实现线程间的协作。 总之,Java多线程的使用可以提高程序的并发性和响应性,但需要注意线程安全、资源消耗以及线程协调和通信等问题。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值