Java中的多线程——学习小结

简要:
1.什么是进程与线程?
2.多线程
3.线程安全问题和解决方法
4.守护线程
5.线程状态

1.进程与线程?

进程:

windows电脑中,打开任务管理器,可看到电脑中执行的每一个程序,这就是【进程】

线程:

如电脑管家是一个程序,但电脑可同时做病毒查杀,垃圾清理,深度加速等功能,每一个功能就是【线程】

注意:
(1)线程使用的是系统资源,该系统资源是操作系统分配给当前进程使用的。
(2)多个线程的情况下,同时【抢占执行】会导致资源紧缺。类似于进程的抢占过程。
(3)一个Java程序,最少有2个线程。main线程、JVM的GC机制,守护线程。
并发和并行
【并发】:两个或两个以上的事物在同一个时间段发生
【并行】:两个或两个以上的事物在同一个时刻发生。宏观并行,微观串行。
【高并发】:同时在一个时间段内,很多事情都发生了,这就是高并发。

2.多线程

2.1多线程的优缺点

优点:
	1.提升资源利用率
	2. 提高用户体验
缺点:
	1. 降低了其他线程的执行概率
	2. 用户会感受到软件的卡顿问题
	3. 增加的系统,资源压力
	4. 多线程情况下的共享资源问题,线程冲突,线程安全问题

2.2 创建自定义线程类的两种方式

class Thread类
	Java中的一个线程类
	Thread类是Runnable接口的实现类,同时提供了很多线程的操作使用的方法。
interface Runnable接口
	这里规定了what will be run?
	里面只有一个方法 run方法

【方式一】

自定义线程类,继承Thread类,重写run方法
创建自定义线程对象,直接调用start方法,开启线程

【方式二】

自定义线程类,遵从Runnable接口
使用自定义遵从接口Runnable实现类对象,作为Thread构造方法参数
借助于Thread类对象和start方法,开启线程

【推荐】

以上两种方式,推荐使用方拾二,遵从Runnable接口来完成自定义线程,不影响正常的继承逻辑,
并且可以使用匿名内部类来完成线程代码块的书写

【代码演示】

/*
 * 自定义线程类MyThread1继承Thread类
 */
class MyThread1 extends Thread {
	@Override
	public void run() {
		for (int i = 0; i < 10; i++) {
			System.out.println("继承Thread类自定义线程类");
		}
	}
}

/*
 * 自定义线程类MyThread2遵从Runnable接口
 */
class MyThread2 implements Runnable {
	@Override
	public void run() {
		for (int i = 0; i < 100; i++) {
			System.out.println("遵从Runnable接口实现自定义线程类");
		}
	}
}

public class Demo1 {
	public static void main(String[] args) {
		new Thread(new Runnable() {
			@Override
			public void run() {
				for (int i = 0; i < 100; i++) {
					System.out.println("匿名内部类方式创建对象,作为线程执行代码");
				}
			}
		}).start();
		// 创建一个继承Thread类自定义线程类对象
		MyThread1 myThread1 = new MyThread1();
		// 这里不是启动线程,而且将run方法做出一个普通方法执行。
		// myThread1.run();
		myThread1.start();
		
		// 创建一个Thread类对象,使用遵从Runnable接口的实现类作为构造方法参数
		Thread thread = new Thread(new MyThread2());
		// 借助于Thread类内的start方法开启线程
		thread.start();

		for (int i = 0; i < 100; i++) {
			System.out.println("main线程");
		}
	}
}

2.3自定义线程执行流程

执行流程图:

2.4 Thread类需要了解的方法

2.4.1 Constructor
Thread();
	分配一个新的线程对象,无目标,无指定名字
Thread(Runnable target);
	创建一个新的线程对象,并且在创建线程对象的过程中,使用Runnable接口的
	实现类对象作为执行的线程代码块目标
Thread(String name);
	创建一个新的线程,无指定目标,但是指定当前线程的名字是什么
Thread(Runnable target, String name);
	创建一个线程的线程对象,使用Runnable接口实现类对象,作为执行目标,并且指定name作为线程名
2.4.2 Method
void setName(String name);
String getName();
	以上两个是name属性setter和getter方法
void setPriority(int Priority);
	设置线程的优先级,非一定执行要求,只是增加执行的概率
	优先级数值范围 [1 - 10] 10最高 1最低 5默认
int getPriority();
	获取线程优先级
void start();
	启动线程对象

public static void sleep(int ms);
	当前方法是静态方法,通过Thread类调用,要求是当前所在线程代码块对应的线程,进行休眠操作,休眠指定的毫秒数
public static Thread currentThread();
	当前方法是静态方法,通过Thread类调用,获取当前所处代码块对应的线程对象。

3. 线程安全问题和解决办法

3.1共享资源使用问题

