疯狂Java讲义_Chapter16多线程

本文详细介绍了Java多线程的概念,包括线程和进程的区别、多线程的优势以及线程的生命周期。阐述了线程的创建和启动方式,如继承Thread类、实现Runnable接口以及使用Callable和Future。讨论了线程的同步、线程通信和线程池的使用,包括死锁处理、线程安全问题和线程状态控制。此外,还提到了线程组、异常处理以及线程池的管理。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Java多线程

1.线程概述

1.线程和进程

  • 在一个系统中,每个独立运行的程序是一个进程;每个程序中有多个顺序流,称为线程;

  • 进程的特征:

  1. 独立性:每个进程都有着自己独立的地址空间,互不影响;

  2. 动态性;

  3. 并发性:多个进程可以在单个处理器上并发执行,且互不影响;

  • 并发性:同一时刻只能有一条指令执行,但是多个进程指令被快速轮换执行,在宏观上看起来是一起执行的效果;
  • 并行性:同一时刻,多条指令在多个处理器上同时执行;
  • Windows和Linux都是抢占式的多任务操作策略;
  • 线程又被称为是轻量级进程,每个线程是独立运行的;一个进程至少包括一个线程;

2.多线程的优势

  • 线程之间共享内存、文件句柄和状态信息;这也是线程执行速度高于进程的原因;
  • 多线程的应用:
  1. javaWeb服务器响应多个用户需求;
  2. JVM虚拟机的垃圾回收机制;

2.线程的创建和启动

1.继承Thread类创建线程类

  • 所有的线程对象都必须是Thread类或其子类的实例;每个线程的任务就是执行一段程序流;
  • 创建并启动线程的步骤如下:
/*
1.定义Thread类的子类,重写Thread的run方法,run就是线程的执行体;
2.创建Thread子类的实例,即创建了线程对象;
3.调用线程对象的start()方法来启动线程;
 */

//继承Thread
public class FirstThread extends Thread
{
	private int i;
	// 重写run
    @Override
	public void run()
	{
		for ( ; i < 100; i++)
		{
			// 直接调用getName方法可以返回当前线程的名字
			System.out.println(getName() + " " + i);
		}
	}
	public static void main(String[] args)
	{
        //这是主线程
		for (var i = 0; i < 100; i++)
		{
			// 调用Thread的currentThread方法获得当前线程;
			System.out.println(Thread.currentThread().getName()
				+ " " + i);
			if (i == 20)
			{
				// 创建并启用第一个线程
				new FirstThread().start();
				// 创建并启用第二个线程
				new FirstThread().start();
			}
		}
	}
}
  • main方法体就是主线程的方法体;

2.实现Runable接口创建线程类

  • 步骤如下

/*
使用Runable接口创建线程
1.定义Runable接口的实现类,并重写run方法,run是线程的实现体;
2.创建实现类的实例,并以此实例作为Thread的target来创建Thread对象,该对象为线程对象;
3.调用线程对象的start方法启动线程;
 */



//实现Runable接口
public class SecondThread implements Runnable
{
	private int i;
	//重写run方法
	public void run()
	{
		for ( ; i < 100; i++)
		{
			//如果想获得线程名,只能通过Thread.currentThread()方法
			System.out.println(Thread.currentThread().getName()
				+ " " + i);
		}
	}

	public static void main(String[] args)
	{
		for (var i = 0; i < 100; i++)
		{
			System.out.println(Thread.currentThread().getName() //主线程
				+ " " + i);
			if (i == 20)
			{
				var st = new SecondThread();     //创建线程实例;
				//用实例初始化Thread实例,并启动线程;
				new Thread(st, "新线程1").start();
				new Thread(st, "新线程2").start();
			}
		}
	}
}

 

