首页 > 代码库 > Java学习资料-线程

Java学习资料-线程

1、 线程的概念

1.1 程序、进程与线程 

程序:程序以静态的代码,如源程序、目标程序。

进程:进程是程序的一次动态的执行过程。平时我们在Windows任务管理器里就可以看到系统进程。

线程:线程是比进程更小的执行单位。 每个Java进程都有一个主线程。从main()开始,由Java虚拟机创建。其它线程由主线程创建。

1.2 线程调度与优先级 

线程的优先级是按照线程的重要性来分的,因为在多个线程执行过程中,会出现多个线程同时想占用同一个资源的情况,如果不用优先级来约束这些线程,机器将处于死锁状态,即谁都想要这个资源,但谁都得不到。这就相当于我们平时说的退一步海阔天空是一个道理。

我们可以通过调用 Thread 类的方法getPriority()和setPriority()来获得和设置线程的优先级,线程的优先级界于1(MIN_PRIORITY)和10(MAX_PRIORITY)之间,缺省是5(NORM_PRIORITY)。

1.3 线程的状态与生命周期 

    新状态:线程已被创建但尚未执行(start()尚未被调用)。

    可执行状态:线程可以执行,虽然不一定正在执行。CPU 时间随时可能被分配给该线程,从而使得它执行。

    运行状态:得到CPU资源,正在执行。

    阻塞状态:线程没有被分配 CPU 时间,无法执行;可能阻塞于I/O,或者阻塞于同步锁。

    死亡状态:正常情况下run()返回使得线程死亡。调用 stop()或 destroy()也有同样效果,但是不被推荐,前者会产生异常,后者是强制终止,不会释放锁。

一个线程的生命周期分为生成、运行、等待、终止等阶段,各阶段及状态间的转换条件具体如图所示:            

技术分享

1.4 控制一个线程生命周期最常用的方法:

(1)start():线程调用该方法启动一个线程,使之从新建状态进入就绪队列,一旦获得CPU就可以脱离创建它的主线程独立开始自己的生命周期。

(2)run():线程的所有活动都是通过线程体run()方法定义,并实现线程对象被调用之后所执行的操作。

(3) stop():为强制终止某个线程的执行,Thread类提供了线程最后一个控制即停止方法stop()。

(4) suspend():在Java语言程序中经常需要挂起一个线程而不指定多少时间,此时可用Thread类提供的suspend()方法,这个方法并不永久地停止线程。

(5) resume():暂停的线程可以重新激活,重新激活的方法为resume()方法。由于suspend()包含了对象锁,所以它使线程极易出现死锁现象,即被锁住的对象在永久地等待resume()方法,对暂停的线程不能使用start()方法重新启动该线程。

(6) isAlive():一个线程已经启动而且没有停止则被认为是激活的,可通过测试线程来判断线程是否被激活。测试方法为isAlive(),如果isAlive()方法返回true,则该线程是激活的。

(7) sleep():Java语言Thread类中提供的sleep()方法是简单地告诉线程休息多少个毫秒的时间,如果想要推迟一个线程的执行,则可以使用sleep()方法。当线程睡眠的时候,sleep()方法并不占用系统资源,其他的线程仍然可以继续工作。一旦延迟时间完毕,则该休眠线程就被激活。sleep()方法的基本调用形式采用一个以毫秒为单位的参数,它使线程暂停一段规定的延时时间。当延时完成后,线程则继续工作。由于Java运行系统采用了线程调度机制,所以通过在run()的主循环中插入对sleep()的调用,一般都可以使Java语言程序运行得更快一些。因为在正在运行的线程准备进入休眠状态之前,较短的延迟可能造成sleep()结束调度机制的中断,强迫调度机制将其中止,并于稍后重新启动,以便该线程能做完它自己的事情,再进入休眠状态。

(8)wait()和notify():当编写的Java语言程序被分成几个逻辑线程时,需要清晰地知道这些线程之间应怎样相互通讯。例如网络数据交换程序经常需要使用Sleep()、Wait()、Suspend()等方法使一个线程进入等待状态,同时需要另一个线程使用Notify()、Resume()等方法将等待线程激活。 

2、线程的创建和启动

创建线程有两种方法:通过继承Thread类或者继承Runnable接口来实现。

第一种:

1)定义现场类实现Runnable接口。

2)Thread myThread = new Thread(target)。

3)Runnable中只有一个方法:public void run()。

4)使用Runnable接口可以为多个线程提供数据的共享。

5)在实现Runnable接口的类的run方法定义中可以使用Thread的静态方法public static Thread currentThread()读取当前线程的引用。

第二种:

1)可以顶一个Thread的之类并重写其run方法如:

Class MyThread extends Thread {

public void run(){...}

}