如:
出售< <我和我的祖国>>的电影票100张
出售方式:
淘票票、美团、猫眼
问题一:100张电影票用什么保存?
【静态成员变量】
问题二:资源冲突问题
在这里插入图片描述

3.2同步代码块

synchronized (/* 锁对象 */) {
    
}

/*
特征:
 	1. synchronized 小括号里面的对象是锁对象,并且要求如果是多线程的情况下,锁对象必须是同一个对象。
 	2. synchronized 大括号中的代码块就是需要进行同步的代码,或者说是加锁的代码,大括号里面的内容,有且只允许一个线程进入。
 	3. 同步代码块越短越好,在保证安全的情况下,提高性能
 
问题:
	1. 目前锁对象感觉很随意,存在一定的隐患
	2. 代码层级关系很复杂,看着有点麻烦
*/

3.3同步方法

synchronized 作为关键字来修饰方法,修饰的方法就是对应的同步方法,有且只允许一个线程进入,到底是谁来完成的加锁操作?

1. 静态成员方法
锁对象,是当前类对应的字节码文件.class 类名.class
2. 非静态成员方法
锁对象就是当前类对象 this

选择同步方法是否使用static修饰问题?

1. 如果非static修饰,要保证执行的线程对象有且只有一个,因为锁对象就是当前线程对象	
2. 如果是static修饰,锁对象具有唯一性,多个线程使用的锁是同一个锁。

3.4Lock锁

Java提供了一个对于线程安全问题,加锁操作相对于同步代码块和同步方法更加广泛的一种操作方式。

1. 对象化操作。
	创建Lock构造方法
		Lock lock = new ReentrantLock();
2. 方法化操作。
	开锁:
		unlock();
	加锁:
		lock();

3.5三种加锁方式的总结

1. 一锁一线程,一锁多线程问题。
使用对应的锁操作对应的线程,考虑静态和非静态问题。	同步方法和Lock锁使用。
静态是一锁多目标,非静态是一锁一目标

2. 涉及到同步问题时,要考虑好锁对象的选择问题
同步代码块,同步方法,Lock对象。

4.守护线程

守护线程,也称之为后台线程,如果当前主线程崩溃,守护线程也就崩溃。

守护线程一般用于:
	1. 自动下载
	2. 操作日志
	3. 操作监控

守护方法是通过线程对象
setDeamon(boolean flag);
true为守护线程
false缺省属性,正常线程

5. 线程状态

5.1 六种线程状态

线程如果按照java.lang.Thread.State枚举方式来考虑,一共提供了6中状态


状态 导致状态的条件
NEW(新建) 线程刚刚被创建,没有启动,没有调用start方法
RUNNABLE(可运行) 线程已经可以在JVM中运行,但是是否运行不确定,看当前线程是否拥有CPU执行权
BLOCKED(锁阻塞) 当前线程进入一个同步代码需要获取对应的锁对象,但是发现当前锁对象被其他线程持有,当前线程会进入一个BLOCKED。如果占用锁对象的线程打开锁对象,当前线程持有对应锁对象,进入Runnable状态
WAITING(无限等待) 通过一个wait方法线程进入一个无限等待状态,这里需要另外一个线程进行唤醒操作。进入无限等待状态的线程是无法自己回到Runnable状态,需要其他线程通过notify或者notifyAll方法进行唤醒操作
TIMED_WAITING(计时等待) 当前线程的确是等待状态,但是会在一定时间之后自动回到Runnable状态,例如Thread.sleep()或者是Object类内的wait(int ms);
TERMINATED(被终止) 因为Run方法运行结束正常退出线程,或者说在运行的过程中因为出现异常导致当前线程崩溃

5.2TIMED_WAITING(计时等待)

Thread.sleep(int ms);
	在对应线程代码块中,当前线程休眠指定的时间。
Object类内  wait(int ms);
	让当前线程进入一个计时等待状态
		1. 规定的时间及时完毕,线程回到可运行状态
		2. 在等待时间内,通过其他线程被notify或notifyAll唤醒
Sleep方法
	1. 调用之后休眠指定时间
	2. sleep方法必须执行在run方法内,才可以休眠线程
	3. sleep不会打卡当前线程占用的锁对象。

在这里插入图片描述

5.3BLOCKED(锁阻塞)

线程中有锁存在,线程需要进入带有锁操作的同步代码,如果锁对象被别人持有,只能在锁外等待

锁阻塞状态的线程是否能够抢到锁对象有很多因素
	1. 优先级问题,非决定因素
	2. CPU执行概率问题。

后期高并发一定会存在多线程操作锁对象问题,秒杀,抢购...
用队列方式来处理

在这里插入图片描述

发布了11 篇原创文章 · 获赞 15 · 访问量 4141

猜你喜欢

转载自blog.csdn.net/qq_41986648/article/details/104615900