JavaSE(四)线程并发

1.进程与线程
多进程:操作系统中同时运行的多个程序。
多线程:在同一个进程中同时运行的多个任务。
进程与线程的区别:
进程:有独立的内存空间,进程中的数据存放空间(堆空间和栈空间)是独立的,至少有一个线程。
线程:堆空间是共享的,栈空间是独立的,线程消耗的资源也比进程小,相互之间可以影响的,又称为轻型进程或进程元。
因为一个进程中的多个线程是并发运行的,那么从微观角度上考虑也是有先后顺序的,那么哪个线程执行完全取决于CPU调度器(JVM来调度),程序员是控制不了的。
我们可以把多线程并发性看作是多个线程在瞬间抢CPU资源,谁抢到资源谁就运行,这也造就了多线程的随机性。

Java程序的进程(Java的一个程序运行在系统中)里至少包含主线程和垃圾回收线程(后台线程)。
线程调度:
计算机通常只有一个CPU时,在任意时刻只能执行一条计算机指令,每一个进程只有获得CPU的使用权才能执行指令.
所谓多进程并发运行,从宏观上看,其实是各个进程轮流获得CPU的使用权,分别执行各自的任务.
那么,在可运行池中,会有多个线程处于就绪状态等到CPU,JVM就负责了线程的调度.
JVM采用的是抢占式调度,没有采用分时调度,因此可以能造成多线程执行结果的的随机性。
2.运行一个进程
方式1:Runtime类的exec方法:
方式2:ProcessBuilder的start方法:
在这里插入图片描述
3.创建一个线程
创建和启动线程,传统有两种方式:
方式1:继承Thread类;
方式2:实现Runnable接口;
方式1:继承Thread类:
步骤:
1):定义一个类A继承于java.lang.Thread类.
2):在A类中覆盖Thread类中的run方法.
3):我们在run方法中编写需要执行的操作---->run方法里的,线程执行体.
4):在main方法(线程)中,创建线程对象,并启动线程.
创建线程类对象: A类 a = new A类();
调用线程对象的start方法: a.start();//启动一个线程

注意:千万不要调用run方法,如果调用run方法好比是对象调用方法,依然还是只有一个线程,并没有开启新的线程.
在这里插入图片描述
方式2:实现Runnable接口;
步骤:
1):定义一个类A实现于java.lang.Runnable接口,注意A类不是线程类.
2):在A类中覆盖Runnable接口中的run方法.
3):我们在run方法中编写需要执行的操作---->run方法里的,线程执行体.
4):在main方法(线程)中,创建线程对象,并启动线程.
创建线程类对象: Thread t = new Thread(new A());
调用线程对象的start方法: t.start();
在这里插入图片描述
使用匿名内部类来创建线程:
只适用于某一个类只使用一次的情况.
在这里插入图片描述
4.线程的不安全问题

public void run() {
		for (int i = 0; i < 50; i++) {
			if (num > 0) {
				System.out.println(Thread.currentThread().getName() + "吃的苹果编号为"
						+ num-- + "的苹果");//currentThread 返回对当前正在执行的线程对象的引用。

			}
		}
	}
}

public class ImplementsDemo {
	public static void main(String[] args) {
		Apple a = new Apple();// 创建一个自定义类的对象作为thread构造器的参数

		new Thread(a, "A").start();// Thread(Runnable target, String name)
		new Thread(a, "B").start();// Thread(Runnable target, String name)
		new Thread(a, "C").start();// Thread(Runnable target, String name)

	}
}

