多线程
一,线程的一些基本知识。
进程与线程
所有的操作系统都支持同时运行多个任务,一个任务通常就是一个程序,每个运行中就是一个进程,当一个程序运行时,内部可能包含了多个顺序执行流,每个顺序执行流就是一个线程。
进程(process)
当一个程序进入内存运行即变成一个进程,进程处于运行过程中的程序,并且具有一定的独立功能,进程是系统进行资源分配和调用的独立单位,进程切换开销大。
多进程
在操作系统中,能同时运行多个任务程序。
进程包含三大特征
1,独立性:
进程是系统中独立存在的实体,它可以拥有自己独立的资源,每一个进程都拥有自己私有的地址空间,没有经过进程本身允许的情况下,一个用户不可以直接访问其他进程的地址空间。
2,动态性:
进程与程序的区别在于程序只是个静态的指定集合,而进程是一个正在系统中活动的指定集合,在进程中加入了时间的概念,进程具有自己的生命周期和各种不同的状态,这些状态在程序中是不具备的。
3,并发性:
多个线程可以在单个处理器是并发执行多个进程,进程之间,不会互相影响。
注:
对于一个CPU而言,它在某个时间点上只能执行一个程序,就是说只能运行一个进程。CPU不断在这些进程之间轮回切换,CPU的执行速度相对于我们的感觉太快,我们感觉不到,所以CPU在多个进程之间轮回执行,但我们人类感觉好像多个进程同时执行。
线程(Thread)
线程被称为轻量级的进程,线程是进程的执行单元,就像进程在操作系统中的地位一样。线程在程序中是独立的,并发的执行的流。当进程被初始化后,主线程就被创建了,一般的应用程序来说,通常仅要求有一个主线程,但我们也可以在该进程内创建多条顺序执行流,这些顺序执行流就是线程,每条线程是相互独立的。
注:
总而言之,一个程序运行后,至少有一个进程,一个进程里可以包含多个线程,但至少要包含一个线程。
多线程(multithreading)
在同一个应用程序中,有多个顺序执行流同时执行。
线程比进程具有更高的性能,这是由于同一个进程中的线程都有共性,多个线程将共享同一个进程的虚拟空间,-线程共享的环境包括进程代码段,进程的公有数据等,利用这些共享的数据,线程很容易实现相互之间的通信。
当操作系统创建一个进程时,该进程必须分配独立的内存空间,分配大量相关资料,但创建一个线程简单的多,因此使用多线程来实现并发比使用多进程来实现并发的性能要高得多。
多线程的优点
1,进程之间不能共享内存,但线程之间共享内存非常容易。
2,系统创建进程需要为该进程重新分配系统资源,但创建线程代价要小的多,因此使用多线程来实现多个任务并发比多进程要效率高。
3,Java语言中内置多线程功能的支持,而不是单纯的作为底层的操作系统的调动方式,从而简化了Java的多线程编程。
二,线程的启动和创建
Thread类(Java.lang.Thread类)
在Java语言中Thread类代表线程,所有的线程对象都必须是Thread类或其子类的实例。每条线程的作用是完成一定的任务,实际上就是执行一段程序流,也叫一段顺序执行的代码,Java使用run方法来封装这样一段程序流。
继承Thread类创建和启动线程
1,定义Thread类的子类,并重写该类的run方法,该run方法的方法体就是代表了线程需要完成的任务,run方法也被称为线程执行体。
2,创建Thread类子类的实例,即创建了线程的对象。
3,用线程对象的start方法来启动该线程。
start()方法
使该线程开始执行,Java虚拟机调用该线程的run方法。
getName()方法
返回该线程的名称
setName(String name)方法
修改该线程的名称
interrupt();
中断线程
sleep(long millis);
在指定的毫秒内让当前正在执行的线程休眠.
1,实现Runable接口的实现类,并重写该接口的run方法,该run方法的方法体同样是该线程的执行体。
2,创建Runable实现类的实例,并以此实例作为Thread类的目标来创建Thread类的目标创建Thread对象,该Thread对象才是真正的线程对象。
Thread类和Runable接口创建线程的对比
继承Thread类方式的多线程的优点:
编写简单,如果访问当前线程,无需使用Thread.currentThread方法,直接使用this,即可获得当前线程。
继承Thread类方式的多线程缺点:
因为线程已经继承了Thread类,所以不能再继承其他的类。
实现Runable接口的优点:
线程只是实现了Runable接口,还可以继承其他的类,在这种方式下,可以多个线程共享同一个目标(target)对象,所以非常适合多个线程来处理同一份资源的情况,从而可以将CPU代码和数据分开,形成清晰的模型,体现了面向对象的思想。
实现Runable接口的缺点:
编程稍微复杂,如果需要访问当前线程,必须使用Thread.currentThread方法。
三,线程的生命周期
当线程被创建并启动以后,它既不是一启动就进入了执行的状态,也不是一直处于执行状态,在线程的生命周期里,它要经过新建,就绪,运行,阻塞,死亡五种状态。尤其是当线程启动以后,它不能一直霸占着CPU独立运行,所以CPU多条线程之间切换,于是线程状态也会多次在运行和阻塞之间切换。
新建或就绪状态
启动线程使用start方法,而不是run方法,永远不用调用线程对象的run方法,调用start方法来启动线程系统会把该run方法当成线程执行体来处理。但如果直接调用线程对象的run方法,则run方法立即会被执行,而且在run方法返回之前其他线程无法并发执行,也就是说系统吧线程对象当成一个普通的对象,而run方法也是一个普通方法,而不是线程执行体。
当线程对象调用了start方法之后,该线程处于就绪状态,JVM会为创建方法调用栈和线程计数器处于这个状态中的线程,并没有开始运行,它只是表示该线程可以运行了,至于该线程何时开始运行,取决于JVM里线程调度器的调度。
运行和阻塞状态
如果处于就绪状态的线程获得了CPU资源,开始执行run方法的执行体,则该线程处于运行状态,当一条线程开始运行后,它不可能一直处于运行状态,线程在运行的过程中,需要被中断,目的是使其他线程获得执行的机会,当发生以下情况下,线程将会进入阻塞状态;
1,线程调用sleep方法主动放弃所占有的CPU资源。
2,线程调用一个阻塞式IO方法,该方法返回之前该线程被阻塞。
3,线程试图获得一个同步监视器,但该同步监视器正被其他线程所占据。
4,线程在等待某个通知(ntify,ntifyAll方法 在Objiet类里)。
5,程序调用了线程的suspend方法,将线程挂起暂停),不过这个方法容易导致死锁,所以程序尽量避免该方法。
线程重新进入就绪状态有以下情况:
1,调用sleep方法的线程经过指定的时间。
2,线程调用阻塞式IO方法已经返回。
3,线程成功的获得了试图取得同步监视器。
4,线程正在等待某个通知,其他线程发出了通知。
5,处于挂起状态的线程被调用了resume方法恢复。
注:
阻塞状态也叫不可运行状态
调用yield方法可以让当前运行的线程转入就绪状态。
线程会在以下三种方式之一结束线程,处于死亡状态:
1,run方法执行完成,线程正常结束。
2,线程抛出一个未捕获的Exception或Error。
3,直接调用线程的stop方法来结束该线程,该方法容易导致死锁,通常不推荐用。
isAlive方法
测试某条线程是否已经死亡,当线程处于就绪,运行,阻塞三种状态时,该方法返回true,当该线程处于创建,死亡两种状态时,该方法返回false。
注:
不要试图对一个已经死亡的线程调用start方法,使它重新启动,该线程将不可再次作为线程执行,它会抛出IllegaThreadStateException异常。
不要让处于死亡状态的线程调用start方法,程序只能对新建状态的线程调用start方法,对新建状态的线程两次调用start方法也是错误的。
join()方法
等待被join的线程执行完成。
join(long millis)方法
等join的新车的时间最长为minllis毫秒,如果在millis毫秒内被join的线程还没有执行结束,则不再等待。
join(long millis ,int nanos)方法
等待被join的时机最长为millis毫秒加nanos纳秒。
setPriority()和方法:
来设值和取值,
返回线程的优先级,其中setPriority可以是个整数,范围是一到十之间,也可以是MAX_PRIORITY ,MIN_PRIORITY ,NORM_PRIORITY 三个静态常量。
getPriority()方法
返回线程的优先级。每个线程执行时,都具有一定的优先级,优先级高的线程获得较多的执行机会,而优先级低的线程则获得较小的执行机会。
后台线程(DaemonThread)
有一种线程它是在后台运行的,它的任务是为其他线程提供服务,这种线程称为后台线程,又称为守护线程,又称为精灵线程。JVM的垃圾回收线程就是典型的后台线程。后台线程有个特征:如果所有的前台线程都死亡,后台线程会自动死亡。调用Thread对象的setDaemon(blooean on)方法(参数传true),可将指定线程设置为后台线程,isDaemon()判断该线程是否为后台线程,如果该线程是守护线程,则返回 true;否则返回 false。
sleep(long millis)方法(线程睡眠)
让当前正在执行的线程暂停并进入阻塞状态,该方法受到系统计时器和线程调度器的精度和准确度的影响。
注:
当前线程调用sleep方法,进入阻塞状态后,在sleep时间段内,该线程不会获得执行的机会,即使系统中没有其他可运行的线程,处于sleep时间段里的线程也不会运行。因此sleep常用来暂停程序的执行,
yield()方法(线程让步)
yield方法和sleep有点相似的方法,让当前正在执行的线程暂停,但它不会阻塞该线程,它只是将该线程转入就绪状态,yield方法只是让当前线程暂停一下,让系统的线程调度器从新调度一次,当某个线程调用了yield方法暂停之后,只有优先级与当前线程相同,或者优先级比当前线程更高的就绪状态的线程才会获得执行的机会
sleep方法和yield方法的区别
1,sleep方法暂停当前线程后,会给其他线程执行机会,不会理会其他线程的优先级,但yield方法只会给优先级相同或优先级更高的线程执行机会。
2,sleep方法会将线程转入阻塞状态,直到经过阻塞时间,才会转入就绪状态,而yield方法不会将线程转入阻塞状态,它只是强制当前线程进入就绪状态,因此完全有可能某个线程用yield方法暂停之后立即再次获得处理器资源被执行。
3,sleep方法声明抛出 InterruptedException 异常。所有调用sleep方法时,要么扑捉该异常,要么显示声明抛出该异常。而yield方法则没有声明抛出任何异常。
4,sleep方法比yield方法有更好的可移植性,通常不用依靠yield方法来控制并发线程的执行。
interrupt()
使该线程中断,如果一个线程抛出异常,可以用interrupt在catch里中断该线程.
Thread()
分配新的 Thread 对象。
Thread(Runnable target)
分配新的 Thread 对象。
Thread(Runnable target, String name)
分配新的 Thread 对象。
Thread(String name)
static Thread currentThread()
返回对当前正在执行的线程对象的引用。
String getName()
返回该线程的名称。
void setName(String name)
改变线程名称,使之与参数 name 相同。
int getPriority()
返回线程的优先级。
void setPriority(int newPriority)
更改线程的优先级。
void interrupt()
中断线程。
boolean isAlive()
测试线程是否处于活动状态。
boolean isDaemon()
测试该线程是否为守护线程。
void join()
等待该线程终止。
void join(long millis)
等待该线程终止的时间最长为 millis 毫秒。
void run()
如果该线程是使用独立的 Runnable 运行对象构造的,则调用该 Runnable 对象的 run 方法;否则,该方法不执行任何操作并返回。
void setDaemon(boolean on)
将该线程标记为守护线程或用户线程。
void start()
使该线程开始执行;Java 虚拟机调用该线程的 run 方法。
static void yield()
暂停当前正在执行的线程对象,并执行其他线程。
ThreadGroup类
ThreadGroup类来表示线程组,它可以对一批线程进行分类管理。Java允许程序直接对线程组进行控制,
线程同步
Java里的多线程编程常常容易突然出现错误情况,这是由于系统的线程调度具有一定随机性,即使程序在运行过程中偶尔出现问题,是由于我们编程不当所引起的。当使用多个线程来访问同一个数据时,非常容易出现线程安全问题,所以我们用同步机制来解决这些问题。
实现同步机制有两个方法:
1,同步代码块:
synchronized(同一个数据){} 同一个数据:就是N条线程同时访问一个数据
2,同步方法:
public synchronized 数据返回类型 方法名(){}
就是使用 synchronized 来修饰某个方法,则该方法称为同步方法。对于同步方法而言,无需显示指定同步监视器,同步方法的同步监视器是 this 也就是该对象的本身,通过使用同步方法,可非常方便的将某类变成线程安全的类,具有如下特征:
1,该类的对象可以被多个线程安全的访问。
2,每个线程调用该对象的任意方法之后,都将得到正确的结果。
3,每个线程调用该对象的任意方法之后,该对象状态依然保持合理状态。
注:synchronized关键字可以修饰方法,也可以修饰代码块,但不能修饰构造器,属性等。
实现同步机制注意以下几点: 安全性高,性能低,在多线程用。性能高,安全性低,在单线程用。
1,不要对线程安全类的所有方法都进行同步,只对那些会改变共享资源方法的进行同步。
2,如果可变类有两种运行环境,当线程环境和多线程环境则应该为该可变类提供两种版本:线程安全版本和线程不安全版本(没有同步方法和同步块)。在单线程中环境中,使用线程不安全版本以保证性能,在多线程中使用线程安全版本.
Java.lang.object 里的三个方法wait() notify() notifyAll()
wait方法导致当前线程等待,直到其他线程调用同步监视器的notify方法或notifyAll方法来唤醒该线程。
wait(mills)方法
都是等待指定时间后自动苏醒,调用wait方法的当前线程会释放该同步监视器的锁定,可以不用notify或notifyAll方法把它唤醒。
notify()
唤醒在同步监视器上等待的单个线程,如果所有线程都在同步监视器上等待,则会选择唤醒其中一个线程,选择是任意性的,只有当前线程放弃对该同步监视器的锁定后,也就是使用wait方法后,才可以执行被唤醒的线程。
notifyAll()方法
唤醒在同步监视器上等待的所有的线程。只用当前线程放弃对该同步监视器的锁定后,才可以执行被唤醒的线程。
多线程
最新推荐文章于 2024-11-13 20:43:20 发布
声明:JavaEye文章版权属于作者,受法律保护。没有作者书面许可不得转载。