3.使用Callable和Future创建线程(739)(这个方法有点复杂)

  • Thread将run方法作为线程执行体,但是不能将其他方法作为线程执行体;(C#可以)

  • Callable接口是Runable的增强版,提供了一个较强的call方法代替了run方法;

4.创建线程的三种方式对比

  • 由于扩展性原因,一般推荐采用Runable接口实现线程的编写;

3.线程的生命周期

1.新建和就绪状态

  • 线程的生命周期中,包括新建New,就绪Ready,运行Running,阻塞Blocked、死亡Dead共5个状态;
  • new一个线程对象,就进入了线程的新建状态;
  • 线程对象调用start方法,就进入了就绪状态;处于这个状态的线程并没有开始运行,只是表示该线程可以运行了,何时开始运行取决于JVM线程调度器的调度;
  • run方法不能直接执行,启动线程只能是start方法;
  • 如果希望子线程点击start方法后立即执行,程序可以使用Thread.sleep(1)来让当前运行的线程睡眠1ms,此时CPU就会去执行另一个已经就绪的线程;

2.运行和阻塞状态

  • 线程调度平台会定期的让一些运行的线程进入阻塞状态,让其他线程有机会运行;
  • Windows和Linux都是抢占式调度方法;而手机系统采用协作式调度方法,即由当前运行线程自动调用sleep或者yield方法进入阻塞状态;
  • 当发生如下状态时,系统进入阻塞状态:
  1. 线程调用sleep方法主动放弃占用线程资源;
  2. 线程调用了一个阻塞式IO方法,在该方法返回之前,该线程被进程阻塞;
  3. 线程试图获得一个同步监视器;
  4. 线程在等待某个通知notify;
  5. 程序调用了线程的suspend方法将该线程挂起(此方法容易导致死锁)
  • 被阻塞的线程会在解除阻塞后重新进入就绪状态;

3.线程死亡

  • 线程会以如下三种方式结束,并进入死亡状态:
  1. run或者call方法执行完成,线程正常结束;
  2. 线程抛出一个未捕获的Exception或Error;
  3. 直接调用stop方法停止线程;
  • 主线程和子线程是平级关系,主线程死亡不会影响子线程;
  • 为了验证线程是否死亡,可以调用isAlive()方法来验证;
  • 已经死亡的线程不能被再次start;

 

4.控制线程
1.join线程

  • join是让一个线程等待另一个线程的方法,当某个程序执行流过程中调用其他线程的join方法时,该线程将被阻塞,直到被join方法加入的join线程执行完为止;

/*
join方法有如下三种重载形式
1.join():等待被join的线程执行完成;
2.join(long millis):等待被join的线程的执行时间最长为millis毫秒;如果在给定时间内被join的线程还没有执行结束,则不再等待;
3.join(long millis, int nanos):等待被join的线程的时间最长为millis+nanos毫微秒;
 */


public class JoinThread extends Thread
{
	// 该构造器用于设置线程名;Thread的有参构造器用于给线程命名
	public JoinThread(String name)
	{
		super(name);
	}
	//重写run方法
	public void run()
	{
		for (var i = 0; i < 100; i++)
		{
			System.out.println(getName() + "  " + i);
		}
	}

	public static void main(String[] args) throws Exception
	{
		// 启动线程
		new JoinThread("新线程").start();
		for (var i = 0; i < 100; i++)
		{
			if (i == 20)
			{
				var jt = new JoinThread("被join的线程");
				jt.start();
				// main线程调用了jt线程的join方法,必须等到jt结束才会向下执行;
				jt.join();
			}
			System.out.println(Thread.currentThread().getName()
				+ "  " + i);
		}
	}
}

 

2.后台线程

  • 后台线程Daemon Thread:为其他线程提供服务,又被称为是守护线程、精灵线程;典型的就是JVM垃圾回收;
  • 如果所有的前台线程都死亡,后台线程会自动死亡;
  • 调用Thread对象的setDaemon(true)方法可以将指定的线程设置为后台线程:

public class DaemonThread extends Thread
{
	public void run()
	{
		for (var i = 0; i < 1000; i++)
		{
			System.out.println(getName() + "  " + i);
		}
	}
	public static void main(String[] args)
	{
		var t = new DaemonThread();
		// 设置为守护进程
		t.setDaemon(true);
		t.start();
		for (var i = 0; i < 10; i++)
		{
			System.out.println(Thread.currentThread().getName()
				+ "  " + i);
		}
	}
}
  • Thread类提供了isDaemon方法,用于判定线程是否为后台线程;

 

3.睡眠线程sleep

  • Thread类的sleep方法可以让当前正在执行的线程暂停一段时间,并进入阻塞状态;
  • sleep的两种重载形式
  1. static void sleep(long millis):让当前正在执行的线程暂停millis毫秒;
  2. static void sleep(long millis, long nanos)
  • sleep常用于暂停线程的执行,即使内存中没有其他线程执行,sleep的线程也不会执行;

import java.util.*;

public class SleepTest
{
	public static void main(String[] args)
		throws Exception
	{
		for (var i = 0; i < 10; i++)
		{
			System.out.println("时间为 " + new Date());
			// 暂停主线程
			Thread.sleep(1000);
		}
	}
}

  • yield方法可以将当前正在执行的线程暂停,但是不会阻塞该线程,它只是将线程转入就绪状态; 

4.改变线程优先级

  • Thread类提供了setPriority(int newPriority)、getPriority()方法来设置和返回指定线程的优先级;
  • Thread类优先级有关的三个静态常量:
  1. MAX_PRIORITY:10
  2. MIN_PRIORITY:1
  3. NORM_PRIORITY:5

public class PriorityTest extends Thread
{
	// 给定线程名称
	public PriorityTest(String name)
	{
		super(name);
	}

	public void run()
	{
		for (var i = 0; i < 50; i++)
		{
			System.out.println(getName() + ",其优先级为:"
				+ getPriority() + ", 循环变量的值为:" + i);
		}
	}
	public static void main(String[] args)
	{
		// 设置主线程优先级为6;
		Thread.currentThread().setPriority(6);
		for (var i = 0; i < 30; i++)
		{
			if (i == 10)
			{
				var low = new PriorityTest("低级");
				low.start();
				System.out.println("创建之初的优先级:"
					+ low.getPriority());
				// 设置low的优先级为最低;
				low.setPriority(Thread.MIN_PRIORITY);
			}
			if (i == 20)
			{
				var high = new PriorityTest("高级");
				high.start();
				System.out.println("创建之初的优先级为:"
					+ high.getPriority());
				// 设置high的优先级
				high.setPriority(Thread.MAX_PRIORITY);
			}
		}
	}
}

 

5.线程同步

1.线程安全问题

  • 定义一个账户取钱程序

 


public class Account
{
    //封装账户编号、账户余额的两个成员变量
	private String accountNo;
	private double balance;
	public Account(){}
	// 给定构造体,初始化账户名和余额;
	public Account(String accountNo, double balance)
	{
		this.accountNo = accountNo;
		this.balance = balance;
	}

	public void setAccountNo(String accountNo)
	{
		this.accountNo = accountNo;
	}
	public String getAccountNo()
	{
		return this.accountNo;
	}
	public void setBalance(double balance)
	{
		this.balance = balance;
	}
	public double getBalance()
	{
		return this.balance;
	}

	// 重写hashcode和equals方法
	public int hashCode()
	{
		return accountNo.hashCode();
	}
	public boolean equals(Object obj)
	{
		if (this == obj)
			return true;
		if (obj != null
			&& obj.getClass() == Account.class)
		{
			var target = (Account) obj;
			return target.getAccountNo().equals(accountNo);
		}
		return false;
	}
}
  • 提供一个取钱的线程类

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);
			try
			{
				Thread.sleep(1);
			}
			catch (InterruptedException ex)
			{
				ex.printStackTrace();
			}
			account.setBalance(account.getBalance() - drawAmount);
			System.out.println("剩余余额为: " + account.getBalance());
		}
		else
		{
			System.out.println(getName() + "取钱失败余额不足");
		}
	}
}
  • 启动两个取钱线程

