Java多线程(全)学习笔记(上)

资源下载地址:http://download.csdn.net/detail/cloudyxuq/3763101

一.线程的创建和启动

java使用Thread类代表线程,所有的线程对象都必须是Thread类或其子类的实例。每条线程的作用是完成一定的任务,实际上就是执行一段程序流(一段顺序流的代码)。Java使用run方法来封装这样一段程序。

1.继承Thread类创建线程类

/**继承Thread来创建线程类*/ public class FirstThread extends Thread { private int i; //重写run方法,run方法的方法体就是线程执行体 public void run() { for(;i<10;i++){ System.out.println(this.getName()+":"+i); } } public static void main(String []args){ for(int i=0;i<20;i++){ System.out.println(Thread.currentThread().getName()+" .."+i); if(i==10){ System.out.println("--------------------------------------------"); new FirstThread().start(); new FirstThread().start(); System.out.println("---------------------------------------------"); } } } } 结果:红色部分每次运行都不一致,因为多线程也是并发的 main ..0 main ..1 main ..2 main ..3 main ..4 main ..5 main ..6 main ..7 main ..8 main ..9 main ..10 -------------------------------------------- Thread-0:0 --------------------------------------------- Thread-1:0 Thread-1:1 Thread-1:2 Thread-1:3 Thread-0:1 Thread-1:4 Thread-1:5 main ..11 Thread-1:6 Thread-1:7 Thread-1:8 Thread-1:9 Thread-0:2 Thread-0:3 main ..12 main ..13 ......

总结:从上面结果可以看出Thread-0和Thread-1两条线程输出的i变量都不连续(注意:i变量是FirestThread的实例属性,而不是局部变量,但因为程序每次创建线程都会创建一个FirstThread对象,所以Thread-0和Thread-1不能共享该实例属性)。

使用继承Thread类的方法来创建线程类,多条线程之间无法共享线程类的实例变量。

2.实现Runnable接口创建线程类

public class SecondThread implements Runnable { private int i; public void run() { for(;i<20;i++){ System.out.println(Thread.currentThread().getName()+":"+i); } } public static void main(String [] args){ for(int i=0;i<20;i++){ System.out.println(Thread.currentThread().getName()+" .."+i); if(i==10){ SecondThread st=new SecondThread(); //通过new Thread( Runable target,String name)来创建新线程 new Thread(st,"线程1").start(); new Thread(st,"线程2").start(); } } } 结果:红色部分每次运行都不一致,因为多线程也是并发的 main ..0 main ..1 main ..2 main ..3 main ..4 main ..5 main ..6 main ..7 main ..8 main ..9 main ..10 -------------------------------------------- 线程1:0 -------------------------------------------- 线程1:1 线程2:1 线程2:3 main ..11 线程2:4 线程2:5 线程2:6 线程1:2 线程2:7 线程2:9 线程2:10 线程2:11 线程2:12 线程2:13 main ..12 线程2:14 线程2:15 线程2:16 线程2:17 线程1:8 线程2:18 main ..13 main ..14 线程1:19 main ..15 main ..16 main ..17 。。。。

总结:根据源代码中Thread类构造方法Ruanalbe接口对象target只能作为参数传递到Thread构造方法中,所以多个线程可以共用一个Runnable对象,因为都用同一个Runnable对象所以在Runnable实现类的实例变量也可以共享了。

所以Runable非常适合多个相同线程来处理同一份资源的情况。

二.线程的生命周期

1.New新建:当线程被创建时,该线程处于新建状态,此时它和其他java对象一样,仅仅由Java虚拟机为其分配了内存,并初始化了其成员变量的值。(此时的线程没有表现出任何表现出任何线程的动态特征,程序也不会执行线程的线程执行体)newThread()||newThread(Runnabletarget,Stringname)。

2.Runnable就绪:就绪也就是说启动线程,但是启动线程使用start方法,而不是run方法!永远不要调用线程对象的run()方法!调用start方法来启动线程,系统会将该run方法当成线程执行体来处理。如果直接调用线程对象的run方法。则run方法会立即执行,且在这个run方法的执行体未执行结束前其他线程无法并发执行(即系统会将run方法当做一个普通对象的普通方法,而不是线程执行体对待)

附1:如果有一个主线程,一个子线程。当根据逻辑代码该调用子线程时不一定会立即调用,为了想在子线程start()后立即调用子线程,可以考虑使用Thread.sleep(1),这样会让当前线程(主线程)睡眠1毫秒,因为cpu在这1毫秒中是不会休息的,这样就会去执行一条处于就绪状态的线程。

附2:不能对已经处于就绪状态的线程,再次使用start()

3.Running运行:当处于就绪状态时,该线程获得cpu,执行体开始运行,就处于运行状态了。

4.Blocked阻塞:线程不可能一直处于运行状态(线程执行体足够短,瞬间就可以完成的线程排除),线程会在运行过程中需要被中断,因为是并发,目的是会让其他线程获得执行的机会,线程的调度细节取决于OS采用的策略。(抢占式调度xpwin7linuxunix..)。如果是一些特殊的小型设备可能采用协作式调度(只有线程自己调用它的sleep()或yield()才会放弃所占用的资源)。


5.Dead死亡:根据上图所示。测试测试某条线程是否已经死亡,可以调用线程对象的isAlive()方法,当线程处于就绪,运行,阻塞时,返回true。线程处于新建,死亡时返回false

不能对已经死亡的线程调用start()方法使它重新启动,死亡就是死亡,是不能再次作为线程执行的。

当主线程结束时候,其他线程不受任何影响,并不会随之结束。一旦子线程启动起来后,它就拥有和主线程相同的地位,它不会受到主线程的影响。

三.控制线程

1.join线程:

让一个线程等待另一个线程完成的方法:join()。当在某个程序执行流中调用其他线程的join()方法,那该执行流对应的线程就会阻塞,知道被join()加入的join线程完成为止。join方法通常有使用线程的程序调用,将大问题划分成许多小问题,每个小问题分配一个线程。当所有的小问题都得到处理后,再调用主线程来进一步操作(Threadt=newThread();t.start();t.join简单来说就是加入到t线程。等t线程执行完成后才会返回出来执行线程。)

Join方法有三种重用形式:

Join():等待被join的线程执行完成

Join(longmillis):等待join线程的时间最长为millis毫秒,如果在这个时间内,被join的线程还没有执行结束则不再等待)

Join(longmillisintnanos)千分之一毫秒(不用)

Code:

public class JoinThread implements Runnable{ @Override public void run() { for(int i=0;i<5;i++){ System.out.println(Thread.currentThread().getName()+":"+i); } } public static void main(String [] args) throws InterruptedException{ //实例化一个Runnable JoinThread jt=new JoinThread(); //创建一个线程 new Thread(jt).start(); for(int i=0;i<10;i++){ if(i==3){ Thread th=new Thread(jt); //启动第二个线程 th.start(); //main的线程中调用了th线程的join方法 //让第二个线程执行完成后再执行main th.join(); } System.out.println(Thread.currentThread().getName()+":"+i); } } } 结果: Thread-0:0 Thread-0:1 Thread-0:2 main:0 main:1 Thread-0:3 main:2 Thread-0:4 Thread-1:0 Thread-1:1 Thread-1:2 Thread-1:3 Thread-1:4 main:3 main:4 main:5 main:6 main:7 main:8 main:9

2.后台线程:

Code:

public class DaemonThread implements Runnable{ @Override public void run() { for(int i=0;i<100;i++){ System.out.println(Thread.currentThread().getName()+":"+i); } } public static void main(String [] args){ //要将前台线程转换成后台线程,需要在该线程刚新建还未start()之前转换。main线程也是前台线程 //所有前台线程死亡时,后台线程也就随之死亡。 DaemonThread dt=new DaemonThread(); Thread td=new Thread(dt,"线程1"); System.out.println("main方法是否是后台线程"+Thread.currentThread().isDaemon()); System.out.println("td线程最初是否是后台线程"+td.isDaemon()); //指定td为后台线程 td.setDaemon(true); System.out.println("td线程执行setDaemon方法后是否是后台线程"+td.isDaemon()); //就绪启动后台线程 td.start(); for(int i=0;i<5;i++){ System.out.println(Thread.currentThread().getName()+" "+i); } } } 结果:只要前台线程结束,后台线程也会随之结束,并不是马上结束 main方法是否是后台线程false td线程最初是否是后台线程false td线程执行setDaemon方法后是否是后台线程true main 0 main 1 线程1:0 线程1:1 main 2 线程1:2 线程1:3 main 3 线程1:4 线程1:5 main 4 线程1:6 线程1:7 线程1:8 线程1:9 线程1:10 线程1:11 线程1:12 线程1:13

3.线程睡眠:sleep

/**
 * 线程睡眠:sleep有两种重载形式:
 * static void sleep(long millis)
 * static void sleep(long millis,int nanos)
 *
 */
public class SleepThread {
public static void main(String [] args) throws InterruptedException{
for(int i=0;i<5;i++){
System.out.println("线程:"+Thread.currentThread().getName()+"当前时间:"+new Date());
//让当前线程暂停2秒
Thread.sleep(2000);
}
}
}
结果:
线程:main当前时间:Fri Nov 04 18:51:33 CST 2011
线程:main当前时间:Fri Nov 04 18:51:35 CST 2011
线程:main当前时间:Fri Nov 04 18:51:37 CST 2011
线程:main当前时间:Fri Nov 04 18:51:39 CST 2011
线程:main当前时间:Fri Nov 04 18:51:41 CST 2011


4.线程让步(yield

/** * yield()方法是一个和sleep方法有点类似的静态方法。yield也可以让当前正在执行的线程暂停 * 但它不会阻塞该线程,它只是将该线程转入就绪状态。yield只是让当前线程暂停一会儿,让系统的 * 调度器重新调度一次(完全可能的情况是:当一个线程调用了yield方法暂停之后,线程调度器又马上 * 将其调度出来重新执行。) * 实际上,当前线程调用了yield方法后,只有优先级和当前线程相同,甚至优先级高于当前线程的处于 * 就绪状态的线程才会获得执行机会。 * */ public class YieldThread implements Runnable{ @Override public void run() { for(int i=0;i<50;i++){ System.out.println(Thread.currentThread().getName()+":"+i); if(i==20){ Thread.yield(); } } } public static void main(String [] args){ //启动第一条子线程 Thread td1=new Thread(new YieldThread(),"线程1"); //最高级 //td1.setPriority(Thread.MAX_PRIORITY); //启动第二条子线程 Thread td2=new Thread(new YieldThread(),"线程2"); //最低级 td2.setPriority(Thread.MIN_PRIORITY); td1.start(); td2.start(); System.out.println(Thread.currentThread().getName()); } }

总结:sleepyield区别

A.sleep方法暂停当前线程后,会给其他线程执行机会,不会理会其他线程的优先级。而yield只会给优先级>=当前优先级的线程执行机会

B.Sleep方法会将线程转入阻塞状态,知道经过阻塞时间才会转入就绪状态。而yield是不会将线程转入阻塞状态的,它只是强制当前线程进入就绪状态。

C.Sleep会抛出InterruptedException异常。而yield没有声明任何异常

D.Sleep方法比yield方法有更好的移植性。

E.通常不依靠yield来控制并发线程控制




  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值