近期学习JAVA多线程,碍于自己捉急的记忆力,故写下一些学习笔记,方便将来使用。主要参看别人的多线程笔记,以及自己的一些心得。
Go,Go,Go!
一、 JAVA线程的创建
JAVA线程的创建有两种方式,实现了runnable 接口,或者继承Thread类,具体方式参看代码:
方式一:
public class MyThread{
public static void main(String [] args)
{
zxsThread zxst1= new zxsThread("小华" );
zxsThread zxst2= new zxsThread("大名" );
Thread t1= new Thread(zxst1 );
Thread t2= new Thread(zxst2 );
t1.start();
t2.start();
}
}
class zxsThread implements Runnable
{
@Override
public void run() {
// TODO Auto-generated method stub
for(int i =0;i <100;i ++)
{
for (long k = 0; k < 100000000; k++) ;
System. out.println(name +": " +i +"s" );
}
}
public zxsThread(String name )
{
this.name = name ;
}
private String name ;
}
运行结果:
大名: 0s
小华: 0s
大名: 1s
小华: 1s
大名: 2s
小华: 2s
大名: 3s
小华: 3s
大名: 4s
小华: 4s
方式二:public class MyThread2 extends Thread{
public MyThread2(String name )
{
super(name );
}
public void run()
{
for(int i =0;i <100;i ++)
{
for (long k = 0; k < 100000000; k++) ;
System. out.println(name +": " +i +"s" );
}
}
public static void main(String[] args)
{
MyThread2 m1= new MyThread2("zxs" );
MyThread2 m2= new MyThread2("wk" );
m1.start();
m2.start();
}
private String name ;
}
运行结果:
zxs: 0s
wk: 0s
zxs: 1s
wk: 1s
zxs: 2s
wk: 2s
zxs: 3s
wk: 3s
zxs: 4s
总结:
1、线程的名字,一个运行中的线程总是有名字的,名字有两个来源,一个是虚拟机自己给的名字(Thread-0,Thread-1...),一个是你自己的定的名字。
2、线程都可以通过m1.setName( "new
name");设置名字,也可以获取线程m1.getName()的名字,连主线程也不例外
3、获取当前线程的对象的方法是:Thread.currentThread()
4、在上面的代码中,只能保证:每个线程都将启动,每个线程都将运行直到完成。一系列线程以某种顺序启动并不意味着将按该顺序执行。对于任何一组启动的线程来说,调度程序不能保证其执行次序,持续时间也无法保证。
5、当线程目标run()方法结束时该线程完成。
6、一旦线程启动,它就永远不能再重新启动,启动两次会有java.lang.IllegalThreadStateException异常。只有一个新的线程可以被启动,并且只能一次。一个可运行的线程或死线程可以被重新启动。
7、线程的调度是JVM的一部分,在一个CPU的机器上,实际上一次只能运行一个线程。JVM线程调度程序决定实际运行哪个处于可运行状态的线程。(线程调度机制,调度处于可运行状态的线程执行。时间片做完,再次调度)。众多可运行线程中的某一个会被选中做为当前线程。可运行线程被选择运行的顺序是没有保障的。
8、尽管通常采用队列形式,但这是没有保障的。队列形式是指当一个线程完成“一轮”时,它移到可运行队列的尾部等待,直到它最终排队到该队列的前端为止,它才能被再次选中。事实上,我们把它称为可运行池而不是一个可运行队列,目的是帮助认识线程并不都是以某种有保障的顺序排列唱呢个一个队列的事实。(突出线程调度是无序的)
二、 JAVA线程的创建
要理解线程调度的原理,以及线程执行过程,必须理解线程栈模型。线程栈是指某时刻时内存中线程调度的栈信息,当前调用的方法总是位于栈顶。线程栈的内容是随着程序的运行动态变化的,因此研究线程栈必须选择一个运行的时刻(实际上指代码运行到什么地方)。下面通过一个示例性的代码说明线程(调用)栈的变化过程:
这幅图描述在代码执行到两个不同时刻1、2,3时候,虚拟机线程调用栈示意图。
当程序执行到t.start();时候,程序多出一个分支(增加了一个调用栈B),这样,栈A、栈B、栈C 并行执行。
从这里就可以看出方法调用和线程启动的区别了。(方法调用还是在一个线程栈里面,而线程启动是开了一个新的栈区)
三、JAVA线程的状态转换
3.1、线程状态
线程的状态转换是线程控制的基础。线程状态总的可分为五大状态:分别是新生(new)、死(Terminated)、可运行(Runnable)、运行、等待(Waiting)/阻塞(Blocked)。用一个图来描述如下:
(1)New 新生状态: 线程对象已经创建,还没有在其上调用start()方法。
(2)Runnable 可运行状态: 当线程有资格运行,但调度程序还没有把它选定为运行线程时线程所处的状态。当start()方法调用时,线程首先进入可运行状态。在线程运行之后或者从阻塞、等待或睡眠状态回来后,也返回到可运行状态。
(3) Blocked 阻塞状态
(4) Waiting 等待状态
(5) Termianted 死亡状态
3.2、阻止线程执行
3.2.1、线程睡眠,Thread.sleep(毫秒数,纳秒数)
线程睡眠的原因:线程执行太快,或者需要强制进入下一轮,因为Java规范不保证合理的轮换。
睡眠的位置:为了让其他线程有机会执行,可以将Thread.sleep()的调用放线程run()之内。这样才能保证该线程执行过程中会睡眠。
try {
Thread.sleep(1000 ,);
} catch (InterruptedException e )
{
// TODO Auto-generated
catch block
e.printStackTrace();
}
注意:
1、线程睡眠是帮助所有线程获得运行机会的最好方法。2、线程睡眠到期自动苏醒,并返回到可运行状态,不是运行状态。sleep()中指定的时间是线程不会运行的最短时间。因此,sleep()方法不能保证该线程睡眠到期后就开始执行。3、sleep()是静态方法,只能控制当前正在运行的线程。
3.2.2、线程的优先级和线程让步yield()
Thread.yield()作用: 暂停当前正在执行的线程对象,并执行其他线程。
yield()应该做的是让当前运行线程回到可运行状态,以允许具有相同优先级的其他线程获得运行机会。因此,使用yield()的目的是让相同优先级的线程之间能适当的轮转执行。但是,实际中无法保证yield()达到让步目的,因为让步的线程还有可能被线程调度程序再次选中。
结论:yield()从未导致线程转到等待/睡眠/阻塞状态。在大多数情况下,yield()将导致线程从运行状态转到可运行状态,但有可能没有效果。
设置线程的优先级:线程默认的优先级是创建它的执行线程的优先级。可以通过setPriority(int newPriority)更改线程的优先级。例如:
Thread t = new MyThread();
t.setPriority(8);
t.start();
t.setPriority(8);
t.start();
线程优先级为1~10之间的正整数,JVM从不会改变一个线程的优先级。然而,1~10之间的值是没有保证的。线程默认优先级是5,Thread类中有三个常量,定义线程优先级范围:static int MAX_PRIORITY线程可以具有的最高优先级。static int MIN_PRIORITY线程可以具有的最低优先级。static int NORM_PRIORITY分配给线程的默认优先级。
3.2.3、join()方法
调用方法,new Thread().join() 主线程等到子线程结束之后才能执行,(子线程执行完毕才能执行后面的)
new Thread().join(int mileseconds) 主线程等到子线程结束之后,或者等待的时间到才能执行后面的
比较下面两端代码及其运行结果:
public class MyThread2 extends Thread{
public MyThread2(String name )
{}
public void run()
{
for(int i =0;i <10;i ++)
{
//for (long k = 0; k < 100000000;
k++) ;
try {
Thread. sleep(1000);//等待1S
} catch (InterruptedException e )
{
// TODO Auto-generated
catch block
e.printStackTrace();
}
System. out.println(super .getName()+":
"+ i+ "s");
//System.out.println(m1.getState());
}
}
public static void main(String[] args)
{
System. out.println("let's
go to" );
m1=new MyThread2("zxs");
m1.start();
System. out.println("1111111111111111111111111" );
try {
m1.join(5000);
//等待5秒之后或者m1执行完毕,才执行join后面的打印内容
} catch (InterruptedException e )
{
// TODO Auto-generated
catch block
e.printStackTrace();
}
System. out.println("sdsadddddddddddddddddddddddddddddddddd" );
}
private static MyThread2 m1;
}
运行结果:
let's go to
1111111111111111111111111
Thread-0: 0s
Thread-0: 1s
Thread-0: 2s
Thread-0: 3s
Thread-0: 4s
sdsadddddddddddddddddddddddddddddddddd
Thread-0: 5s
Thread-0: 6s
Thread-0: 7s
Thread-0: 8s
将 m1.join(5000);改成 m1.join()//则等待m1执行完毕,才执行join后面的打印内容
运行结果:
let's go to
1111111111111111111111111
Thread-0: 0s
Thread-0: 1s
Thread-0: 2s
Thread-0: 3s
Thread-0: 4s
Thread-0: 5s
Thread-0: 6s
Thread-0: 7s
Thread-0: 8s
Thread-0: 9s
sdsadddddddddddddddddddddddddddddddddd
小结:
到目前位置,介绍了线程离开运行状态的3种方法:1、调用Thread.sleep():使当前线程睡眠至少多少毫秒(尽管它可能在指定的时间之前被中断)。2、调用Thread.yield():不能保障太多事情,尽管通常它会让当前运行线程回到可运行性状态,使得有相同优先级的线程有机会执行。3、调用join()方法:保证当前线程停止执行,直到该线程所加入的线程完成为止。然而,如果它加入的线程没有存活,则当前线程不需要停止。
除了以上三种方式外,还有下面几种特殊情况可能使线程离开运行状态:
1、线程的run()方法完成。
2、在对象上调用wait()方法(不是在线程上调用)。
3、线程不能在对象上获得锁定,它正试图运行该对象的方法代码。
4、线程调度程序可以决定将当前运行状态移动到可运行状态,以便让另一个线程获得运行机会,而不需要任何理由。
参考博客:
http://lavasoft.blog.51cto.com/62575/27069/