public class DrawTest
{
	public static void main(String[] args)
	{
		// 创建一个Account实例
		var acct = new Account("1234567", 1000);
		//设置两个取钱线程
		new DrawThread("甲", acct, 800).start();
		new DrawThread("乙", acct, 800).start();
	}
}

2.同步代码块

  • 当两个线程访问和修改同一文件时,就有可能导致线程执行错误;
  • 为了解决这个问题,Java引入了同步监视器来解决这个问题,使用同步监视器的通用代码方法就是同步代码块,语法格式为:
//线程开始执行同步代码块之前,必须先获得同步监视器的锁定
synchronized (obj)
{
   ...
}
  •  同步监视器的目的就是阻止两个线程对同一共享资源进行并发访问;
  • 对于上面的取钱程序,可以改为:

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()
	{
		synchronized (account)  //使用account作为同步监视器(因为它是共享资源),任何线程进入下面的同步代码之前,必须先获得对account账户的锁定——其他线程无法获得锁,也就无法修复它;
                //这种做法符合“加锁——修改——释放锁”的逻辑;
		{
			if (account.getBalance() >= drawAmount)
			{
				System.out.println(getName()
					+ "取钱额度为" + drawAmount);
				try //这步是为了测试有没有抢占发生
				{
					Thread.sleep(1);
				}
				catch (InterruptedException ex)
				{
					ex.printStackTrace();
				}
				//设定余额
				account.setBalance(account.getBalance() - drawAmount);
				System.out.println("余额为: " + account.getBalance());
			}
			else
			{
				System.out.println(getName() + "额度不足");
			}
		}
	}
}

 

