- 创建任务和线程
为任务定义一个类,这个类必须实现runnable接口,runnable接口只包含一个run办法,这个方法告诉系统线程将如何运行。
>>> TaskClass task = new TaskClass(…);
创建任务的线程
>>> Thread thread = new Thread(task);
用start()方法告诉java虚拟机该线程准备运行。线程启动,run()方法执行。
>>> thread.start()
Eg.创建三个任务以及三个运行这些任务的线程:
第一个任务打印字母a 100次。
第二个任务打印字母b 100次。
第三个任务打印1-100的整数。
三个线程将共享cpu,并且在控制台上轮流打印字母和数字。
public class TaskThreadDemo {
public static void main(String[] args) {
// Create tasks
Runnable printA = new PrintChar('a', 100);
Runnable printB = new PrintChar('b', 100);
Runnable print100 = new PrintNum(100);
// Create threads
Thread thread1 = new Thread(printA);
Thread thread2 = new Thread(printB);
Thread thread3 = new Thread(print100);
// Start threads
thread1.start();
thread2.start();
thread3.start();
}
}
// The task for printing a specified character in specified times
class PrintChar implements Runnable {
private char charToPrint; // The character to print
private int times; // The times to repeat
/** Construct a task with specified character and number of
* times to print the character
*/
public PrintChar(char c, int t) {
charToPrint = c;
times = t;
}
/** Override the run() method to tell the system
* what the task to perform
*/
public void run() {
for (int i = 0; i < times; i++) {
System.out.print(charToPrint);
}
}
}
// The task class for printing number from 1 to n for a given n
class PrintNum implements Runnable {
private int lastNum;
/** Construct a task for printing 1, 2, ... i */
public PrintNum(int n) {
lastNum = n;
}
/** Tell the thread how to run */
public void run() {
for (int i = 1; i <= lastNum; i++) {
System.out.print(" " + i);
}
}
}
2. thread类
sleep(long mills)可以将该线程设置为休眠以确保其他xian线程的执行,休眠时间为指定的毫秒数。sleep方法可能抛出一 个InterruptedException必检异常,必须放进try-catch中。
yield() 线程让步。为其他线程临时让出cpu时间。表示暂停当前线程,执行其他线程(包括自身线程),由cpu决定
join() 使一个线程等待另一个线程结束,阻塞所在线程,等调用它的线程执行完毕,再向下执行。
- 线程池
线程池是管理并发执行任务个数的理想方法。java提供executor接口来执行线程池中的任务,提供ExecutorService接口来管理 和控制任务。ExecutorService是executor的子接口。
如果需要为一个任务创建一个线程,就使用thread类。如果需要为多个任务创建线程,最好使用线程池。
>>> ExecutorService executor =Executors.newFixedThreadPool(3);
创建一个最大线程数为3的线程池执行器。
>>> ExecutorService executor =Executors.newFixedThreadPool(1);
可运行的任务将顺次执行,因为在线程池中只有一个线程。
>>> ExecutorService executor =Executors.newFixedThreadPool( );
为每个等待的任务创建一个新线程,所以,所有的任务都并发地执行。
import java.util.concurrent.*;
public class ExecutorDemo {
public static void main(String[] args) {
// Create a fixed thread pool with maximum three threads
ExecutorService executor = Executors.newFixedThreadPool(3);
// Submit runnable tasks to the executor
executor.execute(new PrintChar('a', 100));
executor.execute(new PrintChar('b', 100));
executor.execute(new PrintNum(100));
// Shut down the executor
executor.shutdown();
}
}
- 4. 线程同步
如果一个共享资源被多个线程同时访问,可能会遭到破坏。
添加关键字 synchronized可以避免竞争状态的发生。
一个同步方法在执行之前需要加锁。对于实例方法,要给调用该方法的对象加锁。对于静态方法,要给这个类加锁。如果一个线 程调用一个对象上的同步实例方法(静态方法),首先要给该对象(类)加锁,然后执行该方法,最后解锁。在解锁之前,另一 个调用那个对象(类)中方法的线程将被阻塞,直到解锁。
调用一个对象的同步实例方法要求给该对象加锁。调用一个类的同步静态方法要求对该类加锁。当执行方法中某一个代码块时, 同步语句不仅可用于对this对象加锁,而且可用于对任何对象加锁。这个代码成为同步块。
synchronized (expr) {
statements;
}
如 synchronized (account) {
account.deposit(1);
}
expr必须求出对象的引用。同步语句允许设置同步方法中的部分代码,而不必是整个方法。这大大增强了程序的并发能力。
任何同步的实例方法都可以转换为同步语句。
A.
public synchronized void xMethod() {
//method body
}
B.
public void xMethod() {
synchronized (this) {
//method body
}
}
另一种方法是利用加锁同步
package chapter32;
import java.util.concurrent.*;
import java.util.concurrent.locks.*;
public class AccountWithSyncUsingLock {
private static Account account = new Account();
public static void main(String[] args) {
ExecutorService executor = Executors.newCachedThreadPool();
// Create and launch 100 threads
for (int i = 0; i < 100; i++) {
executor.execute(new AddAPennyTask());
}
executor.shutdown();
// Wait until all tasks are finished
while (!executor.isTerminated()) {
}
System.out.println("What is balance ? " + account.getBalance());
}
// A thread for adding a penny to the account
public static class AddAPennyTask implements Runnable {
public void run() {
account.deposit(1);
}
}
// An inner class for account
public static class Account {
private static Lock lock = new ReentrantLock(); // Create a lock
private int balance = 0;
public int getBalance() {
return balance;
}
public void deposit(int amount) {
lock.lock(); // Acquire the lock
try {
int newBalance = balance + amount;
// This delay is deliberately added to magnify the
// data-corruption problem and make it easy to see.
Thread.sleep(5);
balance = newBalance;
}
catch (InterruptedException ex) {
}
finally {
lock.unlock(); // Release the lock
}
}
}
}
33行创建一个锁,41行获取该锁,55行释放该锁。
在对lock()的调用后紧随一个try-catch块并且在finally子句中释放这个锁是一个很好的习惯。
5. 线程协作
有时候线程之间需要相互协作。使用条件便于线程间通信。一个线程可以置顶在某种条件下该做什么。条件是通过调用lock对象 的newCondition()方法而创建的对象。一旦创建了条件,就可以使用await()、signal()和signalAll()方法来实现线程之间的相互 通信。
await() 让当前线程都处于等待状态,直到条件发生。
signal() 唤醒一个等待的线程
signalAll() 唤醒所有等待的线程
假设创建并启动两个任务,一个用来向账户中存款,另一个从同一账户中提款。当提款的数额大于账户的当前余额是,提款线程 必须等待。不管什么时候,只要向账户新存入一笔资金,存储线程必须通知提款线程重新尝试。如果余额仍未达到提款的数额, 提款线程必须继续等待新的存款。
为了同步这些操作,使用一个具有条件的锁newDeposit.如果余额小于取款数额,提款任务将等待newDeposit条件。当存款任务 给账户增加资金时,存款任务缓行等待中的提款任务再次尝试。
从lock对象中创建条件。为了使用条件,必须首先获取锁。Await()让线程等待并且自动释放条件上的锁,一旦条件正确,线程重 新获取锁并且继续执行。
import java.util.concurrent.*;
import java.util.concurrent.locks.*;
public class ThreadCooperation {
private static Account account = new Account();
public static void main(String[] args) {
// Create a thread pool with two threads
ExecutorService executor = Executors.newFixedThreadPool(2);
executor.execute(new DepositTask());
executor.execute(new WithdrawTask());
executor.shutdown();
System.out.println("Thread 1\t\tThread 2\t\tBalance");
}
// A task for adding an amount to the account
public static class DepositTask implements Runnable {
public void run() {
try { // Purposely delay it to let the withdraw method proceed
while (true) {
account.deposit((int)(Math.random() * 10) + 1);
Thread.sleep(1000);
}
}
catch (InterruptedException ex) {
ex.printStackTrace();
}
}
}
// A task for subtracting an amount from the account
public static class WithdrawTask implements Runnable {
public void run() {
while (true) {
account.withdraw((int)(Math.random() * 10) + 1);
}
}
}
// An inner class for account
private static class Account {
// Create a new lock
private static Lock lock = new ReentrantLock();
// Create a condition
private static Condition newDeposit = lock.newCondition();
private int balance = 0;
public int getBalance() {
return balance;
}
public void withdraw(int amount) {
lock.lock(); // Acquire the lock
try {
while (balance < amount) {
System.out.println("\t\t\tWait for a deposit");
newDeposit.await();
}
balance -= amount;
System.out.println("\t\t\tWithdraw " + amount +
"\t\t" + getBalance());
}
catch (InterruptedException ex) {
ex.printStackTrace();
}
finally {
lock.unlock(); // Release the lock
}
}
public void deposit(int amount) {
lock.lock(); // Acquire the lock
try {
balance += amount;
System.out.println("Deposit " + amount +
"\t\t\t\t\t" + getBalance());
// Signal thread waiting on the condition
newDeposit.signalAll();
}
finally {
lock.unlock(); // Release the lock
}
}
}
}
一旦线程调用条件上的await(),线程就进入等待状态,等待恢复的信号。如果忘记对状态调用signal()或者signalAll(),那么线程就 永远等待下去。
条件由lock对象创建,为了调用任意方法(await() 、signal()、signalAll()),首先必须拥有锁。如果没有获取锁就调用这些方 法,会抛出IllegalMonitorStateException异常。