一,线程的概念
线程提供了运行一个任务的机制。对于Java而言,可在一个程序中并发的启动多个线程。这些线程可以在多出力器系统上同时运行。
在单处理器系统中,多个线程共享cpu时间称为事件分享,而操作系统负责调度及分配资源给他们。
多线程可以使程序反应更快,交互性更强,执行效率更高。
线程 是程序中的执行线程。Java 虚拟机允许应用程序并发地运行多个执行线程。
每个线程都有一个优先级,高优先级线程的执行优先于低优先级线程。每个线程都可以或不可以标记为一个守护程序。当某个线程中运行的代码创建一个新 Thread 对象时,该新线程的初始优先级被设定为创建线程的优先级,并且当且仅当创建线程是守护线程时,新线程才是守护程序。
当 Java 虚拟机启动时,通常都会有单个非守护线程(它通常会调用某个指定类的 main 方法)。Java 虚拟机会继续执行线程,直到下列任一情况出现时为止:
调用了 Runtime 类的 exit 方法,并且安全管理器允许退出操作发生。
非守护线程的所有线程都已停止运行,无论是通过从对 run 方法的调用中返回,还是通过抛出一个传播到 run 方法之外的异常。
二,创建任务和线程
一个任务就是一个对象,创建任务首先要实现runnable接口的类,runnable接口中只含有一个run方法。
public class Mythread implements Runnable{
@Override
public void run() {
// TODO Auto-generated method stub
}
}
创建了一个mythread类之后,可以用他的构造方法创建一个任务
例如:
Mythread th01 = new Mythread();
任务必须在线程中执行。Thread类包括创建线程的构造方法以及控制线程的很多有用的方法。
//创建任务的线程:
Thread thread = new Thread();
//调用start方法开启线程
thread.strat();
三,thread类
Thread thread = new Thread();
thread.start(); //使该线程开始执行
thread.run(); //如果该线程是使用独立的 Runnable 运行对象构造的,则调用该 Runnable 对象的 run 方法;否则,该方法不执行任何操作并返回。
thread.currentThread(); //返回对当前正在执行的线程对象的引用。
thread.yield(); //暂停当前正在执行的线程对象,并执行其他线程
thread.sleep(0); //在指定的毫秒数内让当前正在执行的线程休眠(暂停执行),
thread.interrupt(); //中断线程。
thread.interrupted(); //测试当前线程是否已经中断。
thread.isAlive(); //测试线程是否处于活动状态
thread.setPriority(0);//更改线程的优先级。 0-10,初始为5
thread.getPriority(); //返回线程的优先级。
thread.setName(null); //改变线程名称,使之与参数 name 相同
thread.getName(); //返回该线程的名称
thread.join(); //等待该线程终止的时间最长为 millis 毫秒
thread.getId(); //返回该线程的标识符。线程 ID 是一个正的 long 数,在创建该线程时生成。线程 ID 是唯一的,并终生不变。线程终止时,该线程 ID 可以被重新使用。
四,线程池
由于用实现runnable方法定义一个任务类的方法对每一个方法都要创建一个线程,对大量的任务是不够高效的。为每一个任务创建一个新线程可能会限制吞吐量并造成性能降低。线程池就是一个管理并发执行任务个数的理想方法。
Java提供Executor接口来执行线程池中的任务,提供ExecutorService接口来管理和控制任务。ExecutorService是Executor的子接口。
public static void main(String[] args) {
ExecutorService executor = Executors.newFixedThreadPool(3); //创建一个最大线程数为3的线程池
executor.execute(new PrintChar('a',100); //任务1
executor.execute(new PrintChar('b',100);//任务2
executor.execute(new PrintChar(100); //任务3
executor.shutdown(); //线程关闭,不能接受新的任务,但是现有的任务要继续执行完成
}
五,线程同步
如果一个共享资源被多个线程同时访问,可能会遭到破坏。线程的同步用来协调相互依赖的线程的执行。
//假设创建并启动100个线程,每个线程都往同一个账户加1便士。
public class t01 {
public static Account account = new Account(); //一个初始额为0的钱包
public static void main(String[] args) {
ExecutorService executor = Executors.newCachedThreadPool();
//创建并且开始100个线程
for (int i = 0; i < 100; i++) {
executor.execute(new AddPennyTask());
}
executor.shutdown();
//等待所有任务完成
while (!executor.isTerminated()) {
}
//线上目前余额
System.out.println("what is balance" + account.getBalance());
}
//向账户中加1便士
private static class AddPennyTask implements Runnable {
public void run() {
account.deposit(1);
}
}
//钱包类
private static class Account {
private int balance = 0;
public int getBalance() {
return balance;
}
public void deposit(int amount) {
int newBalance = balance + amount;
try {
Thread.sleep(5);
} catch (InterruptedException ex) {
// TODO: handle exception
}
balance = newBalance;
}
}
}
//太多线程或导致出现竞争状态。
synchronized关键字
为了避免竞争状态,应该防止多个线程同时进入程序的某一特定部分,程序的这部分叫做临界区。可以使用关键字synchronized来同步访问,以便一次只有一个线程可以访问这个方法。
给上面的代码加锁,使其变安全
public void deposit(int amount) ------》public synchronized void deposit(int amount)
一个同步方法在执行之前要加锁。锁是一种实现资源排他使用的机制。对于实例方法,要给调用该方法的对象加锁。对于静态方法,要给这个类加锁。
同步语句
调用一个对象上的同步实例方法,需要给该对象加锁。而调用一个类上的同步静态方法,需要给该类加锁。当执行方法中某一个代码块时,同步语句不仅可用于对this对象加锁,而且可用于对任何对象加锁。这个代码块称为同步块( synchronized block)。同步语句的一般形式如下所示:
synchronized(expr) {
statements;
}
表达式expr求值结果必须是一个对象的引用。如果对象已经被另一个线程锁定,则在解锁之前,该线程将被阻塞。当获准对一个对象加锁时,该线程执行同步块中的语句,然后解除给对象所加的锁。
对上面程序的改进:
synchronized(account){
account.deposit(1);
}
六,阻塞队列
阻塞队列 ( blocking qucue)在试图向一个满队列添加元素或者从空队列中删除元素时会导致线程阻塞。BlockingQueue接口继承了java.util.Queue,并且提供同步的put和take方法向队列尾部添加元素,以及从队列头部删除元素。
Java支持的三个具体的阻塞队列ArrayB1ockingQueue、LinkedB1ockingQueue和PriorityBlockingQueue 。它们都在java.uti1.concurrent包中。Array-BlockingQueue使用数组实现阻塞队列。必须指定一个容量或者可选的公平性策略来构造ArrayBlockingQueue。LinkedBlockingQueue使用链表实现阻塞队列。可以创建无边界的或有边界的LinkedBlockingQueue。PriorityBlockingQueue是优先队列。可以创建无边界的或有边界的优先队列。
七,信号量
计算机科学中,信号量指对共同资源进行访问控制的对象。在访问资源之前,线程必须从信号量获取许可。在访问完资源之后,这个线程必须将许可返回给信号量。
为了创建信号量,必须确定许可的数量,同时可选用公平策略,。任务通过调用信号量的acquire()方法来获得许可,通过调用信号量的release()方法来释放许可。一旦获得许可,信号量中可用许可的总数减1。一旦许可被释放,信号量中可用许可的总数加1。
只有一个许可的信号量可以用来模拟一个相互排斥的锁。
八,避免死锁
有时两个或多个线程需要在几个共享对象上获取锁,这可能会导致死锁。也就是说,每个线程已经获取了其中一个对象上的锁,而且正在等待另一个对象上的锁。考虑有两个线程和两个对象的情形,如图30-24所示。线程1获取object1上的锁,而线程2获取object2上的锁。现在线程1等待object2上的锁,线程2等待object1上的锁。每个线程都在等待另一个线程释放它所需要的锁,结果导致两个线程都无法继续运行。
使用一种称为资源排序的简单技术可以轻易地避免死锁的发生。该技术是给每一个需要锁的对象指定一个顺序,确保每个线程都按这个顺序来获取锁。使用一种称为资源排序的计数可以轻易避免死锁。
九,线程状态
有几种原因可能使线程进入阻塞状态(即非活动状态)。可能是它自己调用了join( ,sleep()或wait()方法。它可能是在等待I/O操作的完成。当使得其处于非激活状态的动作不起作用时,阻塞线程可能被重新激活。例如,如果线程处于休眠状态并且休眠时间已过期,线程就会被重新激活并进入就绪状态。
最后,如果一个线程执行完它的run(方法,这个线程就被结束(finished)。