3.同步方法

  • 与同步代码块对应,Java的多线程安全支持还提供了同步方法,同步方法的同步监视器是this,也就是调用该方法的对象;

  • 通过使用同步方法可以非常方便的实现线程安全的类,线程安全的类具有如下特征:

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

  2. 每个线程调用该对象的任意方法都能获得正确结果;

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


public class Account
{
	private String accountNo;
	private double balance;
	public Account(){}

	public Account(String accountNo, double balance)
	{
		this.accountNo = accountNo;
		this.balance = balance;
	}

	public void setAccountNo(String accountNo)
	{
		this.accountNo = accountNo;
	}
	public String getAccountNo()
	{
		return this.accountNo;
	}
	public double getBalance()
	{
		return this.balance;
	}

	// 使用synchronized关键字修饰方法
	public synchronized void draw(double drawAmount)
	{
		if (balance >= drawAmount)
		{
			System.out.println(Thread.currentThread().getName()
				+ "取钱额度为:" + drawAmount);
			try
			{
				Thread.sleep(1);
			}
			catch (InterruptedException ex)
			{
				ex.printStackTrace();
			}
			balance -= drawAmount;
			System.out.println("余额为: " + balance);
		}
		else
		{
			System.out.println(Thread.currentThread().getName()
				+ "额度不足");
		}
	}
	
	public int hashCode()
	{
		return accountNo.hashCode();
	}
	public boolean equals(Object obj)
	{
		if (this == obj)
			return true;
		if (obj != null
			&& obj.getClass() == Account.class)
		{
			var target = (Account) obj;
			return target.getAccountNo().equals(accountNo);
		}
		return false;
	}
}
  • synchronized关键字可以修饰方法、代码块,但是不能修饰变量、构造器;
  •  线程安全是会降低程序性能的,在编程时要遵循以下几点:
  1. 不要对线程安全类的所有方法都进行同步,只对那些会引起共享资源改变的方法进行同步;
  2. 如果可变类有两种运行环境:单线程和多线程环境,则应该提供两个版本,在单线程中提供线程不安全版本提高性能,在多线程中提供线程安全版本保证正确性;
  • StringBuilder线程不安全;StringBuffer线程安全;

4.释放同步监视器的锁定(753)

5.同步锁

  • Java5定义了lock对象作为同步锁来实现线程安全;


import java.util.concurrent.locks.*;

public class Account
{
	// ReetrantLock可以显式加锁和释放锁;
	private final ReentrantLock lock = new ReentrantLock();
	private String accountNo;
	private double balance;
	public Account(){}
	
	public Account(String accountNo, double balance)
	{
		this.accountNo = accountNo;
		this.balance = balance;
	}
	
	public void setAccountNo(String accountNo)
	{
		this.accountNo = accountNo;
	}
	public String getAccountNo()
	{
		return this.accountNo;
	}
	public double getBalance()
	{
		return this.balance;
	}
	
	public void draw(double drawAmount)
	{
		// 上锁
		lock.lock();
		try
		{
			if (balance >= drawAmount)
			{
				System.out.println(Thread.currentThread().getName()
					+ "取钱数量为" + drawAmount);
				try
				{
					Thread.sleep(1);
				}
				catch (InterruptedException ex)
				{
					ex.printStackTrace();
				}
				balance -= drawAmount;
				System.out.println("余额为: " + balance);
			}
			else
			{
				System.out.println(Thread.currentThread().getName()
					+ "余额不足");
			}
		}
		finally
		{
			// 在finally块中解锁
			lock.unlock();
		}
	}
	
