java线程的总结.docx
《java线程的总结.docx》由会员分享,可在线阅读,更多相关《java线程的总结.docx(15页珍藏版)》请在冰点文库上搜索。
java线程的总结
线程
Java中的进程、线程和多线程
一、进程:
进程就是一个正在执行的程序。
二、线程:
线程是进程执行的一条线索或路径。
进程中至少有一个线程存在。
三、多线程:
顾名思义,一个进程中的多个线程。
线程的开始:
当要运行一个程序时,JVM首先会找到main函数,然后从main函数开始执行(也就是说,程序是从main函数开始运行的),
此时,程序就成为一个进程,既然是进程肯定有线程的存在。
此时的线程就是主线程,主线程会向下顺序执行代码。
如果程序中存在一个庞大的循环语句,主程序就会一直在这里运行,直到循环语句结束,下面的代码才能被执行到。
这可能要花费较长的时间,影响了程序运行的效率。
所以,为了提高程序的效率,就引入了多线程。
由主线程开辟另一个或多个线程,
让这些线程都去执行代码。
线程线程是一个程序内部的控制流
java的线程是通过java.long.Thread类实现的,VM启动时会有一个主方法publicstaticvoidmain(){}的线程
1可以通过Thread的实例来创建一个新的线程,必须通过start来开始一个线程,每个线程都是通过特定的Thread对象来调用run方法来完成其操作的,方法run()称为线程体。
类直接继承Thread也可以形成线程,但是这样该类就只能继承一个类
而实现接口的话,可以实现多个接口,从而更加灵活。
2实现Runnable接口,复写run方法。
用Thread来new一个线程对象,并将Runnable的子类对象传入Thread的构造方法中。
我们用代码来模拟铁路售票系统,实现通过四个售票点发售某日某次列车的100张车票,一个售票点用一个线程表示。
我们首先这样编写这个程序:
1publicclassThreadDome1{
2 publicstaticvoidmain(String[]args){
3 ThreadTestt=newThreadTest();
4 t.start();
5 t.start();
6 t.start();
7 t.start();
8 }
9 }
10 classThreadTestextendsThread{
11 privateintticket=100;
12 publicvoidrun(){
13 while(true){
14 if(ticket>0){
15 System.out.println(Thread.currentThread().getName()+
16 "issalingticket"+ticket--);
17 }else{
18 break;
19 }
20 }
21 }
22 }
上面的代码中,我们用ThreadTest类模拟售票处的售票过程,run方法中的每一次循环都将总票数减1,模拟卖出一张车票,同时该车票号打印出来,直接剩余的票数到零为止。
在ThreadDemo1类的main方法中,我们创建了一个线程对象,并重复启动四次,希望通过这种方式产生四个线程。
从运行的结果来看我们发现其实只有一个线程在运行,这个结果告诉我们:
一个线程对象只能启动一个线程,无论你调用多少遍start()方法,结果只有一个线程。
我们接着修改ThreadDemo1,在main方法中创建四个Thread对象:
1publicclassThreadDemo1{
2 publicstaticvoidmain(String[]args){
3 newThreadTest().start();
4 newThreadTest().start();
5 newThreadTest().start();
6 newThreadTest().start();
7 }
8 }
9 classThreadTestextendsThread{
10 privateintticket=100;
11 publicvoidrun(){
12 while(true){
13 if(ticket>0){
14 System.out.println(Thread.currentThread().getName()+
15 "issalingticket"+ticket--);
16 }else{
17 break;
18 }
19 }
20 }
21 }
这下达到目的了吗?
从结果上看每个票号都被打印了四次,即四个线程各自卖各自的100张票,而不去卖共同的100张票。
这种情况是怎么造成的呢?
我们需要的是,多个线程去处理同一个资源,一个资源只能对应一个对象,在上面的程序中,我们创建了四个ThreadTest对象,就等于创建了四个资源,每个资源都有100张票,每个线程都在独自处理各自的资源。
经过这些实验和分析,可以总结出,要实现这个铁路售票程序,我们只能创建一个资源对象,但要创建多个线程去处理同一个资源对象,并且每个线程上所运行的是相同的程序代码。
在回顾一下使用接口编写多线程的过程。
1publicclassThreadDemo1{
2 publicstaticvoidmain(String[]args){
3 ThreadTestt=newThreadTest();
4 newThread(t).start();
5 newThread(t).start();
6 newThread(t).start();
7 newThread(t).start();
8 }
9 }
10 classThreadTestimplementsRunnable{
11 privateinttickets=100;
12 publicvoidrun(){
13 while(true){
14 if(tickets>0){
15 System.out.println(Thread.currentThread().getName()+
16 "issalingticket"+tickets--);
17 }
18 }
19 }
20 }
上面的程序中,创建了四个线程,每个线程调用的是同一个ThreadTest对象中的run()方法,访问的是同一个对象中的变量(tickets)的实例,这个程序满足了我们的需求。
在Windows上可以启动多个记事本程序一样,也就是多个进程使用同一个记事本程序代码。
实现Runnable接口相对于继承Thread类来说,有如下显著的好处:
(1)适合多个相同程序代码的线程去处理同一资源的情况,把虚拟CPU(线程)同程序的代码,数据有效的分离,较好地体现了面向对象的设计思想。
(2)可以避免由于Java的单继承特性带来的局限。
我们经常碰到这样一种情况,即当我们要将已经继承了某一个类的子类放入多线程中,由于一个类不能同时有两个父类,所以不能用继承Thread类的方式,那么,这个类就只能采用实现Runnable接口的方式了。
(3)有利于程序的健壮性,代码能够被多个线程共享,代码与数据是独立的。
当多个线程的执行代码来自同一个类的实例时,即称它们共享相同的代码。
多个线程操作相同的数据,与它们的代码无关。
当共享访问相同的对象时,即它们共享相同的数据。
当线程被构造时,需要的代码和数据通过一个对象作为构造函数实参传递进去,这个对象就是一个实现了Runnable接口的类的实例。
线程的状态与生命周期
1.新建状态当创建一个Thread类或子类的对象时,该线程称为新建的线程
2.就绪:
线程创建后,处于就绪状态,等待start()方法被调用
3.运行:
线程开始执行进入运行状态
4.睡眠:
线程的执行可以通过调用sleep()方法来暂时中止
5.等待:
如果调用了wait()方法,线程将处于等待状态。
用于两个或多个线程并发运行时
6.挂起(Suspended):
在临时停止或中断线程的执行时,线程就处于挂起状态
7.阻塞(Blocked):
在线程等待一个事件时,例如输入输出操作,就称其处于阻塞状态
8.死亡:
在run()方法已完成执行或stop()方法被调用后,线程处于死亡状态
优先级
设置线程的优先级,用setPriority()方法,该方法也是thread类成员。
它的通常形式为:
finalvoidsetPrority(intlevel)
这里level指定了对所调用的线程的新的优先权的设置。
level的值必须在MIN_PRIORITY到MAX_PRIORITY范围内。
通常,它们的值分别是1和10.要返回一个线程为默认的优先级,指定NORM_PRIORITY,通常值为5.这些优先级在Thread中都被定义为final型变量。
通过调用thread类的getPriority()方法可以获得当前的优先级设置。
方法如下:
finalintgetPriority()
如果t1的优先级比t2高,并不是说t2不执行,而是t1执行时间多点,t2执行时间少
正常停止一个线程的方法,不是调用interrupt,stop,而是定义一个变量,bolleanf=true;
classMyThreadextendsThread{
publicvoidrun(){
bolleanf=true;
while(true){
System.out.println("1");
try{
sleep(1000);
}catch(InterruptedExceptione){
return;
}
多线程的安全问题
什么时候会出现线程安全问题?
满足3个条件时,会出现:
1.存在多个线程
2.多个线程共享数据(共享数据一般是成员变量,局部变量不是)。
3.操作共享数据的语句至少要有两条。
出现安全问题的原因:
多个线程执行共享数据的代码块时,其中的一个线程还没有执行完代码块,另一个线程就开始执行代码块,这会造成共享数据的错误。
从而出现安全问题。
多线程安全问题的解决:
同步。
因为同步可以保证多线程代码只能被持有锁的线程运行,其它线程不能运行。
任意时刻,一个锁只能被一个线程拥有。
(持有锁的线程会在执行完多线程代码时释放锁,这样锁就能被其它线程拥有。
)
1.同步代码块;将要同步的代码放synchronized中,并加锁。
synchronized(对象)//只要是个对象就行,这个对象就是锁
{要被同步的代码}
2.同步函数:
synchronized能将代码封装并同步,函数只能将代码封装,所以用synchronized修饰函数,让函数既能封装又能同步。
同步函数也有锁。
非静态同步函数的锁是this,同步函数所属对象的引用;静态同步函数的锁是:
类名.class,函数所属类的字节码文件对象。
需注意的是:
1.不需要同步的代码不要放入同步函数中。
2.synchronized放在函数返回值类型前
publicsynchronizedvoidshow()
{要被同步}
找到要被同步的代码的方法:
1.先找到多线程所有要运行的代码(可能是一个run方法被多个线程使用同,也可能是一个run方法只被其中的一个线程使用);
2.多线程的共享数据(一般成员变量都是);
3.多线程要运行的代码中操作共享数据的代码就是要被同步的代码。
注意:
有时使用了同步仍然不成功,出现这种现象的原因可能是:
需要被同步的代码可能放在程序的不同位置,有的代码要用到同步代码块,有的要用同步函数
那么怎么才能使同步成功?
1.首先判断要被同步的代码是否正确,确定所有要被同步的代码都被同步了。
2.同步代码块和同步函数中的锁是同一个锁,即必须是多个线程使用同一个锁
线程同步的问题
1.方法声明时使用,放在范围操作符(public等)之后,返回类型声明(void等)之前。
即一次只能有一个线程进入该方法,其他线程要想在此时调用该方法,只能排队等候,当前线程(就是在synchronized方法内部的线程)执行完该方法后,别的线程才能进入。
锁定后别的线程不能调用该方法B,但是其他线程可以调用别的未锁定的方法A,访问方法A就有可以改变方法B中的变量。
解决方法时,将两个方法全部加锁.
针对某变量,想要同步,涉及改该变量的方法都要加锁,而读该变量的方法不需要加锁
publicclassTTimplementsrunnable{
intm=100;
publicsynchronizedvoidm1()throwsException{
m=1000;
Tread.sleep(5000);
System.out.println(m);
}
publicvoidm2{
m=2000;}
publicvoidrun(){
try{m1();}catch(){}
}
}
publicstaticvoidmian(String[]args)throwsException{
TTtt=newTT();
Threadt=newThread(tt);
t.start();
tt.m2();
System.out.println(m);
}
进程t先锁定m1方法,改变m为1000,在睡眠,主线程继续执行,通过m2方法改变m为2000,最后m1,m2方法的打印m均为2000.
2.对某一代码块使用,synchronized后跟括号,括号里是变量,这样,一次只有一个线程进入该代码块。
publicintsynMethod(inta1){
synchronized(a1){//一次只能有一个线程进入}
}
3.synchronized后面括号里是一对象,此时,线程获得的是对象锁。
例如:
publicclassMyThreadimplementsRunnable{
publicstaticvoidmain(Stringargs[]){
MyThreadmt=newMyThread();
Threadt1=newThread(mt,"t1");
Threadt2=newThread(mt,"t2");
Threadt3=newThread(mt,"t3");
t1.start();
t2.start();
t3.start();
}
publicvoidrun(){
synchronized(this){
System.out.println(Thread.currentThread().getName());
}
}
}
对于3,如果线程进入,则得到对象锁,那么别的线程在该类所有对象上的任何操作都不能进行。
在对象级使用锁通常是一种比较粗糙的方法。
为什么要将整个对象都上锁,而不允许其他线程短暂地使用对象中其他同步方法来访问共享资源?
如果一个对象拥有多个资源,就不需要只为了让一个线程使用其中一部分资源,就将所有线程都锁在外面。
由于每个对象都有锁,可以如下所示使用虚拟对象来上锁:
classFineGrainLock{
MyMemberClassx,y;
Objectxlock=newObject(),ylock=newObject();
publicvoidfoo(){
synchronized(xlock){//accessxhere
}
synchronized(ylock){//accessyhere
}
}
publicvoidbar(){
synchronized(this){
//accessbothxandyhere
}
4.synchronized后面括号里是类。
例如:
classArrayWithLockOrder{
privatestaticlongnum_locks=0;
privatelonglock_order;
privateint[]arr;
publicArrayWithLockOrder(int[]a)
{arr=a;
synchronized(ArrayWithLockOrder.class){//--------这里类
num_locks++;//锁数加1
lock_order=num_locks;}//为此对象实例设置唯一的lock_order。
}
publiclonglockOrder()
{returnlock_order;}
publicint[]array()
{returnarr;}
}
classSomeClassimplementsRunnable
{
publicintsumArrays(ArrayWithLockOrdera1,
ArrayWithLockOrdera2)
{
intvalue=0;
ArrayWithLockOrderfirst=a1;//保留数组引用的一个
ArrayWithLockOrderlast=a2;//本地副本。
intsize=a1.array().length;
if(size==a2.array().length)
{
if(a1.lockOrder()>a2.lockOrder())//确定并设置对象的锁定
{//顺序。
first=a2;
last=a1;
}
synchronized(first){//按正确的顺序锁定对象。
synchronized(last){
int[]arr1=a1.array();
int[]arr2=a2.array();
for(inti=0;ivalue+=arr1[i]+arr2[i];
}
}
}
returnvalue;
}
publicvoidrun(){
//...
}
}
对于4,如果线程进入,则线程在该类中所有操作不能进行,包括静态变量和静态方法,实际上,对于含有静态方法和静态变量的代码块的同步,我们通常用4来加锁。
以上4种之间的关系:
锁是和对象相关联的,每个对象有一把锁,为了执行synchronized语句,线程必须能够获得synchronized语句中表达式指定的对象的锁,一个对象只有一把锁,被一个线程获得之后它就不再拥有这把锁,线程在执行完synchronized语句后,将获得锁交还给对象。
在方法前面加上synchronized修饰符即可以将一个方法声明为同步化方法。
同步化方法在执行之前获得一个锁。
如果这是一个类方法,那么获得的锁是和声明方法的类相关的Class类对象的锁。
如果这是一个实例方法,那么此锁是this对象的锁。
synchronzied块后面跟类的具体详细例子、publicclassDB2_JDBCFactory{privatestaticDB2_JDBCFactoryinstance=null;publicstaticfinalThreadLocalthreadLocal=newThreadLocal();privateDB2_JDBCFactory(){
}publicstaticDB2_JDBCFactorygetInstance(){
if(instance==null){
synchronized(DB2_JDBCFactory.class){//synchronized后面跟一个类
instance=newDB2_JDBCFactory();
}
}
returninstance;
}publicConnectiongetConnection_JNDI_localhost(){
Connectionc=(Connection)threadLocal.get();
try{
if(c==null||c.isClosed()){
InitialContextctx=newInitialContext();
DataSourceds=(DataSource)ctx.lookup("java:
comp/env/jdbc/localhost");
c=ds.getConnection();
threadLocal.set(c);
}
}catch(Exceptionex){
System.err.println("getConnection_JNDIInitialfailed."+ex);
returnnull;
}
returnc;
}}外面的对象访问这个类的需要通过调用它的getInstance()
死锁的例子
publicclassTestDeadLockimplementsRunnable{
publicintflag=1;
staticObjecto1=newObject();
staticObjecto2=newObject();
publicvoidrun(){
System.out.println("flag="+flag);
/*
if(flag==1){
synchronized(o1)
{
try{Thread.sleep(5000);}
catch(Exceptione){e.printStackTrace();}
}
synchronized(o2)
{
System.out.println("1");
}
}//这种情况是if语句中的两个锁并列,即便没有o2的锁,o1的语句也一样能执行完毕,锁在执行完毕后立即返还,可以供别的对象使用。
if(flag==1){
synchronized(o1)
{
try{Thread.sleep(5000);}
catch(Exceptione){e.printStackTrace();}
synchronized(o2){System.out.println("1");}
}
}//此时有了O1的锁还必须要O2的锁,才能执行完O1语句,从而返还锁O1.