黑马程序员-多线程(上)

来源:互联网 发布:怎么出售淘宝店铺 编辑:程序博客网 时间:2024/05/16 15:14

-----------android培训java培训、java学习型技术博客、期待与您交流! ------------


一、重要概念区分:


1、进程和线程

进程:正在进行的程序。即:每个独立程序(例如QQ360管家)在计算机上的一次执行活动。

线程:进程内部的一条执行路径或一个独立的控制单元。线程控制着进程的执行。一个进程中至少有一个线程。

多线程:如果一个程序中可以在同一进程内执行多个线程,我们就说这个程序是支持多线程的;比如迅雷下载软件可以同时下载多个任务。

注:进程在执行过程中拥有独立的内存单元,而一个线程的多个线程共享内存。


2、程序和任务

并发编程使我们可以将程序划分为多个分离的、独立运行的任务。通过使用多线程机制,这些独立任务中的每一个都将由执行线程来驱动。一个线程就是在进程中的一个单一的顺序控制流,因此,单个进程可以拥有多个并发执行的任务,使得程序的每个任务都好像有自己的CPU一样。实质上其底层机制是切分CPU时间,划分成片段分配给了所有的任务。


3、任务和线程

Thread类自身不执行任何操作,它只是驱动赋予它的任务。而且,程序员对Thread类实际没有任何控制权。你创建任务,并通过某种方式将一个线程附着到任务上,以使得这个线程可以驱动任务。

将任务和线程区分使用:在描述将要执行的工作内容时使用“任务”;只有在引用到驱动任务的具体机制时,才使用“线程”。


二、多线程的意义:


1、提高执行速度(提高计算机CPU的利用率);
2、改善程序结构。一个既长又复杂的进程可以考虑分为多个线程,成为几个独立或半独立的运行部分,这样的程序会利于理解和修改。

三、主线程


Java VM启动的时候会有一个进程java.exe。该进程中至少一个线程负责java程序的执行;而且这个线程运行的代码存在于main方法中。该线程称为主线程。


四、jvm启动的是多线程吗?


如果java的虚拟机jvm启动的是单线程,就有发生内存泄露的可能,而我们使用java程序没出现这样的问题,也就是说jvm启动至少有两个线程,一个执行java程序(主线程),一个执行垃圾回收。所以是多线程。



五、多线程的弊端:


线程太多会导致效率的降低,因为线程的执行依靠的是CPU的来回切换。


六、实现多线程的方法:


如何在自定义的代码中,自定义一个线程?实现多线程可以通过继承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...0true...two+++ run...0main.....0true...two+++ run...1true...one--- run...1true...two+++ run...2main.....1main.....2true...two+++ run...3true...one--- run...2true...two+++ run...4main.....3true...two+++ run...5true...two+++ run...6true...one--- run...3true...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 : 100Thread-1....sale : 99Thread-3....sale : 99Thread-2....sale : 98Thread-3....sale : 96Thread-2....sale : 95Thread-0....sale : 97Thread-1....sale : 96Thread-1....sale : 94Thread-2....sale : 92Thread-0....sale : 93Thread-3....sale : 91Thread-0....sale : 90Thread-1....sale : 88Thread-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方法运行结束,此线程随即终止。


2、Run()方法:

run()方法只是Thread类的一个普通方法,作用只是封装线程要执行的代码;如果直接调用Run方法,程序中依然只有主线程这一个线程,其程序执行路径还是只有一条,还是要等待run方法体执行完毕后才可继续执行下面的代码,这样就没有达到多线程的目的。

原创粉丝点击