	public int hashCode()
	{
		return accountNo.hashCode();
	}
	public boolean equals(Object obj)
	{
		if (this == obj)
			return true;
		if (obj != null
			&& obj.getClass() == Account.class)
		{
			var target = (Account) obj;
			return target.getAccountNo().equals(accountNo);
		}
		return false;
	}

 

6.死锁及常用处理策略

  • 死锁:当两个线程相互等待对方释放同步监视器时就会发生死锁;例如,A和B分别锁了两个资源,但是,A必须访问B的锁定资源,才能完成线程;而B也必须访问A的锁定资源,才能完成线程,由此互相等待;
  • 避免死锁的方法:
  1. 尽量避免同一个线程对多个同步监视器进行锁定;
  2. 让线程具有相同的加锁顺序;
  3. 使用定时锁;

6.线程通信

1.传统的线程通信

  • 为什么线程需要通信:举个例子,银行如果要求存钱者在存好钱之后,取钱者立刻取钱,不允许多次连续存钱和连续取钱,这时候就需要线程通信;

  • Object类提供三种方法:wait(), notify(), notifyAll()方法,这三个方法必须由同步监视器对象调用;

  • 同步方法中(synchronized)可以直接调用这三个方法;同步代码块中需要使用对象调用;

  • 方法解释:

  1. ​​​​​​​wait():导致当前线程等待,直到其他线程调用该同步监视器的notify方法或notifyAll方法来唤醒该线程;

  2. notify():唤醒在此同步监视器上等待的单个线程。如果所有的线程都在这个同步监视器上等待,则会随机唤醒其中一个线程

  3. notifyAll():唤醒在此同步监视器上等待的所有线程;


public class Account
{
	private String accountNo;
	private double balance;
	// 状态判定变量
	private boolean flag = false;
    
	public Account(){}
	public Account(String accountNo, double balance)
	{
		this.accountNo = accountNo;
		this.balance = balance;
	}
	
	public void setAccountNo(String accountNo)
	{
		this.accountNo = accountNo;
	}
	public String getAccountNo()
	{
		return this.accountNo;
	}
	public double getBalance()
	{
		return this.balance;
	}

	//取钱方法
	public synchronized void draw(double drawAmount)
	{
		try
		{
			// 如果flag为true,表示还没有存钱进账户,方法阻塞;
			if (!flag)
			{
				wait();
			}
			else
			{
				// ִ执行取钱
				System.out.println(Thread.currentThread().getName()
					+ "取钱" + drawAmount);
				balance -= drawAmount;
				System.out.println("账户余额为" + balance);
				// 将flag设为false,表示标识账户已有存款;
				flag = false;
				//唤醒其他线程;
				notifyAll();
			}
		}
		catch (InterruptedException ex)
		{
			ex.printStackTrace();
		}
	}
	//存钱方法
	public synchronized void deposit(double depositAmount)
	{
		try
		{
			if (flag)             
			{
				wait();
			}
			else
			{
				//存钱
				System.out.println(Thread.currentThread().getName()
					+ "存钱" + depositAmount);
				balance += depositAmount;
				System.out.println("余额" + balance);
				// 存入钱,唤醒其他线程
				flag = true;
				notifyAll();
			}
		}
		catch (InterruptedException ex)
		{
			ex.printStackTrace();
		}
	}
	
	public int hashCode()
	{
		return accountNo.hashCode();
	}
	public boolean equals(Object obj)
	{
		if (this == obj)
			return true;
		if (obj != null
			&& obj.getClass() == Account.class)
		{
			var target = (Account) obj;
			return target.getAccountNo().equals(accountNo);
		}
		return false;
	}
}
  • 存款者线程:

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()
	{
		for (var i = 0; i < 100; i++)
		{
			account.draw(drawAmount);
		}
	}
}
  • 取钱者线程:

public class DepositThread extends Thread
{
	private Account account;
	private double depositAmount;
	public DepositThread(String name, Account account,
		double depositAmount)
	{
		super(name);
		this.account = account;
		this.depositAmount = depositAmount;
	}
	// 线程体
	public void run()
	{
		for (var i = 0; i < 100; i++)
		{
			account.deposit(depositAmount);
		}
	}
}
  • 主程序:

