------- android培训、java培训、期待与您交流! ----------
线程的概念
在操作系统中,可以通过多个进程并发的执行多个任务。进程是有代码、数据、内核状态和一组寄存器组成。一个包含多线程的进程也能够实现进程内多任务并发的执行。但线程不包含进程地址空间中的代码和数据,线程是计算过程在某一时刻的状态。所以,线程间的切换要比进程间的切换负担小得多。
进程是一个内核级的尸体,用户(程序)不能直接访问这些数据,而线程是用户级的尸体,线程结构保存在用户空间中,能够被普通的用户级函数直接访问。Java中的线程模型包括三个部分:一个虚拟的CPU,该CPU执行的代码和代码所操作的数据。
线程的创建
Thread类
Java中的线程体是由线程类的run方法开始执行,就想Java Application从main()开始,Applet从init()开始一样。Thread类的构造方法有多个,一般结构可以表示如下:
public Thread(ThreadGroup group, Runnable target, String name)
group指明该线程所属的线程组.
target提供线程体的对象。Runnable接口中定义了run()方法,实现该接口的类可以提供线程体,线程启动时该对象的run()方法将被调用。
name线程的名称。每个线程有自己的名称,如果name为null, 则Java自动赋予一个唯一的名称
由于Thread类本身已经实现了Runnable接口,因此可以有两种方式提供run()方法的实现:实现Runnable接口和继承Thread类。
通过实现Runnable接口创建线程
public class ThreadTest {
public static void main(String[] args) {
Thread t1 = new Thread(new Hello());
Thread t2 = new Thread(new Hello());
t1.start();
t2.start();
}
}
class Hello implements Runnable{
@Override
public void run() {
for(int i=0;i<5;i++){
System.out.println("Hello"+i);
}
}
}
打印结果可能为:
Hello0
Hello0
Hello1
Hello2
Hello3
Hello4
Hello1
Hello2
Hello3
Hello4
一个线程是Thread类的一个实例,线程是从一个传递给线程的Runnable的Runnable实例的run()方法开始执行。必须调用线程的start()方法才能启动线程。
通过继承Thread类创建线程
因为Thread类实现了Runnable接口,所以可以通过继承Thread类来创建线程:
从Thread类派生子类,并重写其run方法来定义线程体
创建该子类的对象创建线程
public class ThreadTest2 {
public static void main(String[] args) {
Thread t1 = new Hello2();
Thread t2 = new Hello2();
t1.start();
t2.start();
}
}
class Hello2 extends Thread{
@Override
public void run() {
for(int i=0;i<5;i++){
System.out.println("Hello"+i);
}
}
}
创建线程的两种方法的比较
采用继承Thread类方法的优点:
程序代码简单,并可以在run()方法中直接调用线程的Thread类的其他方法。
实现Runnable接口的优点:
1. 符合面向对象设计的思想:从面向对象设计的角度看,Thread类是虚拟CPU的封装,所以Thread的子类应该封装CPU的行为,但在Thread子类中构建线程,子类大都是与CPU不相关的类。而实现Runnable接口,更加符合面向对象设计思想。
2. 便于继承其他类:实现了Runnable接口的类也可以继承其他类
可见一般通过实现Runnable接口创建线程的方法更值得提倡。
线程控制
下面是Thread类提供的一些基本线程的控制方法
1. sleep()
该方法能够把CPU让给优先级比其低的线程。该方法可以使一个线程暂停运行一端固 定的时间。在休眠时间内,线程将不运行。
2. yield()
调用该方法后,可以使其具有与当前线程相同优先级的线程有运行的机会。yield()方法 执行后,把该线程放入到可运行线程池中,并允许其他同优先级的可运行的线程运行。 如果没有同优先级的线程,该线程继续运行。
3. join()
t.join()方法使当前的线程等待直到线程t线程运行结束为止,当线程恢复到可运行状态。join()方法可以提供参数join(long millis)和join(long millis, int nanos):参数表示当前线程将等待线程t结束或者最多等待给定时间后,在继续执行。如果没有参数则会等待t结束。
1. interrupt()
如果一个线程t在调用sleep()、join()、wait()等方法被阻塞时,则t.interrupt()方法中断t的阻塞状态,并且t将接收到InterruptException异常。
2. currentThread()
该静态方法返回当前线程的引用。
3. isAlive()
判断线程是否活着,返回true则表示线程已经启动但还没有运行结束。
4. stop()
强行终止线程,容易造成线程的不一致,不提倡使用。可以使用标志flag通知一个线程应该结束。
5. suspend()和resume()
在一个线程中调用t.suspend(),将使另一个线程t暂停执行。要想恢复线程,必须由其他线程调用t.resume(),不提倡该方法,易造成死锁。
public class JoinTest {
public static void main(String[] args) throws InterruptedException {
Thread t = new Thread(new Hello());
t.start();
for(int i=0;i<5;i++){
if(i==2 && t.isAlive()){
System.out.println("Hello Thread joins");
t.join();
}
System.out.println("main Thread "+i);
}
}
}
class Hello implements Runnable{
@Override
public void run() {
for(int i=0;i<5;i++){
System.out.println("Hello "+i);
}
}
}
打印结果如下:
main Thread 0
main Thread 1
Hello Thread joins
Hello 0
Hello 1
Hello 2
Hello 3
Hello 4
main Thread 2
main Thread 3
main Thread 4
本例测试join方法,主线程在调用t.join()之后将会等待直到t运行结束。
public class ThreadTest {
public static void main(String[] args) {
Hello2 r = new Hello2();
Thread t = new Thread(r);
t.setPriority(Thread.MAX_PRIORITY);
t.start();
System.out.println("Here to stop thread t");
r.stopHello();
for(int i=0;i<3;i++){
System.out.println("main thread "+i);
}
}
}
class Hello2 implements Runnable{
int i=0;
private boolean timeToQuit = false;
@Override
public void run() {
while(!timeToQuit){
System.out.println("Hello "+i++);
try{
if(i%2 == 0){
Thread.sleep(1);
}
}catch(Exception e){}
}
}
public void stopHello(){
timeToQuit = true;
}
}
可能的打印结果:
Here to stop thread t
Hello 0
Hello 1
main thread 0
main thread 1
main thread 2
本例测试通过标志flag来停止一个线程。
线程同步
多线程多共享数据的操作时,由于线程运行顺序是不确定的,这会导致运行结果的不确定性,使共享数据的一致性被破坏,因此在某些应用程序中必须对线程的并发操作进行控制。
class Hello3 implements Runnable {
private int count = 5;
public void run() {
for (int i = 0; i < 10; ++i) {
printCount();
}
}
private synchronized void printCount() {
if (count >= 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(count--);
}
}
public static void main(String[] args) {
Hello3 he = new Hello3();
Thread h1 = new Thread(he, "t1");
Thread h2 = new Thread(he, "t2");
Thread h3 = new Thread(he, "t2");
h1.start();
h2.start();
h3.start();
}
}
一次可能的打印结果如下:
5
5
4
3
2
1
0
-1
显然几个线程对数据count进行了共享操作,run()方法可能并不会在某个线程执行过程中完全执行,这就造成了数据的不一致。为了保证数据的一致性,需要使用到同步技术,所谓同步就是某个数据在某一段时间内只被一个线程使用。采用同步,可以使用同步代码块和同步方法两种来完成。我们对代码做如下改动:
private void printCount() {
synchronized (this) {
if (count >= 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(count--);
}
}
}
或者
private synchronized void printCount() {
if (count >= 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(count--);
}
}
这时候的打印结果为:
5
4
3
2
1
这样一来就实现了线程的同步,保证了线程对共享数据同步操作,保证了数据的一致性。