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

本文介绍了Java中线程的创建方法,包括继承Thread类和实现Runnable接口两种方式,并对比了它们在线程间共享资源方面的差异。此外,还详细讲解了线程的生命周期及其状态转换,以及如何使用join、yield和sleep等方法来控制线程的执行。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一.线程的创建和启动

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虚拟机为其分配了内存,并初始化了其成员变量的值。(此时的线程没有表现出任何表现出任何线程的动态特征,程序也不会执行线程的线程执行体)new Thread()||new Thread(Runnable target,String name)。

   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采用的策略。(抢占式调度xp win7 linux unix..)。如果是一些特殊的小型设备可能采用 协作式调度(只有线程自己调用它的sleep()或yield()才会放弃所占用的资源)。


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

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

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

三.控制线程

1.join线程:

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

     Join方法有三种重用形式:

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

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

           Joinlong millisint nanos)千分之一毫秒(不用)

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来控制并发线程控制


转载于:https://my.oschina.net/91jason/blog/295333

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值