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

-----------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...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方法运行结束,此线程随即终止。


2、Run()方法:

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

黑马程序员多线程练习题主要包括两个问题。第一个问题是如何控制四个线程在打印log之前能够同时开始等待1秒钟。一种解决思路是在线程的run方法中调用parseLog方法,并使用Thread.sleep方法让线程等待1秒钟。另一种解决思路是使用线程池,将线程数量固定为4个,并将每个调用parseLog方法的语句封装为一个Runnable对象,然后提交到线程池中。这样可以实现一秒钟打印4行日志,4秒钟打印16条日志的需求。 第二个问题是如何修改代码,使得几个线程调用TestDo.doSome(key, value)方法时,如果传递进去的key相等(equals比较为true),则这几个线程应互斥排队输出结果。一种解决方法是使用synchronized关键字来实现线程的互斥排队输出。通过给TestDo.doSome方法添加synchronized关键字,可以确保同一时间只有一个线程能够执行该方法,从而实现线程的互斥输出。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* *3* [黑马程序员——多线程10:多线程相关练习](https://blog.csdn.net/axr1985lazy/article/details/48186039)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 100%"] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值