public class DrawTest
{
	public static void main(String[] args)
	{
		var acct = new Account("1234567", 0);
		new DrawThread("取钱", acct, 800).start();
		new DepositThread("存钱", acct, 800).start();
		new DepositThread("存钱", acct, 800).start();
		new DepositThread("存钱", acct, 800).start();
	}
}

 

2.使用Condition控制线程通信

  • 当使用lock对象来保证同步时,Java提供了Condition类来保持协调,Condition可以让那些已经得到Lock对象却无法继续执行的线程释放Lock对象,Condition也可以唤醒其他处于等待的线程;
  • Condition提供的三种方法:
  1. ​​​​​​​await();
  2. signal();
  3. signalAll();
  • 程序示例:

import java.util.concurrent.*;
import java.util.concurrent.locks.*;

public class Account
{
	// 建一个锁对象
	private final Lock lock = new ReentrantLock();
	// 将一个Condition对象绑定在这个Lock上
	private final Condition cond = lock.newCondition();
	
	private String accountNo;
	private double balance;
	private boolean flag = false;

	public Account(){}
	public Account(String accountNo, double balance)
	{
		this.accountNo = accountNo;
		this.balance = balance;
	}
	
	public void setAccountNo(String accountNo)
	{
		this.accountNo = accountNo;
	}
	public String getAccountNo()
	{
		return this.accountNo;
	}
	public double getBalance()
	{
		return this.balance;
	}

	public void draw(double drawAmount)
	{
		// 锁方法
		lock.lock();
		try
		{
			if (!flag)
			{
				cond.await();   //类似于wait()方法
			}
			else
			{
				System.out.println(Thread.currentThread().getName()
					+ "取钱:" + drawAmount);
				balance -= drawAmount;
				System.out.println("余额为" + balance);
				flag = false;
				cond.signalAll();   //类似notify()
			}
		}
		catch (InterruptedException ex)
		{
			ex.printStackTrace();
		}
		// 解锁
		finally
		{
			lock.unlock();
		}
	}
	public void deposit(double depositAmount)
	{
		lock.lock();
		try
		{
			if (flag)             
			{
				cond.await();
			}
			else
			{
				System.out.println(Thread.currentThread().getName()
					+ "存钱" + depositAmount);
				balance += depositAmount;
				System.out.println("余额为" + balance);
				flag = true;
				cond.signalAll();
			}
		}
		catch (InterruptedException ex)
		{
			ex.printStackTrace();
		}
		finally
		{
			lock.unlock();
		}
	}
	
	public int hashCode()
	{
		return accountNo.hashCode();
	}
	public boolean equals(Object obj)
	{
		if (this == obj)
			return true;
		if (obj != null
			&& obj.getClass() == Account.class)
		{
			var target = (Account) obj;
			return target.getAccountNo().equals(accountNo);
		}
		return false;
	}
}
  •  

3.使用阻塞队列(BlockingQueue)控制线程通信

  • BlockingQueue是Queue的子接口,它是一种线程工具:当生产者线程试图从BQ中放入数据,如果队列已满,则阻塞;当消费者线程试图从BQ中拿出数据,如果队列为空,则阻塞;
  • BQ提供两个支持阻塞的方法:
  1. put(E e):尝试把E元素放入BQ中;
  2. take():从BQ中取出元素;
  • 同时,BQ继承了Queue接口,BQ也能使用Queue的方法;(763)
  • BQ的使用例程:

import java.util.concurrent.*;

public class BlockingQueueTest
{
	public static void main(String[] args)
		throws Exception
	{
		// 创建一个容量为2的BQ队列
		BlockingQueue<String> bq = new ArrayBlockingQueue<>(2);
		bq.put("Java"); // 等同于add("java")
		bq.put("Java"); 
		bq.put("Java"); //超出队列要求,程序阻塞; 
	}
}

7.线程组和未处理的异常(767)

8.线程池