2)然后生成该类的对象

MyThread myThread = new MyThread(...)

3、 线程的同步与死锁

3.1 同步的概念 

首先我们需要了解什么是线程同步,因为在计算机中同一时间cup可能要处理很多线程,但如果一个劲地执行一个线程也不是一回事呀,如果这个线程要用一天时间,那么其它线程岂不是没有机会执行了。针对这个问题,我们就提出了线程同步的概念,线程同步就是在一段时间内每个线程都执行一次。那么怎么才可以都能执行一次呢?就是不断地交替执行。例如,在计算机中有3个线程A、B、C需要执行,假设从A开始执行,则在A执行一段时间后即使没有执行完,也要停止让B来执行,接着如果时间到了B即使也没有执行完,但必须让出位置让C来执行。就这样一直循环下去,直到所有的线程都执行完为止。当然,如果是多个线程抢占一个资源道理也是一样的。

在java语言中,引入了对象互斥锁的概念,保证共享数据操作的完整性,关键字synchronized来与对象的互斥锁联系。当某个对象synchronized修饰时,表明该对象在任一时刻只能由一个线程访问。我们只需针对方法提出一套机制,这套机制就是 synchronized 关键字,它包括三种用法:synchronized 方法、 synchronized 块和synchronized 类。

synchronized 块:通过 synchronized关键字来声明synchronized 块。语法如下:

synchronized(syncObject) {

//允许访问控制的代码

}

synchronized 类:通过 synchronized关键字来声明类。语法如下:

Synchronized class {

//类内的方法全部为synchronized 的

}

synchronized 块是这样一个代码块,其中的代码必须获得对象 syncObject 的锁方能执行,具体机制同前所述。由于可以针对任意代码块,且可任意指定上锁的对象,故灵活性较高。 

3.2 线程同步举例

public class TestSync implements Runnable {

Timer timer = new Timer();


public static void main(String[] args) {

TestSync test = new TestSync();

Thread t1 = new Thread(test);

Thread t2 = new Thread(test);

t1.setName("t1");

t2.setName("t2");

t1.start();

t2.start();

}


public void run() {

timer.add(Thread.currentThread().getName());

}

}


class Timer {

private static int num = 0;


public synchronized void add(String name) { // 执行这个方法当前对象被锁定

// synchronized(this) {//锁定当前对象

num++;

try {

Thread.sleep(1);

} catch (InterruptedException e) {

}

System.out.println(name + ",你是第" + num + "个使用timer的线程");

// }

}

}

运行结果:

t1,你是第1个使用timer的线程

t2,你是第2个使用timer的线程

3.3 线程死锁 

线程死锁就是在cpu中多个线程同时都想使用同一个资源,这样他们谁都想得到这资源,但都不肯放手,所以最后谁都得不到,就这样一种耗下去,所以就出现了死锁状态。为了解决这种情况,Java就要通过各种机制来预防这种情况的发生。

        Sleep()方法:就是在多个线程抢占资源时,让优先级较低的线程先睡一会,就是让它先暂停执行,这个方法最大的优点就是可以认为设置让该线程暂时的时间长短,在sleep()里面是以毫秒为参数的。

        Suspend()和resume()方法:两个方法配套使用,suspend()使得线程进入阻塞状态,并且不会自动恢复,必须其对应的resume()被调用,才能使得线程重新进入可执行状态。典型地,suspend()和 resume()被用在等待另一个线程产生的结果的情形:测试发现结果还没有产生后,让线程阻塞,另一个线程产生了结果后,调用resume()使其恢复。

        yield()方法:yield()使得线程放弃当前分得的 CPU 时间,但是不使线程阻塞,即线程仍处于可执行状态,随时可能再次分得CPU 时间。调用 yield()的效果等价于调度程序认为该线程已执行了足够的时间从而转到另一个线程。

        wait()和 notify()(notifyAll())方法:两个方法配套使用,wait()使得线程进入阻塞状态,它有两种形式,一种允许指定以毫秒为单位的一段时间作为参数,另一种没有参数,前者当对应的 notify()被调用或者超出指定时间时线程重新进入可执行状态,后者则必须对应的 notify()被调用。

关于 wait()和 notify()方法最后再说明两点:

(1)调用 notify()方法导致解除阻塞的线程是从因调用该对象的 wait()方法而阻塞的线程中随机选取的,我们无法预料哪一个线程将会被选择,所以编程时要特别小心,避免因这种不确定性而产生问题。

(2)除了 notify(),还有一个方法 notifyAll()也可起到类似作用,唯一的区别在于,调用 notifyAll()方法将把因调用该对象的wait()方法而阻塞的所有线程一次性全部解除阻塞。当然,只有获得锁的那一个线程才能进入可执行状态。

Java学习资料-线程