在这里插入图片描述
图中看到编号50的苹果被吃了三次
吃苹果比赛-分析继承方式和实现方式的区别:
继承方式:
1):Java中类是单继承的,如果继承了Thread了,该类就不能再有其他的直接父类了.
2):从操作上分析,继承方式更简单,获取线程名字也简单.(操作上,更简单)
3):从多线程共享同一个资源上分析,继承方式不能做到.
实现方式:
1):Java中类可以多实现接口,此时该类还可以继承其他类,并且还可以实现其他接口(设计上,更优雅).
2):从操作上分析,实现方式稍微复杂点,获取线程名字也比较复杂,得使用Thread.currentThread()来获取当前线程的引用.
3):从多线程共享同一个资源上分析,实现方式可以做到(是否共享同一个资源).
但是你以为用实现就安全了吗,进行网络延迟模拟问题暴露
`class Apple1 implements Runnable {
private int num = 50;

public void run() {
	for (int i = 0; i < 50; i++) {
		if (num > 0) {
			try {
				Thread.sleep(10);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			System.out.println(Thread.currentThread().getName() + "吃了苹果编号"
					+ num-- + "的苹果");
		} //此处输出的结果 有很多编号重复了 ,而且有负的编号
	}
}

}`在这里插入图片描述
在这里插入图片描述
5.同步
以上问题必须用同步解决
方式1:同步代码块
方式2:同步方法
方式3:锁机制(Lock)
在这里插入图片描述
在这里插入图片描述
synchronized的好与坏:
好处:保证了多线程并发访问时的同步操作,避免线程的安全性问题.
缺点:使用synchronized的方法/代码块的性能比不用要低一些.
建议:尽量减小synchronized的作用域.
同步锁(Lock):
Lock机制提供了比synchronized代码块和synchronized方法更广泛的锁定操作,同步代码块/同步方法具有的功能Lock都有,除此之外更强大,更体现面向对象.
在这里插入图片描述
6.生产者与消费者模型

public class App {
public static void main(String[] args) {
	//创建生产者和消费者的共同对象
	ShareResource resource = new ShareResource();
	//启动生产者线程
	//创建同一个资源对象  是在调用它的时候将他传过来 ,确保是同一个 资源对象
	new Thread(new Producer(resource)).start();
	//启动消费者线程
	new Thread(new Consumer(resource)).start();
}
}
public class Consumer implements Runnable{
//共享资源对象
	private ShareResource resource = null ;
	
	
	public Consumer(ShareResource resource){
		this.resource  = resource ;
	}
	
	
	//消费者消费对象
	public void  run() {
		for (int i = 0; i < 500; i++) {
			resource.popup();
		}
	}
}
public class Producer implements Runnable {
	// 共享资源对象
	private ShareResource resource = null;
/*//创建同一个资源对象,对共享资源对象进行初始化操作
 * 这里不可以new ,因为消费者里面再new 就有两个对象   要确保是同一个资源对象
 * 
 * resource对象的初始化 :在下面中使用了resource,必须进行初始化 ,不然会报空指针错误,直接赋值不行
 * 通过构造器实现      构造器的初始化 : 接收一个shareresource类型的对象,在下次其他类中
 *  new priducer对象时进行初始化
*/
	public Producer(ShareResource resource) {
		this.resource = resource;
	}

	public void run() {
		for (int i = 0; i < 500; i++) {// 生产50个对象
			if (i % 2 == 0) {
				resource.push("凤姐", "女");
			} else {
				resource.push("春哥", "男");
			}
		}
	}
}
//共享资源对象 ,  姓名-性别
public class ShareResource {

	private String name ;
	private String gender;
	
	//生产者向共享资源对象存储数据 
	public void push(String name,String gender){
		this.name = name;
	/*	try {
			Thread.sleep(10);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}*/
		this.gender = gender;
	}
	
	//消费者从共享资源对象获取数据
	public void popup(){
		System.out.println(this.name + "-" + this.gender);
	}
}