1.使用线程池管理线程

  • 当系统需要在启动时就创建大量线程时,采用线程池可以提高效率;
  • Java5新增了一个Executors工厂类来产生线程池:
  1. newCachedThreadPool():创建一个具有缓存功能的线程池,线程将会被存在这个线程池中;
  2. newFixedThreadPool(int nThread):创建一个可重用的,具有固定线程数的线程池;
  3. newSingleThreadExecutor():创建一个只有单线程的线程池;
  4. newScheduledThreadPool(int corePoolSize):创建具有指定数的线程池;可以在指定延迟后执行线程任务;
  5. newSingleThreadScheduledExecutor():创建只有一个线程的线程池,它可以在指定延迟后执行线程任务;
  • 使用线程池来执行线程任务的步骤如下:
  1. 调用Executors类的静态工厂方法创建一个ExecutorService对象,该对象代表一个线程池;
  2. 创建Runnable实现类或Callable实现类的实例,作为线程执行任务;
  3. 调用ExecutorService对象的submit()方法来提交Runnable实例或Callable实例;
  4. 当不想提交任何任务时,调用ExecutorService对象的shutdown()方法来关闭线程池;

import java.util.concurrent.*;

public class ThreadPoolTest
{
	public static void main(String[] args)
		throws Exception
	{
		//创建一个线程池,包含6个线程
		ExecutorService pool = Executors.newFixedThreadPool(6);
		// 使用lambda表达式创建Runnable对象
		Runnable target = () -> {
			for (var i = 0; i < 100; i++)
			{
				System.out.println(Thread.currentThread().getName()
					+ "线程名称" + i);
			}
		};
		// 提交两个线程
		pool.submit(target);
		pool.submit(target);
		// 关闭线程池
		pool.shutdown();
	}
}

2.使用ForkJoinPool利用多CPU

  • Java7提供了ForkJoinPool来支持将一个任务拆分成多个小任务并行计算,再把多个小任务的结果合并成总的计算结果;FJP是ExecutorService的实现类,是一种特殊的线程池;
  • 没有返回值的FJP:把一个任务拆成2个任务

import java.util.concurrent.*;

// RecursiveAction代表没有返回值的FPJ子类;
class PrintTask extends RecursiveAction
{
	// 每个小任务打印的数字
	private static final int THRESHOLD = 50;
	private int start;
	private int end;
	
	public PrintTask(int start, int end)
	{
		this.start = start;
		this.end = end;
	}
	@Override
    //执行体
	protected void compute()
	{
		if (end - start < THRESHOLD)
		{
			for (var i = start; i < end; i++)
			{
				System.out.println(Thread.currentThread().getName()
					+ "的i值" + i);
			}
		}
		else
		{
			// 将任务分解成两个小任务
			int middle = (start + end) / 2;
			var left = new PrintTask(start, middle);
			var right = new PrintTask(middle, end);
			// 执行两个任务
			left.fork();
			right.fork();
		}
	}
}
public class ForkJoinPoolTest
{
	public static void main(String[] args)
		throws Exception
	{
		var pool = new ForkJoinPool();
		// 提交可分解的FJP任务
		pool.submit(new PrintTask(0, 300));
		pool.awaitTermination(2, TimeUnit.SECONDS);
		// 关闭线程池
		pool.shutdown();
	}
}

 

  • 有返回值的FJP:

import java.util.concurrent.*;
import java.util.*;
//RecursiveTask<T>代表了能够返回值的可分解任务
class CalTask extends RecursiveTask<Integer>
{
	private static final int THRESHOLD = 20;
	private int arr[];
	private int start;
	private int 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;
		if (end - start < THRESHOLD)
		{
			for (var i = start; i < end; i++)
			{
				sum += arr[i];
			}
			return sum;
		}
		else
		{
		    //递归调用
			int middle = (start + end) / 2;
			var left = new CalTask(arr, start, middle);
			var 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 Exception
	{
		var arr = new int[100];
		var rand = new Random();
		var total = 0;
		// ��ʼ��100������Ԫ��
		for (int i = 0, len = arr.length; i < len; i++)
		{
			int tmp = rand.nextInt(20);
			// ������Ԫ�ظ�ֵ����������Ԫ�ص�ֵ��ӵ�sum�ܺ��С�
			total += (arr[i] = tmp);
		}
		System.out.println(total);
		// ����һ��ͨ�ó�
		ForkJoinPool pool = ForkJoinPool.commonPool();
		// �ύ�ɷֽ��CalTask����
		Future<Integer> future = pool.submit(new CalTask(arr, 0, arr.length));
		System.out.println(future.get());
		// �ر��̳߳�
		pool.shutdown();
	}
}

9.线程相关类(773)

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值