-----------android培训、java培训、java学习型技术博客、期待与您交流! ------------
一、重要概念区分:
1、进程和线程
进程:正在进行的程序。即:每个独立程序(例如QQ,360管家)在计算机上的一次执行活动。
线程:进程内部的一条执行路径或一个独立的控制单元。线程控制着进程的执行。一个进程中至少有一个线程。
多线程:如果一个程序中可以在同一进程内执行多个线程,我们就说这个程序是支持多线程的;比如迅雷下载软件可以同时下载多个任务。
注:进程在执行过程中拥有独立的内存单元,而一个线程的多个线程共享内存。
2、程序和任务
并发编程使我们可以将程序划分为多个分离的、独立运行的任务。通过使用多线程机制,这些独立任务中的每一个都将由执行线程来驱动。一个线程就是在进程中的一个单一的顺序控制流,因此,单个进程可以拥有多个并发执行的任务,使得程序的每个任务都好像有自己的CPU一样。实质上其底层机制是切分CPU时间,划分成片段分配给了所有的任务。
3、任务和线程
Thread类自身不执行任何操作,它只是驱动赋予它的任务。而且,程序员对Thread类实际没有任何控制权。你创建任务,并通过某种方式将一个线程附着到任务上,以使得这个线程可以驱动任务。
将任务和线程区分使用:在描述将要执行的工作内容时使用“任务”;只有在引用到驱动任务的具体机制时,才使用“线程”。
二、多线程的意义:
1、提高执行速度(提高计算机CPU的利用率);
2、改善程序结构。一个既长又复杂的进程可以考虑分为多个线程,成为几个独立或半独立的运行部分,这样的程序会利于理解和修改。
三、主线程
Java VM启动的时候会有一个进程java.exe。该进程中至少一个线程负责java程序的执行;而且这个线程运行的代码存在于main方法中。该线程称为主线程。
四、jvm启动的是多线程吗?
五、多线程的弊端:
六、实现多线程的方法:
如何在自定义的代码中,自定义一个线程?实现多线程可以通过继承Thread类和实现Runnable接口。
1、继承Thread。
步骤:(1)定义一个类继承Thread类
(2)复写Thread类中的public void run()方法。(目的:将线程的任务代码封装到run方法中,直接创建Thread的子类对象,创建线程)
(3)调用start()方法。(该方法有2个作用:启动线程,由虚拟机调用线程的run方法)
//另外可以通过Thread的getName()获取线程的名称。
注意:new一个thread对象就是创建了一个线程。
代码示例:创建两个线程,和主线程交替运行。
class Test extends Thread {
// private String name;
Test(String name) {
// this.name = name;
super(name);
}
public void run() {
for (int x = 0; x < 60; x++) {
System.out.println((Thread.currentThread() == this) + "..."
+ this.getName() + " run..." + x);
}
}
}
class ThreadTest {
public static void main(String[] args) {
Test t1 = new Test("one---");
Test t2 = new Test("two+++");
t1.start();// 开启线程并调用run方法用start()方法
t2.start();
// t1.run();//直接调用run方法没有开启线程
// t2.run();
for (int x = 0; x < 60; x++) {
System.out.println("main....." + x);
}
}
}
/*output(exemple)
true...one--- run...0
true...two+++ run...0
main.....0
true...two+++ run...1
true...one--- run...1
true...two+++ run...2
main.....1
main.....2
true...two+++ run...3
true...one--- run...2
true...two+++ run...4
main.....3
true...two+++ run...5
true...two+++ run...6
true...one--- run...3
true...two+++ run...7
*/
2、实现Runnable接口。
(1)定义一个类,实现Runnable接口;(2)覆盖接口的public void run()的方法,将线程的任务代码封装到run方法中;
(3)创建Runnable接口的子类对象
(4)将Runnable接口的子类对象作为参数传递给Thread类的构造函数,创建Thread类线程对象。
(原因:线程的任务都封装在Runnable接口子类对象的run方法中。因为要在线程对象创建时就必须明确要运行的任务,即run方法,所以就必须明确该任务所属对象,即Runnable接口子类对象)。
(5)调用Thread类的start()方法,启动线程,并调用Runnable接口子类的run方法。
代码示例:
/*需求:简单的卖票程序。
多个窗口同时卖票。*/
class Ticket implements Runnable// extends Thread
{
private int tick = 100;
public void run() {
while (true) {
if (tick > 0) {
try {
Thread.sleep(10);
} catch (Exception e) {
}// 能看出会发生线程问题
System.out.println(Thread.currentThread().getName()
+ "....sale : " + tick--);
}
}
}
}
class TicketDemo {
public static void main(String[] args) {
Ticket t = new Ticket();
Thread t1 = new Thread(t);// 创建了一个线程;
Thread t2 = new Thread(t);// 创建了一个线程;
Thread t3 = new Thread(t);// 创建了一个线程;
Thread t4 = new Thread(t);// 创建了一个线程;
t1.start();
t2.start();
t3.start();
t4.start();
}
}
/*output(exemple)
Thread-0....sale : 100
Thread-1....sale : 99
Thread-3....sale : 99
Thread-2....sale : 98
Thread-3....sale : 96
Thread-2....sale : 95
Thread-0....sale : 97
Thread-1....sale : 96
Thread-1....sale : 94
Thread-2....sale : 92
Thread-0....sale : 93
Thread-3....sale : 91
Thread-0....sale : 90
Thread-1....sale : 88
Thread-3....sale : 89
...
*/
七、两种方法的特点及区别:
1、特点
(1)Thread类的特点:
1)当类去描述事物,事物中有属性和行为。如果行为中有部分代码需要被多线程所执行,同时还在操作属性。就需要该类继承Thread类,产生该类的对象作为线程对象。可是这样做会导致每一个对象中都存储一份属性数据。无法在多个线程中共享该数据。可以加上静态,虽然实现了共享但是生命周期过长。2)如果一个类明确了自己的父类,那么它就不可以在继承Thread。或者一个类继承了Thread后也同样不能继承别的类,因为java不允许类的多继承。因为考虑到java只能单继承的弊端,所以请对比一下第二种。
(2)Runnable的特点:
1)描述事物的类中封装了属性和行为,如果有部分代码需要被多线程所执行。同时还在操作属性。那么可以通过实现Runnable接口的方式。因为该方式是定义一个Runnable接口的子类对象,可以被多个线程所操作实现了数据的共享。2)实现了Runnable接口的好处,避免了单继承的局限性。也就说,一个类如果已经有了自己的父类是不可以继承Thread类的。但是该类中还有需要被多线程执行的代码。这时就可以通过在该类上功能扩展的式。实现一个Runnable接口。
所以综上说述:强烈建议用第二种方式!!!
2、两种方法区别:
(1)实现Runnable接口避免了单继承的局限性。
1)因为实现Runnable接口方法能通过传参的方式将创建的子类对象以参数形式传给多个Thread线程对象,这样多个线程对象能共享一个任务;2)而单继承每创建一个对象,就开启了一个任务,彼此不能共享一个任务
(2)线程代码存放的位置不同
1)继承Thread类线程代码存放在Thread子类的run方法中2)实现Runnable接口线程代码存放在接口的子类的run方法中;
3、总结:
在定义线程时,建议使用实现Runnable接口,因为几乎所有多线程都可以使用这种方式实现
八、为什么运行结果每一次都不同?
因为多个线程都获取cpu的执行权。cpu执行到谁,谁就运行。
明确一点,在某一个时刻,只能有一个程序在运行。(多核除外)
cpu在做着快速的切换,以达到看上去是同时运行的效果。
我们可以形象地把多线程的运行形容为在互相抢夺cpu的执行权。
这就是多线程的一个特性:随机性。谁抢到谁执行,至于执行多长,cpu说的算。
九、创建线程是为什么要复写run方法?
Thread类用于描述线程。Thread类定义了一个功能,用于存储线程要运行的代码,该存储功能就是run方法。也就是说Thread类中的run方法,用于存储线程要运行的代码。
对比:主线程调用的代码储存在main函数中,这是由虚拟机定义的。主线程先运行,所以程序都是从main函数开始执行。
十、start()和run方法有什么区别?
调用start方法方可启动线程,而run方法只是thread和Runnable的一个普通方法,仅仅用来封装线程要运行的代码,因此调用run方法不能实现多线程;
1、Start()方法:
start方法用来为该线程执行必需的初始化操作,并启动线程,然后调用thread(或Runnable)的run()方法,以便在这个新线程中启动该任务,从而实现了多线程运行。这时start()方法迅速返回main()线程,直接继续执行main()函数下面的代码,无需等待run()方法体代码执行完毕,因为run()方法是由不同的线程执行的。通过调用Thread类的start()方法来启动一个线程,这时此线程处于就绪(可运行)状态,并没有运行,一旦得到cpu时间片(执行权),就开始执行run()方法,这里方法run()称为线程体,它包含了要执行的这个线程的内容,Run方法运行结束,此线程随即终止。