作者:hanlei99999 | 来源:互联网 | 2023-09-14 18:35
- 线程的概述
进程:正在运行的程序,负责了这个程序的内存空间分配,代表了内存中的执行区域。
线程:就是在一个进程中负责一个执行路径。
多线程:就是在一个进程中多个执行路径同时执行。
多线程的好处:
- 解决了一个进程里面可以同时运行多个任务(执行路径)。
- 提供资源的利用率,而不是提供效率。
多线程的弊端:
- 降低了一个进程里面的线程的执行频率。
- 对线程进行管理要求额外的 CPU开销。线程的使用会给系统带来上下文切换的额外负担。
- 公有变量的同时读或写。当多个线程需要对公有变量进行写操作时,后一个线程往往会修改掉前一个线程存放的数据,发生线程安全问题。
- 线程的死锁。即较长时间的等待或资源竞争以及死锁等多线程症状。
在java中要想实现多线程,有两种手段,一种是继续Thread类,另外一种是实现Runable接口.(其实准确来讲,应该有三种,还有一种是实现Callable接口,并与Future、线程池结合使用,此文这里不讲这个。
Thread和Runnable的区别(这个在面试中也被常常问到,希望多注意)
如果一个类继承Thread,则不适合资源共享。但是如果实现了Runable接口的话,则很容易的实现资源共享。
总结:
实现Runnable接口比继承Thread类所具有的优势:
1):适合多个相同的程序代码的线程去处理同一个资源
2):可以避免java中的单继承的限制
3):增加程序的健壮性,代码可以被多个线程共享,代码和数据独立
4):线程池只能放入实现Runable或callable类线程,不能直接放入继承Thread的类
参考了博文:https://www.cnblogs.com/yjd_hycf_space/p/7526608.html
线程状态转换
下面的这个图非常重要!你如果看懂了这个图,那么对于多线程的理解将会更加深刻!
1、新建状态(New):新创建了一个线程对象。
2、就绪状态(Runnable):线程对象创建后,其他线程调用了该对象的start()方法。该状态的线程位于可运行线程池中,变得可运行,等待获取CPU的使用权。
3、运行状态(Running):就绪状态的线程获取了CPU,执行程序代码。
4、阻塞状态(Blocked):阻塞状态是线程因为某种原因放弃CPU使用权,暂时停止运行。直到线程进入就绪状态,才有机会转到运行状态。阻塞的情况分三种:
(一)、等待阻塞:运行的线程执行wait()方法,JVM会把该线程放入等待池中。(wait会释放持有的锁)
(二)、同步阻塞:运行的线程在获取对象的同步锁时,若该同步锁被别的线程占用,则JVM会把该线程放入锁池中。
(三)、其他阻塞:运行的线程执行sleep()或join()方法,或者发出了I/O请求时,JVM会把该线程置为阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态。(注意,sleep是不会释放持有的锁)
5、死亡状态(Dead):线程执行完了或者因异常退出了run()方法,该线程结束生命周期。
Java多线程实例 3种实现方法
Java中的多线程有三种实现方式:
1.继承Thread类,重写run方法。Thread本质上也是一个实现了Runnable的实例,他代表一个线程的实例,并且启动线程的唯一方法就是通过Thread类的start方法。
2.实现Runnable接口,并实现该接口的run()方法.创建一个Thread对象,用实现的Runnable接口的对象作为参数实例化Thread对象,调用此对象的start方法。
3.实现Callable接口,重写call方法。Callable接口与Runnable接口的功能类似,但提供了比Runnable更强大的功能。有以下三点
1).Callable可以在人物结束后提供一个返回值,Runnable没有提供这个功能。
2).Callable中的call方法可以抛出异常,而Runnable的run方法不能抛出异常。
3).运行Callable可以拿到一个Future对象,表示异步计算的结果,提供了检查计算是否完成的方法。
需要注意的是,无论用那种方式实现了多线程,调用start方法并不意味着立即执行多线程代码,而是使得线程变为可运行状态。
run start的区别
start方法是启动一个线程,而线程中的run方法来完成实际的操作。
如果开发人员直接调用run方法,那么就会将这个方法当作一个普通函数来调用,并没有多开辟线程,开发人员如果希望多线程异步执行,则需要调用start方法。
sleep wait的区别
1.两者处理的机制不同,sleep方法主要是,让线程暂停执行一段时间,时间一到自动恢复,并不会释放所占用的锁,当调用wait方法以后,他会释放所占用的对象锁,等待其他线程调用notify方法才会再次醒来。
2.sleep是Threa的静态方法,是用来控制线程自身流程的,而wait是object的方法,用于进行线程通信。
3.两者使用的区域不同。sleep可以在任何地方使用,wait必须放在同步控制方法,或者语句块中执行。
synchronized notify wait的运用
synchronized关键字有两种用法,synchronized方法和synchronized语句块。
public synchronized void function(){}
synchronized(object){}
当某个资源被synchronized所修饰,线程1线程2等多个线程在共同请求这个资源,线程1先请求到,调用了对象的wait方法释放了对象的锁,此时线程2可以对这个对象进行访问,在工作结束时可以调用对象的notify方法,唤醒等待队列中正在等待的线程,此时被唤醒的线程将会再一次拿到对象锁,对对象进行操作。可以调用notifyAll方法,唤醒等待队列中的所有线程。
需要注意的是一个线程被唤醒不代表立即获取对象锁,必须等调用的线程对象的方法推出synchronized块释放对象锁后,被唤醒的进程才会获得对象锁。
一:创建线程的方式
- 创建线程的方式一 继承Thread类
package cn.thread.demo2;/** 创建线程的步骤:
1 定义一个类继承Thread。
2 重写run方法。
3 创建子类对象,就是创建线程对象。
4 调用start方法,开启线程并让线程执行,同时还会告诉jvm去调用run方法。*/
//1.定义一个类继承Thread。
public class MyThread2 extends Thread {// 2 .重写run方法。public void run() {for (int i &#61; 0; i <5; i&#43;&#43;) {//获取线程对象System.out.println(Thread.currentThread() &#43; ":" &#43; i);//获取线程的名称//System.out.println(this.getName()&#43;i);}}public static void main(String[] args) {// 3.创建子类对象&#xff0c;就是创建线程对象。MyThread2 mt &#61; new MyThread2();Thread t1 &#61; new Thread(mt);Thread t2 &#61; new Thread(mt);Thread t3 &#61; new Thread(mt);//4. 调用start方法&#xff0c;开启线程并让线程执行&#xff0c;同时还会告诉jvm去调用run方法。t1.start();t2.start();t3.start();}
}
- 创建线程的方式二 实现runnable,项目实际开发中一般采用实现的方式
-
package cn.thread.demo2;
/** 创建线程的第二种方式.使用Runnable接口.
该类中的代码就是对线程要执行的任务的定义.
1&#xff1a;定义了实现Runnable接口
2&#xff1a;重写Runnable接口中的run方法&#xff0c;就是将线程运行的代码放入在run方法中
3&#xff1a;通过Thread类建立线程对象
4&#xff1a;将Runnable接口的子类对象作为实际参数&#xff0c;传递给Thread类构造方法
5&#xff1a;调用Thread类的start方法开启线程&#xff0c;并调用Runable接口子类run方法*/
public class SellTicketDemo implements Runnable {// 定义票的总数private int total &#61; 100;// 定义票的编号private int no &#61; total &#43; 1;// 定义一个线程同步对象private Object obj &#61; new Object();// 2&#xff1a;重写Runnable接口中的run方法&#xff0c;就是将线程运行的代码放入在run方法中public void run() {while (true) {// 同步锁synchronized (this.obj) {//为什么要加同步锁&#xff0c;因为四个线程在争抢资源&#xff0c;所以需要同步锁去处理if (this.total > 0) {try {Thread.sleep(1);//睡眠1000毫秒&#xff0c;1000毫秒&#61;1秒&#xff0c;线程执行的速度 } catch (InterruptedException e) {e.printStackTrace();}//Thread.currentThread().getName()获取线程名称 String msg &#61; Thread.currentThread().getName() &#43; " 售出第 " &#43; (this.no - this.total) &#43; " 张票";System.out.println(msg);this.total--;} else {System.out.println("票已售完&#xff0c;请下次再来&#xff01;");System.exit(0);}}}}public static void main(String[] args) {//3 通过Thread类建立线程对象SellTicketDemo std &#61; new SellTicketDemo();// 把对象放入线程中Thread t1 &#61; new Thread(std, "售票窗口1");Thread t2 &#61; new Thread(std, "售票窗口2");Thread t3 &#61; new Thread(std, "售票窗口3");Thread t4 &#61; new Thread(std, "售票窗口4");//5&#xff1a;调用Thread类的start方法开启线程&#xff0c;并调用Runable接口子类run方法t1.start();t2.start();t3.start();t4.start();}}