一、认识线程
我们应该都很熟悉操作系统中的多任务,即在同一时刻运行多个程序的能力。操作系统将CPU的时间片分配给每一个进程,给人并行处理的感觉。
这里提到进程的概念,进程是程序的一次动态执行过程,是系统运行程序的基本单位,每一个进程都有自己独立的一块内存空间、一组系统资源,内部数据和状态都是完全独立的。
线程,是进程中执行运算的最小单位,一个进程在执行过程中可以产生多个线程,而线程必须在某个进程内执行。如果在一个进程中同时运行了多个线程,用来完成不同的工作,则称之为多线程。多线程程序可以带来更好的用户体验,避免因程序执行过慢而导致出现计算机死机或者白屏的情况。可以最大限度地提高计算机系统的利用效率。
多线程的优势:进程之间不能共享内存,但线程之间共享内存非常容易;使用多线程来实现多任务并发比多进程的效率高。Java语言内置了多线程功能支持,简化了Java的多线程编程。
二、编写线程类
每个程序至少自动拥有一个线程,称为主线程。当程序加载到内存时启动主线程。Java程序中的public static void main()方法是主线程的入口,运行Java程序时,会先执行这个方法。(开发中,用户编写的线程一般都是指除了主线程之外的其他线程。
使用一个线程的步骤:
1)定义一个线程。定义一个线程通常用两种方法,继承java.lang.Thread类和实现java.lang.Runnable接口(还有一种方法实现Callable接口,与实现Runnable接口的方式基本相同,可以归为一种方式)。
2)创建线程对象。
3)启动线程。
4)终止线程。
1、使用Thread类来创建线程
例:
public class FirstThread extends Thread {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
//当线程类直接继承Thread类时,直接使用this即可获取当前线程
//Thread对象的getName()方法返回当前线程的名字
System.out.println(getName() + "输出:" + i);
}
}
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
//调用Thread的currentThread()方法获取当前线程
System.out.println(Thread.currentThread().getName() + "输出:" + i);
}
FirstThread firstThread = new FirstThread();
//调用run()方法,是由main线程来执行的,并不是启动线程
firstThread.run();
//启动线程要用start()方法
firstThread.start();
}
}
Thread类常用方法
void run():执行任务操作的方法。
void start():使该线程开始执行。
void sleep(long millis):在指定的毫秒数内让当前正在执行的线程休眠(暂停执行)。
String getName():返回该线程的名称。
int getPriority():返回该线程的优先级。
void setPriority(int newPriority):更改线程的优先级。
Thread.State getState():返回线程的状态。
boolean isAlive():测试线程是否处于活动状态。
void join():等待该线程终止。
void interrupt():中断线程。
void yield:暂停当前正在执行的线程对象,并执行其他线程。
2、使用Runnable接口创建线程
Runnable接口中声明了一个run()方法,public void run()。一个类可以通过实现Runnable接口并实现run()方法完成线程的所有活动。
例:
public class FirstThread implements Runnable {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
//当线程类实现Runnable接口时,只能用Thread.currentThread()方法来获取当前线程
System.out.println(Thread.currentThread().getName() + "输出:" + i);
}
}
public static void main(String[] args) {
FirstThread firstThread = new FirstThread();
//Runnable对象仅仅作为Thread对象的target,而实际的线程对象依然是Thread实例
Thread thread = new Thread(firstThread);
thread.start();
}
}
两种创建线程的方式有各自的特点和应用领域:直接继承Thread类的方式编写简单,可以直接操作线程,适用于单重继承的情况,当一个线程继承了另一个类时,就只能实现Runnable接口的方法来创建线程。而且Runnable这种方式还可以使多个线程之间使用同一个Runnable对象,因此一般采用实现接口的方式来创建多线程。
三、线程的状态
线程的生命周期:新建(New)、就绪(Runnable)、运行(Running)、阻塞(Blocked)和死亡(Dead)5种状态。
当程序使用new关键字创建了一个线程之后,该线程就处于新建状态。当线程对象调用了start方法之后,该线程就处于就绪状态。但此时并未真正进入运行状态,当CPU给它分配资源后,才进入运行状态。当它因某种原因不能继续运行时,进入阻塞状态。此时在得到一个特定事件之后才会转回可运行状态。在run()方法运行完毕、stop()方法被调用或者在运行过程中出现未捕获的异常时,线程进入死亡状态。
导致一个线程被阻塞的原因:
1)线程调用sleep()方法主动放弃所占用资源。
2)线程调用了一个IO方法,在该方法返回前,该线程被阻塞。
3)线程试图获得一个对象的锁,而这个对象的锁正被别的线程占用,那么线程会被阻塞。
4)线程的suspend()方法将线程被挂起(方法已过期,基本不再使用)。
四、线程调度
1、线程优先级
线程的优先级用1~10来表示,10表示优先级最高,默认值是5。每个优先级对应一个Thread类的公用静态常量。例如:
public static final int NORM_PRIORITY = 5;
public static final int MIN_PRIORITY = 1;
public static final int MAX_PRIORITY = 10;
线程的优先级可以通过setPriority(int grade)方法更改。
2、实现线程调度的方法
1)join()方法:使当前线程暂停执行,等待调用该方法的线程结束后再继续执行本线程。
2)sleep()方法:会让当前线程睡眠(停止执行)millis毫秒,线程由运行中的状态进入不可运行状态,睡眠时间过后线程会再次进入可运行状态。
3)yield()方法:可让当前线程暂停执行,允许其他线程执行,但该线程仍然处于可运行状态,并不变为阻塞状态。此时,系统选择其他相同或更高优先级线程执行,若无其他相同或更高优先级线程,则该线程继续执行。
五、实现线程同步
当两个或多个线程需要访问同一资源时,容易造成数据错误,需要以某种顺序来确保该资源在某一时刻只能被一个线程使用的方式称为线程同步。
采用同步来控制线程的执行有两种方式,即同步方法和同步代码块。这两种方式都使用synchronized关键字实现。所有声明为synchronized的方法只能一个处于可执行的状态,从而有效地避免了类成员变量访问冲突。
synchronized关键字自动提供一个锁以及相关的“条件”,对于大多数需要显示调用锁的情况,这是很便利的。Java SE5.0引入了ReentrantLock类,通过Lock和Condition对象也可以实现。
public synchronized void method(){
method body
}
等价于:
public void method(){
this.intrinsicLock.lock();
try{
method body
}
finally{
this.intrinsicLock.unlock();
}
}
六、实现线程间通信
Java提供了如下3个方法实现线程之间的通信。
1)wait()方法:挂起当前线程,并释放共享资源的锁。
2)notify()方法:调用任意对象的notify()方法会在因调用该对象的wait()方法而阻塞的线程中随机选择一个线程解除阻塞,但要等到获得锁后才可真正执行。
3)notifyAll()方法
class Menu{
private int menuNum;
public synchronized void putNum(){
//如果服务员没有通知,点菜总数量为0,则厨师等待
if (menuNum==0){
System.out.println("服务员没有通知消费者的点菜信息,因此厨师停止做菜。");
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
menuNum--;
System.out.println("厨师做了一道菜,还剩"+menuNum+"道菜要做");
notify();
}
public synchronized void getNum(){
if (!(menuNum<4)){
System.out.println("点菜数不小于4,厨师要罢工。");
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
menuNum++;
System.out.println("服务员接受点菜,现在有"+menuNum+"道菜要做");
notify();
}
}
class Chef extends Thread{
private Menu m;
public Chef(Menu m){
this.m = m;
}
@Override
public void run() {
for (int i = 0; i < 50; i++) {
try {
Thread.sleep(4000);
} catch (InterruptedException e) {
e.printStackTrace();
}
m.putNum();
}
}
}
class Waiter extends Thread{
private Menu m;
public Waiter(Menu m){
this.m = m;
}
@Override
public void run() {
for (int i = 0; i < 100; i++) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
m.getNum();
}
}
}
public class Repast {
public static void main(String[] args) {
Menu m = new Menu();
new Chef(m).start();
new Waiter(m).start();
}
}
运行结果: