《学习笔记Day21》多线程的详细内容

一、多线程相关的三组概念

(一)程序和进程

1、程序:一个固定的存储有逻辑和数据的集合,是一个静态的状态,存储在磁盘上
2、进程:一个正在运行着的程序,是一个动态的概念,一般运行在计算机的内存中
3、查看计算机进程:ctrl + shift +ESC

(二)进程和线程

1、进程:是一个正在运行的程序,会分配一部分系统资源,是一个独立的资源分配单位
2、线程:一条正在独立执行的路径。多线程,再执行某个程序的时候,该程序与多个子任务,每个线程都可以独立的完成其中一个子任务。在子任务之间,没有什么依赖关系,可以独立执行。
3、进程和线程的关系:
(1)进程适用于分配系统资源的单位
(2)一个进程中,可以有多条线程,但是一个进程中,至少有一条线程
(3)线程不会独立分配资源,一个进程中的所有线程,共享的是同一个进程的资源

(三)并行和并发

1、并行:在一个时间点,有多个任务(进程、线程)正在执行。多核心、多CPU变成
2、并发:在一个时间点,有多个任务同时发起。但是,在一个时间点,同时只能由一个任务正在运行。单核心、单CPU编程

(四)Q&A:CPU在多个任务之间来回切换,效率提高了还是降低了

1、对于一个任务,毋庸置疑是降低了
2、对于整个计算机系统,效率提高了,计算机硬件的运行效率是不同的:CPU 10^-9秒,内存 10^-6秒,硬盘 10^-3 秒

二、多线程的第一种实现方式

1、实现多线程的第一种方式:继承方式

在JDK中有一个类是线程类:Thread
步骤:

1、自定义一个类型继承Thread
2、在自定义类型中重写Thread类中的run方法,方法体就是将来线程的任务
3、创建自定义类型对象,表示一条线程
4、调用start( )方法启动线程

public class Demo01_ThreadFirstWay {

	public static void main(String[] args) {
		//3.创建一个自定义类型的对象,表示一条线程
		MyThread mt = new MyThread();
		
		//4.调用继承了Thread类中的start()方法,启动线程
		mt.start();
		
		for (int i = 1; i <= 1000; i++) {
			System.out.println(i + "====main");
		}
	}
}

//1.自定义一个类型继承Thread类
class MyThread extends Thread {

	//2.重写Thread类中的run方法
	@Override
	public void run() {
		
		for (int i = 1; i <= 1000; i++) {
			System.out.println(i + "====MyThread");
		}
	}
}

(二)实现多线程的第二种方式:实现方式

1、在JDK中有一个接口:Runnable
2、步骤:

1、自定义一个类,实现Runnable接口
2、在自定义类中重写Runnable接口的run方法
3、创建自定义类型的对象,表示一个任务
4、创建线程对象,将然乌添加到线程中
5、线程对象调用start方法,启动线程

public class Demo02_ThreadSecondWay {

	public static void main(String[] args) {
		//3.创建自定义类型的对象,表示一个任务对象
		MyTask task = new MyTask();
		
		//4.创建一个线程对象,将任务添加到线程中
		Thread t = new Thread(task);
		
		//5.线程对象调用start方法,启动线程
		t.start();
		
		for (int i = 1; i <= 1000; i++) {
			System.out.println(i + "====main");
		}
	}
}

//1.自定义一个类型,实现Runnable接口
class MyTask implements Runnable {

	//2.重写Runnable接口中的run方法
	@Override
	public void run() {
		for (int i = 1; i <= 1000; i++) {
			System.out.println(i + "====MyTask");
		}
	}
}

(三)匿名内部类简化两种线程的实现方式

			Thread t = new Thread() {

			@Override
			public void run() {
				for (int i = 1; i <= 1000; i++) {
					System.out.println(i + "====MyThread");
				}
			}
		};
		
		t.start();
			Thread t = new Thread(new Runnable() {
			
			@Override
			public void run() {
				for (int i = 1; i <= 1000; i++) {
					System.out.println(i + "====MyTask");
				}
			}
		});
		
		t.start();

两种方式的比较

1、代码复杂程度

1、继承简单 2、实现相对复杂

2、实现原理:

1、继承方式:创建好线程对象后,调用start( )方法启动线程,start()方法又调用start0()方法,start0()方法调用了run()方法,因为在自定义类型(子类中)重写了Thread类的run方法,所以直接执行子类中重写后的run,我们重写是定义好的业务逻辑就执行了
2、实现方式:创建接口中的实现类对象,这个对象是一个任务对象。创建线程对象,将任务对象封装到线程对象中,将任务对象当做参数传递给线程对象,线程对象在创建的过程中,连续调用2个init方法,之后,将任务对象设为线程对象的成员变量,赋值给线程。创建好线程对象后,调用start方法启动线程,start方法又调用了start0方法,start0方法调用run方法,因为在初始化的时候,已经将任务对象封装进了线程对象中,所以此时是线程对象在调用线程类中的run方法,此时run方法就要执行。判断任务对象部位null之后,任务对象调用接口中的run方法,因为接口的run方法已经被实现了,所以根据任务对象所属类型,调用任务对象所属类中重写过的run方法,执行重写后的方法。

设计:

1、继承方式:自定义类型继承了Thread类型,就无法再去继承其他类型,代码扩展性较差
2、实现方式:一个类,实现多个接口的同时还能再继承一个类型,扩展性比较强

灵活性:

1、继承的方式:将业务逻辑和线程对象绑定在了一起,耦合度搞,灵活性差
2、实现的方式:将任务对象和线程对象分离开,降低耦合度,;灵活性强。一个任务对象可以被对各线程执行,一个线程也可以执行不同的任务。并且将来还可以将任务对象,提交到线程池中;任务对象被不同的线程执行,也方便多线程之间的数据交互

三、Thread类中常用的方法

1、getName() 获取线程的名称

注意:

  • [1 ] 当我们没有给线程起名时,线程具有默认名字【Thread-x】
  • [2 ] 在继承的方式中,可以直接在run内调用getName(),因为继承方式的run和getName都是来源于父类Thread
  • [3 ] 在实现的方式中,run方法里不能直接调用getName(),因为run方法是接口Runnable的方法,不在一个体系

2、setName(String name) 设置线程名称
构造方法:

  • [1] Thread(String name)
  • [2 ] Thread(Runnable target, String name)

3、static Thread currentThread()返回当前正在执行的这段代码所在线程的线程对象

System.out.println(Thread.currentThread().getName());

四、练习:

1、获取主方法所在的线程名称
2、获取垃圾回收线程的名称

/**
 * 1、获取主方法所在的线程名称
	2、获取垃圾回收线程的名称
 * 
 * @author Zihuatanejo
 *
 */
public class Demo06_Exercise {

	public static void main(String[] args) {
		//哪个线程执行了currentThread方法,就获取哪个线程对象
		System.out.println(Thread.currentThread().getName());
		
		new Garbage();
		
		System.gc();
	}
}

class Garbage {

	@Override
	protected void finalize() throws Throwable {
		System.out.println(Thread.currentThread().getName());
	}
}

(五)线程休眠

1、static sleep(long millis) 让当前线程休眠指定的毫秒数
3、注意事项:
(1)当在重写的方法中使用该方法时,如果重写前的方法没有声明异常,重写后的方法只能try…catch捕获
(2)该方法会出现InterruptedException,中断异常,当线程在休眠过程中被打断,会报此异常

(六)守护线程

1、守护线程:保证其他非守护线程能够正常执行的线程,为其他非守护线程提供良好的运行环境。守护线程死亡,非守护线程可以正常执行,非守护线程死亡,守护线程也就没有存在的意义了,片刻(或长或短的时间)之后,守护线程也会消亡。
2、isDaemon() 判断一个线程是否为守护线程
3、setDaemon(boolean bo) 将一个线程设置为守护线程
4、任何线程在创建出来的时候,都是一个非守护线程
5、别名:后台线程

(七)练习

//分别从作用上和代码上判断,垃圾回收线程是否为守护线程
public class Demo09_Exericse {

	public static void main(String[] args) {
		
		while(true) {
			new Rubbish();
		}
	}
}

class Rubbish {
	//重写finalize
	@Override
	protected void finalize() throws Throwable {
		System.out.println(Thread.currentThread().isDaemon());
	}
	
}

(八)线程的优先级

1、执行多线程的时候,每个线程都有优先级,优先级高的,在执行的整个周期内比较靠前执行,优先级低的,在执行的整个周期内比较靠后的执行
2、有三个线程优先级常量:
(1)MAX_PRIORITY 最高优先级:10
(2)MIN_PRIORITY 最低优先级:1
(3)NORM_PRIORITY 默认优先级:5

		t.setPriority(1);
		t1.setPriority(10);

四、多线程中的线程安全问题

1、同步代码块
使用一种格式,让某段代码执行的时候,CPU不会切换到影响这段代码执行的代码上去。这种格式既能保证CPU在执行A主线程的时候,不会影响A线程执行的其他线程上

2、格式:
synchronized(同步锁对象) {
需要保证执行完整性的代码
}

3、使用同步代码块之后的效果:
(1)当CPU想要去执行同步代码块的时候,需要先获取到同步锁对象,获取之后就能执行同步代码块里面的内容;当CPU想要执行其他线程的时候,但是不会切换到具有相同通过不锁对象的代码上
(2)当CPU执行完了当前代码块中的代码,回释放同步锁对象,CPU就可以运行到其他的线程上,执行其他代码。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

阿福真的不想掉头发

大爷?赏点?

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值