java基础---->线程
一、进程与线程1、Java的线程实现
在Java中如果要想进行多线程代码的实现有两种方式:
·继承Thread类
·实现Runnable接口
1.1、继承Thread类当一个类需要按照多线程的方式处理时,可以让这个类直接继承自Thread类即可,而且继承的时候要覆写好Thread类中提供的run()方法:
启动一个线程并不是依靠run()方法而是start()方法。
class MyThread extends Thread { // 继承Thread类 private String name ; public MyThread(String name){ this.name = name ; } public void run(){ // 做为线程的主体 for(int x=0;x<100;x++){ System.out.println(this.name+ "运行,x = " + x) ; } }};public classThreadDemo01 { public static void main(String args[]){ MyThread mt1 = newMyThread("线程A") ; MyThread mt2 = newMyThread("线程B") ; MyThread mt3 = newMyThread("线程C") ; mt1.start() ; mt2.start() ; mt3.start() ; }};
此时通过start()方法执行线程的操作,操作中可以发现,每一个线程间都属于交替的运行状态,即:所有的线程都是交替运行的,且:那个线程抢到了CPU资源,那个线程就执行。
1.2实现Runnable接口线程实现的第二种手段,实际上就是可以实现Runnable接口来实现线程的操作类,Runnable接口定义如下:
public interfaceRunnable{ public void run() ;} class MyThread implements Runnable { // 实现Runnable接口 private String name ; public MyThread(String name){ this.name = name ; } public void run(){ // 做为线程的主体 for(int x=0;x<10;x++){ System.out.println(this.name+ "运行,x = " + x) ; } }};
线程确实已经实现了,但是需要注意的是,如果要想启动一个线程肯定是Thread类中的start()方法完成,观察Thread类中提供的构造方法:public Thread(Runnable target)通过构造发现,Thread类可以接收Runnable子类的对象,所以一切的线程都可以通过Thread类进行启动。
public class ThreadDemo02 { public static void main(String args[]){ MyThread mt1 = newMyThread("线程A") ; MyThread mt2 = newMyThread("线程B") ; MyThread mt3 = newMyThread("线程C") ; new Thread(mt1).start() ; new Thread(mt1).start() ; new Thread(mt2).start() ; new Thread(mt3).start() ; }};
此时,通过Thread类进行了线程的启动。
1.3、两种实现方式的区别对于Thread类和Runnable接口本身都是可以进行多线程的实现,那么两者到底该使用谁更好呢?
1、 继承局限:使用Runnable接口可以避免单继承的局限,而Thread类则有此局限;
2、 资源共享:使用Runnable接口实现多线程,可以实现资源(对象属性)的共享,而Thread类却无法实现。
|-此点只是相对而言,因为两者的此种区别是有其应用范围的。
范例:观察资源共享
class MyThreadextends Thread { private int count = 5 ; public void run(){ for(int x=0;x<50;x++){ if(this.count>0){ System.out.println("count= " + this.count--) ; } } }};public classThreadDemo03 { public static void main(String args[]){ new MyThread().start() ; new MyThread().start() ; new MyThread().start() ; }};
现在的程序中每一个线程都各自占有各自的count属性,所以并没有达到资源共享的目的,那么如果现在换成了Runnable呢?
class MyThreadimplements Runnable { private int count = 5 ; public void run(){ for(int x=0;x<50;x++){ if(this.count>0){ System.out.println("count= " + this.count--) ; } } }};public classThreadDemo04 { public static void main(String args[]){ MyThreadmt = new MyThread() ; new Thread(mt).start() ; new Thread(mt).start() ; new Thread(mt).start() ; }};
现在的代码中可以发现,count属性已经被所有的线程对象所共同拥有了。
class MyThreadimplements Runnable { private int count = 5 ; public void run(){ for(int x=0;x<50;x++){ if(this.count>0){ System.out.println("count= " + this.count--) ; } } }};public classThreadDemo04 { public static void main(String args[]){ MyThreadmt1= new MyThread() ; MyThread mt2 = new MyThread() ; MyThread mt3 = new MyThread() ; new Thread(mt1).start() ; new Thread(mt2).start() ; new Thread(mt3).start() ; }};
现在的程序中每一个线程都各自占有各自的count属性,
1.4、两种实现方式的联系从Thread类和Runnable接口中都可以发现,都必须同时覆写run()方法,那么两者的关系如何呢?观察Thread类的定义:
public class Threadextends Object implements Runnable
发现Thread类实际上是Runnable的子类。而且Thread类也要去接收Runnable其他子类的对象,而且所有的线程中,通过Runnable接口实现的线程类里面都是编写的具体功能,而并没有所谓的CPU调度,而真正意义上的CPU调度由操作系统完成(通过Thread类的start()方法调用的)。
Thread类要去协调操作系统,并且最终还要执行具体的线程主体的方法,而线程的主体呢,现在只专著于具体的功能实现,至于如何调度根本不管。
Thread代理自定义的线程类的对象,如图所示:
从图的关系上可以清楚的发现,现在在线程中应用的设计思路就是代理设计模式(USB)。
1.5、线程的状态每一个线程对象都要经历五个步骤:
1、 初始化:当创建了一个新的线程对象时
2、 等待:调用了start()方法
3、 执行:调用run()执行的操作的过程
4、 停止:因为所有的线程都需要进行CPU资源的抢占,那么当一个线程执行完部分代码要交出资源,留给其他线程继续执行。
5、 卸载:所有的线程的操作代码都执行完毕之后,就将线程对象卸载下来。
二、线程的操作方法2.1、命名和取得每一个线程实际上都可以为其设置名字,而且也可以取得每一个线程的名字:
·设置线程名称:public final void setName(String name)
·取得线程名称:public final String getName()
但是有一点也非常的麻烦,由于线程的操作不属于固定的状态,所以对于取得线程名称的操作,应该是取得的当前正在运行的线程名称才合适,那么可以通过如下的方法取得一个当前正在运行的线程对象:
·取得当前线程:public static Thread currentThread()
除了以上的设置名称的方法外,在Thread类中也提供了两个构造方法:
·public Thread(String name)
·public Thread(Runnable target,String name)
注意:一般都在线程启动前设置好名字,当然也可以为已经启动的线程修改名字或设置重名线程,不过这样不好。
class MyThreadimplements Runnable { public void run(){ for(int x=0;x<5;x++){ System.out.println(Thread.currentThread().getName()+ "运行,x = " + x) ; } }};public class NameDemo{ public static void main(String args[]){ MyThread mt = new MyThread(); new Thread(mt).start() ; new Thread(mt,"线程A").start() ; new Thread(mt).start() ; new Thread(mt).start() ; new Thread(mt).start() ; }};
本程序明白之后,下面再观察以下的程序输出:
class MyThreadimplements Runnable { public void run(){ for(int x=0;x<5;x++){ System.out.println(Thread.currentThread().getName()+ "运行,x = " + x) ; } }};public classNameDemo { public static void main(String args[]){ MyThread mt = new MyThread() ; new Thread(mt,"自定义线程").start() ; mt.run() ; // main线程 }};
结果: main运行,x = 0
自定义线程运行,x = 0
main运行,x = 1
自定义线程运行,x = 1
main运行,x = 2
自定义线程运行,x = 2
main运行,x = 3
自定义线程运行,x = 3
main运行,x = 4
自定义线程运行,x = 4
在本程序中发现出现了一个main线程,那么这个线程肯定是主方法产生的,之前一直强调,java本身是属于多线程的处理机制,所以每次java运行的时候,实际上都会启动一个JVM的进程。
那么既然是多线程的处理机制,实际上主方法是在一个JVM上产生的一个线程而已,那么一个JVM启动的时候至少启动几个线程呢?两个:main、GC。
2.2、线程的休眠所谓的休眠就是指减缓程序的运行速度,如果要休眠使用如下的方法:
·休眠:public static void sleep(long millis) throwsInterruptedException,指定休眠时间
class MyThreadimplements Runnable { public void run(){ for(int x=0;x<5;x++){ System.out.println(Thread.currentThread().getName()+ "运行,x = " + x) ; try{ Thread.sleep(300); }catch (Exception e){} } }};public classSleepDemo { public static void main(String args[]){ MyThread mt = new MyThread(); new Thread(mt,"自定义线程").start() ; mt.run() ; // main线程 }};
2.3、线程的优先级
实际上所有的线程启动之后并不是立刻运行的,都需要等待CPU进行调度,但是调度的时候本身也是存在“优先”级的,如果优先级高则有可能最先被执行。
如果要想设置优先级可以使用:publicfinal void setPriority(int newPriority)
这个优先级需要接收一个整型的数字,这个数字只能设置三个内容:
·最高优先级:public static final int MAX_PRIORITY
·中等优先级:public static final int NORM_PRIORITY
·最低优先级:public static final int MIN_PRIORITY
范例:观察优先级设置的影响
class MyThreadimplements Runnable { public void run(){ for(int x=0;x<5;x++){ System.out.println(Thread.currentThread().getName()+ "运行,x = " + x) ; } }};public classProDemo { public static void main(String args[]){ MyThread mt = new MyThread(); Thread t1 = new Thread(mt) ; Thread t2 = new Thread(mt) ; Thread t3 = new Thread(mt) ; t1.setPriority(Thread.MIN_PRIORITY); t1.start() ; t2.setPriority(Thread.MAX_PRIORITY); t2.start() ; t3.setPriority(Thread.NORM_PRIORITY); t3.start() ; }};
问题:主方法的优先级是什么?
public classMainDemo { public static void main(String args[]){ System.out.println(Thread.currentThread().getPriority()); //5 System.out.println("MAX_PRIORITY" + Thread.MAX_PRIORITY) ;//10 System.out.println("MIN_PRIORITY" + Thread.MIN_PRIORITY) ;//1 System.out.println("NORM_PRIORITY" + Thread.NORM_PRIORITY) ;//5 }};
主方法属于中等优先级。
三、线程的同步与死锁由于多个线程可以对同一个资源进行操作,那么就必须进行同步的处理,但是如果过多的引入了同步的处理,也可能造成死锁。
3.1、同步当多个线程同时进行一种资源操作,为了保证操作的完整性,引入了同步处理。
class MyThreadimplements Runnable { private int ticket = 10 ; public void run(){ for(int x=0;x<50;x++){ if(this.ticket >0){ System.out.println(Thread.currentThread().getName() +"卖票,剩余:" + this.ticket--) ; } } }};public classSynDemo01 { public static void main(String args[]){ MyThread mt = new MyThread(); new Thread(mt,"票贩子A").start() ; new Thread(mt,"票贩子B").start() ; new Thread(mt,"票贩子C").start() ; }};
但是,从实际的操作来看,都是使用网络卖票,既然是网络卖票的话,则有可能出现延迟。
class MyThreadimplements Runnable { private int ticket = 10 ; public void run(){ for(int x=0;x<50;x++){ if(this.ticket >0){ try{ Thread.sleep(300) ; } catch(Exception e){} System.out.println(Thread.currentThread().getName() +"卖票,剩余:" + this.ticket--) ; } } }};public classSynDemo01 { public static void main(String args[]){ MyThread mt = new MyThread(); new Thread(mt,"票贩子A").start() ; new Thread(mt,"票贩子B").start() ; new Thread(mt,"票贩子C").start() ; }};
本程序中可以发现一旦加入了延迟(不加延迟也可能有问题)之后发现有的人卖出的票数是负数。
由于现在是有多个操作,那么最好的解决方法是加入一个锁的标记,锁的标记中将判断和修改同时进行,要想完成这种锁的程序可以通过两种语句实现:同步代码块、同步方法。
范例:使用同步代码块完成
class MyThreadimplements Runnable { private int ticket = 10 ; public void run(){ for(int x=0;x<50;x++){ synchronized(this){ // 将当前对象锁定 if(this.ticket> 0){ try{ Thread.sleep(300); }catch (Exception e){} System.out.println(Thread.currentThread().getName() +"卖票,剩余:" + this.ticket--) ; } } } }};public classSynDemo02 { public static void main(String args[]){ MyThread mt = new MyThread(); //以下的三个线程都是用同一个实现了Rrunable接口类的对象mt创建的,thread 会调用mt的run方法,所以把它锁住 new Thread(mt,"票贩子A").start() ; new Thread(mt,"票贩子B").start() ; new Thread(mt,"票贩子C").start() ; }};
程序中加入了同步操作之后可以发现执行的速度变慢了。
除了可以使用同步代码块之外还可以使用同步方法完成以上的操作。
class MyThreadimplements Runnable { private int ticket = 10 ; public void run(){ for(int x=0;x<50;x++){ this.sale() ; } } public synchronized void sale() { if(this.ticket > 0){ try{ Thread.sleep(300); } catch (Exceptione){} System.out.println(Thread.currentThread().getName() + "卖票,剩余:" + this.ticket--) ; } }};public classSynDemo03 { public static void main(String args[]){ MyThread mt = new MyThread(); new Thread(mt,"票贩子A").start() ; new Thread(mt,"票贩子B").start() ; new Thread(mt,"票贩子C").start() ; }};
现在就可以给出一个方法的完整定义格式
[public | protected | private ][static] [final] [synchronized]
返回值类型方法名称(参数列表) [throws异常1,异常2,…]{
[return返回值 ;]
}
四、线程操作的经典案例——生产者和消费者以上就是多线程的基本操作,但是在整个多线程存在一个经典的交互案例,生产者和消费者。先通过代码观察问题,现在假设说要生产的是一组信息,此组信息有两种选项:
· oracle 数据库
· java www.sun.com.cn
既然要生产信息,则肯定要建立一个信息的保存对象。
class Info { private String name ="oracle" ; private String desc = "数据库" ; public void setName(String name){ this.name = name ; } public void setDesc(String desc){ this.desc = desc ; } public String getName(){ return this.name ; } public String getDesc(){ return this.desc ; }};
下面建立生产者和消费者,同时为了保证两者之间存在共同的生产线,所以两个类分别都占有同一个Info的引用。
class Proimplements Runnable { private Info info ; public Pro(Info info){ this.info = info ; } public void run(){ for(int x=0;x<50;x++){ if(x%2==0){ this.info.setName("Java"); try{ Thread.sleep(300); } catch(Exception e){} this.info.setDesc("www.sun.com"); } else { this.info.setName("Oracle"); try{ Thread.sleep(300); } catch(Exception e){} this.info.setDesc("数据库") ; } } }};class Cusimplements Runnable { private Info info ; public Cus(Info info){ this.info = info ; } public void run(){ for(int x=0;x<50;x++){ try{ Thread.sleep(300); } catch (Exceptione){} System.out.println(this.info.getName()+ " --> " + this.info.getDesc()) ; } }};public classCommDemo { public static void main(String args[]){ Info info = new Info() ; Pro p = new Pro(info) ; Cus c = new Cus(info) ; new Thread(p).start() ; new Thread(c).start() ; }}
通过以上的操作代码可以发现,现在的程序有两点问题:
1、 数据的设置错误
2、 出现了重复取或重复设置的问题。
4.1、解决错误数据需要使用同步的方法来解决数据设置错误问题,可以通过以下的修改完成。
class Info { private String name ="oracle" ; private String desc = "数据库" ; public synchronized void set(Stringname,String desc){ this.setName(name) ; try{ Thread.sleep(300) ; } catch (Exception e){} this.setDesc(desc) ; } public synchronized void get(){ try{ Thread.sleep(300) ; } catch (Exception e){} System.out.println(this.name+ " --> " + this.desc) ; } public void setName(String name){ this.name = name ; } public void setDesc(String desc){ this.desc = desc ; } public String getName(){ return this.name ; } public String getDesc(){ return this.desc ; }};class Proimplements Runnable { private Info info ; public Pro(Info info){ this.info = info ; } public void run(){ for(int x=0;x<50;x++){ if(x%2==0){ this.info.set("Java","www.sun.com"); } else { this.info.set("Oracle","数据库") ; } } }};class Cus implementsRunnable { private Info info ; public Cus(Info info){ this.info = info ; } public void run(){ for(int x=0;x<50;x++){ this.info.get() ; } }};public classCommDemo { public static void main(String args[]){ Info info = new Info() ; Pro p = new Pro(info) ; Cus c = new Cus(info) ; new Thread(p).start() ; new Thread(c).start() ; }};
现在的程序代码之中,已经增加了同步操作。
现在虽然避免了错误数据的问题,但是重复取和重复设置的问题依然存在。依靠信号量+Object中唤醒等待方法解决
4.2、Object类对线程的支持在Object类中提供了以下的方法可以实现对线程的等待及唤醒的处理:
·等待:public final void wait() throws InterruptedException
·唤醒:public final void notify(),唤醒第一个等待的线程
·唤醒:public final void notifyAll(),唤醒全部等待的线程
下面就可以利用这种机制观察对线程的处理操作,解决重复读取和设置的问题,修改Info类即可。
class Info { private String name ="oracle" ; private String desc = "数据库" ; private boolean flag = false ; // 定义一个标记 /* flag = true,表示可以生产,但是不能取得 flag = false,表示可以取得,但是不能生产 */ public synchronized void set(Stringname,String desc){ if(!this.flag){ try{ super.wait(); }catch(Exceptione){} } this.setName(name) ; try{ Thread.sleep(300); } catch (Exception e){} this.setDesc(desc) ; this.flag = false ; super.notify() ; // 唤醒 } public synchronized void get(){ if(this.flag){ try{ super.wait(); }catch(Exceptione){} } try{ Thread.sleep(300) ; } catch (Exception e){} System.out.println(this.name+ " --> " + this.desc) ; this.flag = true ; super.notify() ; } public void setName(String name){ this.name = name ; } public void setDesc(String desc){ this.desc = desc ; } public String getName(){ return this.name ; } public String getDesc(){ return this.desc ; }};