分析生产者和消费者案例存在的问题:
建议在生产姓名和性别之间以及在打印之前使用Thread.sleep(10);使效果更明显.
此时出现下面的情况:
问题1:出现姓别紊乱的情况.
解决方案:只要保证在生产姓名和性别的过程保持同步,中间不能被消费者线程进来取走数据.
可以使用同步代码块/同步方法/Lock机制来保持同步性.
问题2:应该出现生产一个数据,消费一个数据.
应该交替出现: 春哥哥-男–>凤姐-女–>春哥哥-男–>凤姐-女…
解决方案: 得使用 等待和唤醒机制.
7.同步锁池
同步锁池:
同步锁必须选择多个线程共同的资源对象.
当前生产者在生产数据的时候(先拥有同步锁),其他线程就在锁池中等待获取锁.
当线程执行完同步代码块的时候,就会释放同步锁,其他线程开始抢锁的使用权.
在这里插入图片描述
8.等待与唤醒
线程通信-wait和notify方法介绍:
java.lang.Object类提供类两类用于操作线程通信的方法.
wait():执行该方法的线程对象释放同步锁,JVM把该线程存放到等待池中,等待其他的线程唤醒该线程.
notify:执行该方法的线程唤醒在等待池中等待的任意一个线程,把线程转到锁池中等待.
notifyAll():执行该方法的线程唤醒在等待池中等待的所有的线程,把线程转到锁池中等待.
在这里插入图片描述
9.Lock和Condition接口
wait和notify方法,只能被同步监听锁对象来调用,否则报错IllegalMonitorStateException.
那么现在问题来了,Lock机制根本就没有同步锁了,也就没有自动获取锁和自动释放锁的概念.
因为没有同步锁,所以Lock机制不能调用wait和notify方法.
解决方案:Java5中提供了Lock机制的同时提供了处理Lock机制的通信控制的Condition接口.
从Java5开始,可以:
1):使用Lock机制取代synchronized 代码块和synchronized 方法.
2):使用Condition接口对象的await,signal,signalAll方法取代Object类中的wait,notify,notifyAll方法.
在这里插入图片描述
在这里插入图片描述
10.线程状态
1:新建状态(new):使用new创建一个线程对象,仅仅在堆中分配内存空间,在调用start方法之前.
新建状态下,线程压根就没有启动,仅仅只是存在一个线程对象而已.
Thread t = new Thread();//此时t就属于新建状态
当新建状态下的线程对象调用了start方法,此时从新建状态进入可运行状态.
线程对象的start方法只能调用一次,否则报错:IllegalThreadStateException.
2:可运行状态(runnable):分成两种状态,ready和running。分别表示就绪状态和运行状态。
就绪状态:线程对象调用start方法之后,等待JVM的调度(此时该线程并没有运行).
运行状态:线程对象获得JVM调度,如果存在多个CPU,那么允许多个线程并行运行
3:阻塞状态(blocked):正在运行的线程因为某些原因放弃CPU,暂时停止运行,就会进入阻塞状态.
此时JVM不会给线程分配CPU,直到线程重新进入就绪状态,才有机会转到运行状态.
阻塞状态只能先进入就绪状态,不能直接进入运行状态.
阻塞状态的两种情况:
1):当A线程处于运行过程时,试图获取同步锁时,却被B线程获取.此时JVM把当前A线程存到对象的锁池中,A线程进入阻塞状态.
2):当线程处于运行过程时,发出了IO请求时,此时进入阻塞状态.

4:等待状态(waiting)(等待状态只能被其他线程唤醒):此时使用的无参数的wait方法,
1):当线程处于运行过程时,调用了wait()方法,此时JVM把当前线程存在对象等待池中.

