第十章 线程
10.1线程的概念:
事实上,在单个程序内部是可以在同一时刻进行多种运算的,这就是所谓的多进程(这与多任务的概念有相似之处)。、
一个单独的进程与顺序程序相似,也有一个入口,一个出口,以及一个顺序执行的序列。从概念上说,一个线程是一个程序内部的一个顺序控制流。线程并不是程序,它自己本身并不能运行,而必须在程序中运行。在一个程序中可以实现多个进程,这些进程同时运行,但是多线程并不等于多次启动一个程序,操作系统也不会把每个线程当做进程来对待。
线程如进程的区别:
1:两者的颗粒度不同,是两个不同层次上的概念。进程是由操作系统来管理的,而线程则是在一个程序(进程)内。
2:不同的的代码,内部数据和状态都是完全独立的,而一个程序内的多线程共享同一块内存空间和同一组系统资源,有可能互相影响。
3:线程本身的数据通常只有寄存器数据,以及一个程序执行时使用的堆栈,所以线程的切换负担比进程切换有的要小。
使用多线程具有的如下的优点:
1:多线程编程简单,效率高(能直接共享数据和资源,而多进程不能)
2:适合于开发服务程序(如web服务,聊天服务)
3:适合于开发有多种交互接口的程序(如聊天程序的客户端,网路下载工具)
4:适合于有人机交互又有计算量的程序
5:减轻编写交互频繁,涉及面多的程序的困难(如监听网络接口)
6:程序的吞吐量会得到改善
7:有多个处理器的系统,可以并发运行不同的线程
10.2线程的建立
Java提供了类Java.lang.Thread来支持多线程编程,Thread类有以下的构造方法
Thread() |
Thread(Runable target) |
Thread(Runable target,String name) |
Thread(String name) |
Thread(ThreadGroup group,Rnuable target) |
Thread(ThreadGroup group,Rnuable target,String name) |
Thread(ThreadGroup group,String name) |
参数target是线程执行的目标对象,即线程执行的代码;group是线程所在的组;name是线程的名字
10.2.1采用继承法创建线程
该方法比较简单,主要是通过继承java.lang.Thread类,并覆盖Thread类的run()方法来完成线程的创建.Thread类是一个具体的类不是抽象类,该类封装了线程的行为.要创建一个线程,程序员必须创建一个Thread的子类.在Thread类中最重要的方法时run()和start().
Run()方法必须进行重写,把线程所要执行的代码加入到这个方法中,也就是线程体.但是它必须经过start()方法来启动线程.
public class MyThread extends Thread
{
// count变量用于统计打印的次数并共享变量
private static int count = 0;
public static void main(String[] args)
{
MyThread p = new MyThread("t1");
// 线程执行
p.start();
// 主线程main方法执行一个循环
for (int i = 0; i < 5; i++)
{
count++;
System.out.println(count + " :main");
}
}
// 构造方法
public MyThread(String name)
{
// 调用父类的构造方法
super(name);
}
@Override
public void run()
{
// 线程中必须有的run方法
for (int i = 0; i < 5; i++)
{
count++;
System.out.println(count + ":" + this.getName());
}
}
}
10.2.2 通过实现接口创建线程
该方法通过实现java.lang.Runnable接口的类来创建多线程.该接口定义了一个方法run(),所以必须的新类中实现它,但是Runnable接口中没有任何对线程的支持,还必须要创建Thread类的实例
public class MyThread2 implements Runnable
{
public static void main(String[] args)
{
for (int i = 0; i < 5; i++)
{
new Thread(new MyThread2(i + 1)).start();
}
}
int count, number;
public MyThread2(int i)
{
this.number = i;
System.out.println("创建线程 " + this.number);
}
public void run()
{
while (true)
{
System.out.println("线程 " + this.number + ":计数 " + this.count);
if (++this.count == 6)
{
return;
}
}
}
}
10.3线程的生命周期及调度
10.3.1 线程的生命周期
线程是动态的,具有一定的生命周期,分别从创建,执行,堵塞直到死亡.在每一个线程类中都定义了用于完成功能的run方法,这个方法称为线程体
1:线程的四个状态
1.1:创建状态:
当利用new关键字创建线程对象实例后,它仅仅作为一个对象实例存在,JVM没有为其分配CPU时间片等线程资源
1.2:就绪状态:
当处于创建状态的线程中调用start方法将线程的状态转换为就绪状态.
1.3:堵塞状态
堵塞指的是暂停一个线程的执行以等待某个条件发生(如某资源准备就绪),若线程处于堵塞状态,调度机制不给它分配任何CPU时间,直接跳过它.
1.4:死亡状态
当线程运行结束或者在调用线程对象的stop方法后线程将终止运行,由JVM收回线程占用的资源
10.3.2:线程调度和优先级
Java采用的是一种简单,固定的调度法,即固定优先级调度(抢先式调度),Java将线程的优先级分为10个等级,分别用1到10的数字表示.数字越大表明线程的级别越高.相应的,在Thread类中定义了表示线程最高,最低,和普通优先级的常量MIN_PRIORITY,MAX_PRIORITY,NORMAL_PRIORITY,代表的优先级分别是1,10,5,当一个线程对象被创建时,其默认的优先级是5.
在应用程序中设置线程优先级的方法比较简单,在创建线程对象后可以调用线程对象的setPriority()方法该表线程的优先级,同样可以调用getPriority()来获取当前线程的优先级.
public class TestThreadPriority extends Thread
{
public static void main(String[] args)
{
TestThreadPriority t1 = new TestThreadPriority("Thread1");
t1.setPriority(MIN_PRIORITY);
t1.start();
TestThreadPriority t2 = new TestThreadPriority("Thread2");
t2.setPriority(NORM_PRIORITY);
t2.start();
TestThreadPriority t3 = new TestThreadPriority("Thread3");
t3.setPriority(MAX_PRIORITY);
t3.start();
}
public TestThreadPriority(String name)
{
super(name);
}
@Override
public void run()
{
for (int i = 0; i < 3; i++)
{
System.out.println(this.getName() + " is running");
}
super.run();
}
}
10.4:线程互斥
在并发程序设计中已经被研究并得到解决.对多线程共享的资源或数据称为临界资源,而把每一个线程中访问临界资源的那一段代码称为临界代码.通过为临界带买段设置信号灯,就可以保证资源的完整性,从而安全地访问共享资源.
为了实现这种机制,Java语言提供了以下两方面的支持
为每个对象设置了一个”互斥锁”标记.该标记保证在任何时候,只能有一个线程有该互斥锁,其他线程如果需要获得互斥锁,必须等待当前拥有该锁的线程将其释放.该对象称为互斥对象.
为了配合使用对象的互斥锁,Java语言提供了保留字synchronized.其基本用法如下:
Synchronized(互斥对象){
临界代码
}
当一个线程执行到该行代码时,首先检测该互斥对象的互斥锁.如果该互斥锁没有被占用,则该线程将获得该互斥锁,并执行临界代码,直到执行完毕并释放互斥锁;
可以看出,任意一个对象都可以作为信息灯,从而解决上面的问题,首先定义一个互斥对象类,作为信号灯.由于该对象只作为信号量使用,所以并不需要为它定义其他方法.
public class AccountThread extends Thread
{
public static void main(String[] args)
{
Account account = new Account(100);
Semaphore semaphore = new Semaphore();
AccountThread at1 = new AccountThread(account, 1000, semaphore);
AccountThread at2 = new AccountThread(account, 0, semaphore);
at1.start();
at2.start();
}
Account account;
int delay;
Semaphore semaphore;
// 构造方法
public AccountThread(Account account, int delay, Semaphore semaphore)
{
this.account = account;
this.delay = delay;
this.semaphore = semaphore;
}
@Override
public void run()
{
synchronized (this.semaphore)
{
if (this.account.balance >= 100)
{
try
{
// 延迟
sleep(this.delay);
// 模拟取钱100
this.account.balance = this.account.balance - 100;
System.out.println("withdraw 100 successful!!!");
}
catch (InterruptedException e)
{
e.printStackTrace();
}
}
else
{
System.out.println("withdraw failed!!!");
}}}}
// 定义一个类,利用其对象作为互斥信号灯
class Semaphore{}