多线程.docx
《多线程.docx》由会员分享,可在线阅读,更多相关《多线程.docx(30页珍藏版)》请在冰豆网上搜索。
![多线程.docx](https://file1.bdocx.com/fileroot1/2023-2/8/ebff7057-7e31-4784-a7f5-8dcc465b2040/ebff7057-7e31-4784-a7f5-8dcc465b20401.gif)
多线程
多线程
程序:
是计算机指令的集合.程序是一组静态的指令集,不占用系统运行资源,不能被系统调度,也不能作为独立运行的单位,它以文件的形式存储在磁盘上。
进程:
是一个程序在其自身的地址空间中的一次执行活动。
比如,打开一个记事本,就是调用了一个进程。
进程是资源申请、调度和独立运行的单位,因此,它使用系统中的运行资源;而程序不能申请系统资源,一个程序可以对应多个进程。
线程:
又称为轻量级进程,是进程中的一个单一的连续控制流程。
它和进程一样拥有独立的执行控制,由操作系统负责调度,区别在于线程没有独立的存储空间,而是和所属进程中的其它线程共享一个存储空间,这使得线程间的通信远较进程简单一个进程可以拥有多个线程。
多线程优点:
可以更好的实现并行。
恰当地使用线程时,可以降低开发和维护的开销,并且能够提高复杂应用的性能。
CPU在线程之间开关时的开销远比进程要少得多。
因开关线程都在同一地址空间内,只需要修改线程控制表或队列,不涉及地址空间和其他工作。
创建和撤销线程的开销较之进程要少。
多线程的性能比多进程高,在实际工作中如果需要在有限的时间内出来完大量的数据,可以考虑开启多个线程,比如电信项目每个月末需要计算所有的话单量,假设总的话单量有4000万,为了提高效率,可以考虑开启4个进程或者4个线程。
依次进行处理,比如第一个进程处理1-1000万,第二个进程处理1001到-2000万,依次类推。
也可以考虑开启多个线程,第一个线程处理1-1000万,第二个线程处理1001到-2000万,依次类推。
在同样资源的情况下开启多个线程的效率是大于开启多个进程的。
因为线程之间通过对CPU的抢占可以实现资源共享,而进程之间无法实现资源共享。
可能有的进程内存不够,而有的线程又用不完。
Java在语言级提供了对多线程程序设计的支持。
多线程操作会增加程序的执行效率。
各线程之间切换执行,时间比较短,看似是多线程同时运行,但对于执行者CPU来说,某一个时刻只有一个线程在运行。
只是CPU切换的过程比较快,感觉像是多个线程并行。
那么单线程和多线程之间到底有什么区别呢?
先看一个单线程的例子:
publicclassSingleThread{
publicstaticvoidmain(String[]args){
SingleThreadsingleThread=newSingleThread();
singleThread.run();
while(true){
System.out.println("inthemainmethod..");
}
}
publicvoidrun(){
while(true){
System.out.println("intherunmethod..");
}
}
}
以上函数的run方法中有个while(true),判断条件永远为真,所以持续会打印"intherunmethod.."字符串,入口函数中的"inthemainmethod.."永远不会被执行到。
下面看一个多线程的实例,
publicclassMutipleThreadextendsThread{
publicstaticvoidmain(String[]args){
MutipleThreadsingleThread=newMutipleThread();
singleThread.start();
while(true){
System.out.println("inthemainmethod..");
}
}
publicvoidrun(){
while(true){
System.out.println("intherunmethod..");
}
}
}
运行程序打印结果:
inthemainmethod..
intherunmethod..
inthemainmethod..
intherunmethod..
inthemainmethod..
intherunmethod..
inthemainmethod..
intherunmethod..
inthemainmethod..
intherunmethod..
intherunmethod..
以上就是一个多线程的实例,与单线程相比,区别是
1.类继承(extends)Thread
2.通过调用start()方法启动线程,而不是直接调用run方法。
注意以上“inthemainmethod..”与“intherunmethod..”不一定会按照特定的顺序输出,因为不确定谁会抢占到CPU
编写线程
编写线程有2中方法
ExtendsThread
不管是以上的哪种方式,都必须要提供run方法。
Thread是线程里面的基类,如果我们想要编写多线程,可以继承它。
在编写多线程的时候必须要提供一个run方法,run方法是多线程的入口方法,相当我们常用的main入口函数。
调用多线程程序时必须要通过调用start方法来启动线程。
先看一个最基本的多线程实例
publicclassFirstThreadextendsThread{
publicstaticvoidmain(String[]args){
FirstThreadfirstThread=newFirstThread();
firstThread.start();
}
publicvoidrun(){
System.out.println("firstthread..");
}
}
打印结果:
firstthread..
一般情况下需要使用多线程来做的任务一般情况下是需要比较常的运行时间,为了体现出多线程的特点经常会在run方法中写入死循环或者for循环语句。
实际上多数的的服务器比如在学习javaEE时讲到的Tomcat启动后就是一个死循环的多线程。
如果想要持续提供给外部服务,就必须一直在运行。
ImplementsRunnable
使用实现Runnable的方式需要继承Runnable,产生实现了Runnable接口的对象,把产生的对象传递给Thread在调用Thread的start方法即可。
publicclassImplementsRunnableDemoimplementsRunnable{
publicstaticvoidmain(String[]args){
ImplementsRunnableDemoird=newImplementsRunnableDemo();
Threadthread=newThread(ird);
thread.start();
}
publicvoidrun(){
while(true){
System.out.println("intherunmethod...");
}
}
}
以上两种方法都是通过调用start来启动一个线程,可以产生多个多线程对象,通过在每个对象上调用start方法,就可以启动多个线程,注意不能在一个对象上调用两次start方法否则会出现:
java.lang.IllegalThreadStateException
通过以上实现多线程的两种方式大家对多线程应该有了大概的了解。
使用extends继承以后不能在继承其他的类,如果使用implements还可以继续在继承其他的类。
在做实际的使用中使用第2种的情况比较多。
那么为什么要提供2种不同的方式呢?
他们之间到底有什么样的区别的。
下面会详细解释。
现在还是从继承Thread入手
publicclassExtendsThreadDemoextendsThread{
privateintamount=10;
publicstaticvoidmain(String[]args){
ExtendsThreadDemoetd0=newExtendsThreadDemo();
etd0.start();
ExtendsThreadDemoetd1=newExtendsThreadDemo();
etd0.start();
}
publicvoidrun(){
while(amount>0){
System.out.println("amount:
"+(amount--));
}
}
}
运行程序打印输出:
amount:
10
amount:
9
amount:
8
amount:
7
amount:
6
amount:
5
amount:
4
amount:
3
amount:
2
amount:
1
amount:
10
amount:
9
amount:
8
amount:
7
amount:
6
amount:
5
amount:
4
amount:
3
amount:
2
amount:
1
以上程序中定义一个amount的实例变量,运行的时候先来判断amount的值,如果大于0则进行减操作。
由于在入口函数中产生了两个ExtendsThreadDemo对象,则存在两个amount实例,并且都是从10依次递减到1.
下面在使用implementsRunnable来实现。
publicclassImplementsRunnableDemoimplementsRunnable{
privateintamount=10;
publicstaticvoidmain(String[]args){
ImplementsRunnableDemoird=newImplementsRunnableDemo();
Threadthread0=newThread(ird);
thread0.start();
Threadthread1=newThread(ird);
thread1.start();
}
publicvoidrun(){
while(amount>0){
System.out.println("amount:
"+(amount--));
}
}
}
运行程序打印输出:
amount:
10
amount:
9
amount:
8
amount:
7
amount:
6
amount:
5
amount:
4
amount:
3
amount:
2
amount:
1
以上程序中只产生了一个ImplementsRunnableDemo实例,仅有一个amount实例变量,所以虽然我们开启了2个线程,但实际上操作的是同样一个amount。
所以只有一个从10到1的打印输出。
第2中比较典型的应用更是卖票系统,请大家思考下火车站在卖车票的时候可能开启了多个窗口,但是实际上卖的都在卖用样的一组车票,同一个列出上不可能出现2次2车厢22号座位。
在这种情况下一定需要使用第2种方式实现。
新线程:
当利用new关键字创建线程对象实例后,它仅仅作为一个对象实例存在,JVM没有为其分配CPU时间片和其他线程运行资源;
就绪状态:
在处于创建状态的线程中调用start方法将线程的状态转换为就绪状态。
这时,线程已经得到除CPU时间之外的其它系统资源,只等JVM的线程调度器按照线程的优先级对该线程进行调度,从而使该线程拥有能够获得CPU时间片的机会。
运行态:
就绪态的线程获得cpu就进入运行态
等待/阻塞:
线程运行过程中被剥夺资源或者,等待某些事件就进入等待/阻塞状态,suspend()方法被调用,sleep()方法被调用,线程使用wait()来等待条件变量;线程处于I/O等待等,调用suspend方法将线程的状态转换为挂起状态。
这时,线程将释放占用的所有资源,但是并不释放锁,所以容易引发死锁,直至应用程序调用resume方法恢复线程运行。
等待事件结束或者得到足够的资源就进入就绪态
死亡状态:
当线程体运行结束或者调用线程对象的stop方法后线程将终止运行,由JVM收回线程占用的资源。
Sleep
在多线程运行的时候,可能遇到某个线程持续处于运行状态,其他线程没有机会运行,为了让其他线程有机会执行,可是使用Sleep方法,该方法的作用就是使当前线程暂时停止运行,让其他线程有更多的机会执行。
注意当我们调用了某个sleep方法的时候是处在了blocked状态,如果线程想再次进入运行状态必须从blocked到ready状态,而不是从blocked直接进入running状态。
就相当于在参加赛跑的中场,可能会休息一段时间,休息完后再到起跑点做好跑的动作,然后听到哨声响起再跑,而不会休息完后,从凳子上起来立刻就跑。
在Thread中有个Thread.currentThread().getName(),可以通过该方法得到当前运行线程的名字。
看以下程序:
publicclassSleepDemoimplementsRunnable{
publicstaticvoidmain(String[]args){
SleepDemosd=newSleepDemo();
Threadthread0=newThread(sd);
thread0.start();
Threadthread1=newThread(sd);
thread1.start();
}
publicvoidrun(){
while(true){
System.out.println("currentThread:
"+Thread.currentThread().getName());
}
}
}
currentThread:
Thread-1
currentThread:
Thread-1
currentThread:
Thread-1
currentThread:
Thread-1
currentThread:
Thread-1
currentThread:
Thread-0
currentThread:
Thread-1
currentThread:
Thread-0
currentThread:
Thread-0
currentThread:
Thread-0
currentThread:
Thread-0
currentThread:
Thread-0
currentThread:
Thread-0
currentThread:
Thread-0
currentThread:
Thread-0
currentThread:
Thread-0
currentThread:
Thread-0
currentThread:
Thread-0
currentThread:
Thread-0
currentThread:
Thread-0
currentThread:
Thread-0
currentThread:
Thread-0
currentThread:
Thread-0
currentThread:
Thread-0
currentThread:
Thread-0
currentThread:
Thread-0
currentThread:
Thread-0
从上面的程序中可以发现有时Thread-0执行很长时间,有时Thread-1运行较长时间,能不能使两个线程有比较公平的机会输出呢?
也就是说不能让其中的某个线程一直占用了CPU。
下面先run中增加一个sleep方法。
publicclassSleepDemoimplementsRunnable{
publicstaticvoidmain(String[]args){
SleepDemosd=newSleepDemo();
Threadthread0=newThread(sd);
thread0.start();
Threadthread1=newThread(sd);
thread1.start();
}
publicvoidrun(){
while(true){
try{
Thread.sleep(10);
}catch(InterruptedExceptione){
//TODOAuto-generatedcatchblock
e.printStackTrace();
}
System.out.println("currentThread:
"+Thread.currentThread().getName());
}
}
}
运行结果:
currentThread:
Thread-0
currentThread:
Thread-1
currentThread:
Thread-0
currentThread:
Thread-1
currentThread:
Thread-0
currentThread:
Thread-1
currentThread:
Thread-1
currentThread:
Thread-0
currentThread:
Thread-0
currentThread:
Thread-1
currentThread:
Thread-1
currentThread:
Thread-0
currentThread:
Thread-0
currentThread:
Thread-1
currentThread:
Thread-1
currentThread:
Thread-0
currentThread:
Thread-0
currentThread:
Thread-1
从运行结果已经可以看出Thread-0与Thread-1有比较平均的运行机会,但是注意不能够确保一定交替运行,因为CPU是处于抢占的,而且从抢到到运行还有个过程,在多线程中不能够保证先start的线程先得到执行。
Synchronized
在启动多个线程的情况下,有可能存在线程抢占资源的情况,使程序出现错误,先看以下实例。
publicclassSleepDemoimplementsRunnable{
intamount=10;
publicstaticvoidmain(String[]args){
SleepDemosd=newSleepDemo();
Threadthread0=newThread(sd);
thread0.start();
Threadthread1=newThread(sd);
thread1.start();
}
publicvoidrun(){
while(amount>0){
try{
Thread.sleep(10);
}catch(InterruptedExceptione){
//TODOAuto-generatedcatchblock
e.printStackTrace();
}
System.out.println("amount:
"+(--amount));
}
}
}
运行结果:
amount:
9
amount:
8
amount:
7
amount:
6
amount:
5
amount:
4
amount:
3
amount:
2
amount:
1
amount:
0
amount:
-1
由于启动过了两个线程,当amount=1时,第一个线程满足条件进入sleep,第二个线程线程进入了while判断,此时由于第一个线程仍处在sleep阶段,还没有执行—amount操作,此时amount仍等于1,当第一个线程执行—amount操作后,amount=0,但是此时第二个线程已经进入while判断,并且又执行了--amount操作,最后amount=-1;
如何避免这样的问题?
如果在—amount操作之前不让第二个线程进入即可避免这样的问题,在java提供了代码同步来实现,在同一时间内只有一个线程执行某段代码,这就是代码同步,synchronized。
下面对以上代码进行重构如下:
publicclassSleepDemoimplementsRunnable{
intamount=10;
publicstaticvoidmain(String[]args){
SleepDemosd=newSleepDemo();
Threadthread0=newThread(sd);
thread0.start();
Threadthread1=newThread(sd);
thread1.start();
}
publicsynchronizedvoidrun(){
while(amount>0){
try{
Thread.sleep(10);
}catch(InterruptedExceptione){
//TODOAuto-generatedcatchblock
e.printStackTrace();
}
System.out.println("amount:
"+(--amount));
}
}
}
运行结果:
amount:
9
amount:
8
amount:
7
amount:
6
amount:
5
amount:
4
amount:
3
amount:
2
amount:
1
amount:
0
以上代码只是在run方法上加了一个synchronized关键字,该关键字的作用就是在某段时间内只有一个线程执行run方法,如果当前线程没有执行run方法,别的线程都不能进入run方法执行。
在此例中必须是---amount操作执行完后,别的线程才能进行while(amount>0)判断。
当把synchronized关键字放在方法上时,称之为同步方法。
我们可以选择同步代码块的方式来实现代码同步。
重构以上代码如下所示:
publicclassSleepDem