5:计时等待状态(timed waiting)(使用了带参数的wait方法或者sleep方法)
1):当线程处于运行过程时,调用了wait(long time)方法,此时JVM把当前线程存在对象等待池中.
2):当前线程执行了sleep(long time)方法.
6:终止状态(terminated):通常称为死亡状态,表示线程终止.
1):正常执行完run方法而退出(正常死亡).
2):遇到异常而退出(出现异常之后,程序就会中断)(意外死亡).
线程一旦终止,就不能再重启启动,否则报错(IllegalThreadStateException).
11.线程休眠
线程休眠:让执行的线程暂停一段时间,进入计时等待状态。
方法:static void sleep(long millis)
调用sleep后,当前线程放弃CPU,在指定时间段之内,sleep所在线程不会获得执行的机会。
此状态下的线程不会释放同步锁/同步监听器.
在这里插入图片描述
12.联合线程
线程的join方法表示一个线程等待另一个线程完成后才执行。join方法被调用之后,线程对象处于阻塞状态。
有人也把这种方式称为联合线程,就是说把当前线程和当前线程所在的线程联合成一个线程。
在这里插入图片描述
13.后台线程
后台线程:在后台运行的线程,其目的是为其他线程提供服务,也称为“守护线程"。JVM的垃圾回收线程就是典型的后台线程。
特点:若所有的前台线程都死亡,后台线程自动死亡,前台线程没有结束,后台线程是不会结束的。
测试线程对象是否为后台线程:使用thread.isDaemon()。
前台线程创建的线程默认是前台线程,可以通过setDaenon(true)方法设置为后台线程,并且当且仅当后台线程创建的新线程时,新线程是后台线程。
设置后台线程:thread.setDaemon(true),该方法必须在start方法调用前,否则出现IllegalThreadStateException异常
在这里插入图片描述
14.线程优先级
每个线程都有优先级,优先级的高低只和线程获得执行机会的次数多少有关,并非线程优先级越高的就一定先执行,哪个线程的先运行取决于CPU的调度。
MAX_PRIORITY=10,最高优先级
MIN_PRIORITY=1,最低优先级
NORM_PRIORITY=5,默认优先级
int getPriority() :返回线程的优先级。
void setPriority(int newPriority) : 更改线程的优先级。

优先级的高低只和线程获得执行机会的次数多少有关,
并非线程优先级越高的就一定先执行,哪个线程的先运行取决于CPU的调度。
*/

class PriorityThread extends Thread{
	public PriorityThread(String name){
		super(name);//调用父类的构造器
	}
	public void run(){
		for (int i = 0; i < 111; i++) {
			System.out.println(super.getName() +  "-" + i);
		}
	}
}

public class PriorityDemo {
public static void main(String[] args) {
	//设置当前线程的优先级
	//Thread.currentThread().setPriority(8);
	
	//获取当前的线程的优先级级别
	//System.out.println(Thread.currentThread().getPriority());
	PriorityThread max = new PriorityThread("高优先级");
	max.setPriority(Thread.MAX_PRIORITY);
	PriorityThread min = new PriorityThread("低优先级");
	min.setPriority(Thread.MIN_PRIORITY);
	min.start();
	max.start();
	
}
}

在这里插入图片描述
15.线程礼让
yield方法:表示当前线程对象提示调度器自己愿意让出CPU资源,但是调度器可以自由的忽略该提示。
调用该方法之后,线程对象进入就绪状态,所以完全有可能:某个线程调用了yield()之后,线程调度器又把它调度出来重新执行。
从Java7提供的文档上可以清楚的看出,开发中很少会使用到该方法,该方法主要用于调试或测试,它可能有助于因多线程竞争条件下的错误重现现象。

/*sleep方法会给其他线程运行机会,但是不考虑其他线程的优先级,
 * yield方法只会给相同优先级或者更高优先级的线程运行的机会.
    调用sleep方法后,线程进入计时等待状态,调用yield方法后,线程进入就绪状态.*/


class YieldThread extends Thread{
	public YieldThread(String name){
		super(name);//调用父类的构造器
	}
	public void run(){
		for (int i = 0; i < 111; i++) {
			System.out.println(super.getName() +  "-" + i);
			if(i ==20){
				Thread.yield();//当i=20的时候,做出礼让   (效果不明显)
			}
		}
	}
}

public class YieldDemo {
public static void main(String[] args) {
	//设置当前线程的优先级
	//Thread.currentThread().setPriority(8);
	
	//获取当前的线程的优先级级别
	//System.out.println(Thread.currentThread().getPriority());
	YieldThread max = new YieldThread("高优先级");
	max.setPriority(Thread.MAX_PRIORITY);
	YieldThread min = new YieldThread("低优先级");
	min.setPriority(Thread.MIN_PRIORITY);
	min.start();
	max.start();
	
}
}

在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/qq_37282683/article/details/88737831
今日推荐