java-多线程学习(一)

java多线程(一)

声明:本博客只用于本人笔记,不保证笔记正确性,但欢迎指出错误,如有遇喷子,直接反击加举报~~

进程、线程

进程是是一个应用程序(1个进程是一个软件)。

线程是一个进程中的执行场景/执行单元。

一个进程可以是多个线程。

例如:对于java程序来说,当在DOS命名窗口中输入:java HelloWorld回车之后。会先启动JVM,而JVM就是一个进程。JVM再启动一个主线程调用main方法。同时再启动一个垃圾回收线程负责看护,回收垃圾。最起码,现在的java程序中至少有两个线程并发,一个是垃圾回收线程,一个是执行main方法的主线程。

进程和线程是什么关系?

举个例子:

​ 阿里巴巴:进程

​ 马云:阿里巴巴的一个线程

​ 童文红:阿里巴巴的一个线程

​ 华为:进程

​ 任正非:华为的一个线程

进程可以看做是现实生活中的公司。

线程可以看做是公司当中的某个员工。

注意:

进程A和进程B的内存不共享。(阿里巴巴和京东资源不会共享!)

在java语言中线程A和线程B,堆内存和方法区内存共享。但是栈内存独立,一个线程一个栈。

	假设启动10个进程,会有10个栈空间,栈和栈之间互不干扰,各自执行各自的,这个就是多线程并发。

火车站,可以看做是一个进程。火车站中的每一个售票可以看做是一个线程。就好比我在窗口1购票,你可以在窗口2购票,那你不需要等我,我也不需要等 你。所以多线程并发可以提高效率。

java中之所以有多线程机制,目的就是为了提高程序的处理效率。

使用了多线程机制之后,main方法结束,其他线程也 不一定结束,因为main方法结束之时主线程结束了,主栈空了,其它的栈(线程)可能还在压栈弹栈。

在这里插入图片描述

真正的多线程并发:t1线程执行t1的,t2线程执行t2的,t1不会影响t2,但是t2也不会影响t1。这叫真正的多线程并发。

4核CPU表示同一个时间点上,可以真正的有4个进程并发执行。

单核的CPU表示只有一个大脑:对于单核的CPU来说,在某一个时间点上实际上只能处理一件事情,但是由于CPU的处理速度极快,多个线程之间频繁切换执行,跟人的感觉就是多个事情同时再做…

不能够做到真正的多线程并发,但是可以给人一种“多线程并发的感觉。”

java支持多线程。并且java已经将多线程实现了,我们只需要继承就行了。

实现线程的方式

第一种方式:编写一个类,直接继承java.langThread,重写run方法。

/*
注意:亘古不变的道理:代码永远都是自上而下的顺序一次逐行执行的 
*/

public class Thread01 
{

	public static void main(String[] args){
		//创建线程对象
		MyThread t = new MyThread();
		/*
		start()作用是:启动一个分支线程,在JVM中开辟一个新的栈空间,在任务完成之后,瞬间就结束了,
		这段代码的任务只是为了开启一个新的一个栈空间,只要新的栈空间出来,start()方法瞬间结束,线程就启动成功了
		启动成功后的线程会自动调用这个run()方法,并且run方法在分支栈的栈底部(压栈)。
		run()方法在分支栈的栈底部,main方法在主栈的栈底部。run和main是平级的。
		
		*/
		t.start();//启动线程
		for(int i = 0 ; i < 1000 ; i++){
			System.out.println("主线程------->" + i);
		}
	}

   

}
//定义线程类
class MyThread extends Thread
{
	public void run(){
		for(int i = 0 ; i < 1000 ;  i++){
			System.out.println("分支线程------->" + i);
		}
	}
}

运行结果为:

在这里插入图片描述

但是如果这个地方没有启用分支栈(也就是没有启动除主线程以外的线程)而是直接的调用run()方法。程序是不会并发的执行的。观察以下的代码:

/*
注意:亘古不变的道理:代码永远都是自上而下的顺序一次逐行执行的 
*/

public class Thread01 
{

	public static void main(String[] args){
		//创建线程对象
		MyThread t = new MyThread();
		t.run();
		
		//my.start();//启动线程
		for(int i = 0 ; i < 1000 ; i++){
			System.out.println("主线程------->" + i);
		}
	}

   

}
//定义线程类
class MyThread extends Thread
{
	public void run(){
		for(int i = 0 ; i < 1000 ;  i++){
			System.out.println("分支线程------->" + i);
		}
	}
}

运行的结果为:

在这里插入图片描述

下面用内存图的方式来分析分析。

执行run()方法的内存分析

在这里插入图片描述

先把main方法栈帧压进来,然后执行MyThread t = new MyThread(),随后在堆空间中开辟一块内存空间用来存放MyThread对象,这段执行完以后开始执行run()方法,注意:这里只是调用run方法,并没有开启一个新的线程,把run方法压入主线程。

在这里插入图片描述

run方法开始执行,然后开始打印0–999

在这里插入图片描述

等run方法执行完以后,run方法栈帧就结果了不在执行,然后程序才会往下继续的执行

在这里插入图片描述

最后才执行下面的代码,也就是main方法主栈的0–999。

执行start()方法的内存图分析

在这里插入图片描述

前面和上面的一样,等到了执行到start()方法以后,会停下来
在这里插入图片描述

把start()方法压入主线程栈中

在这里插入图片描述

start()方法任务是开辟一个新的栈空间,称为分支栈,只要开完这个方法就结束。

在这里插入图片描述

开辟出新的空间,start()方法结束

在这里插入图片描述

然后两个分支栈和主栈各自执行(并发执行),互不干扰。

在这里插入图片描述

run()方法是不需要手动来调用的,它直接由JVM线程调度机制来运作的。

第二种方法:编写一个类实现java.lang.Runnable接口,实现run方法

public class  RunnableTest{
	public static void main(String[] args){
		//把m包装成Runnable类型
		MyRunnable m = new MyRunnable();
		//然后传进thread()里面,因为Thread提供了一个构造方法,Thread(Runnable target)
		Thread t = new Thread(m);
		t.start();//启动一个分线程

		for(int i = 0 ; i < 100 ; i++){
			System.out.println("主线程----->"+i);
		}
	}
}
class MyRunnable implements Runnable{
	//实现run()方法
	public void run(){
		for(int i = 0 ; i < 100 ; i++){
			System.out.println("分支线程------->"+i);
		}
	}
}

运行结果:

在这里插入图片描述

注意:第二种方式实现接口比较常用,因为一个类实现了接口,它还可以去继承其他的类,更加的灵活。

也可以用其他的方式实现,用匿名内部类方式实现

public class RunnableTest
{
	public static void main(String[] args){

		//使用匿名内部类
		Thread t = new Thread(new Runnable(){

			public void run(){
				for(int i = 0 ; i < 100 ; i++){
					System.out.println("分支线程------->"+i);
				}
				
			}
		});
       t.start();

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

运行结果:

在这里插入图片描述

多线程的生命周期

关于线程的生命周期:

  • 新建状态
  • 就绪状态
  • 运行状态
  • 阻塞状态
  • 死亡状态

刚 new 出来的线程对象处于新建状态,然后调用 start() 方法,进入到就绪状态以后就拥有了抢夺CPU的时间片的权利,抢到时间片以后执行 run() 方法,run() 方法执行标志着这个阶段进入到运行阶段,等到时间片用完以后又回到就绪状态继续抢夺,直到 run() 方法执行完成。等到 run() 结束以后这个线程就进入到了死亡状态。具体情况看一下图片

在这里插入图片描述

然后当线程遇到阻塞事件,例如接受用户输入,就会进入到阻塞状态,阻塞状态下,此线程会放弃之前占用的CPU时间片

在这里插入图片描述

当阻塞结束的时候,又回到就绪状态,继续抢夺CPU时间片
在这里插入图片描述

如果是多个线程抢夺时间片,抢夺到的进入到运行状态,等时间片用完又回到就绪状态,然后又开始抢夺时间片,就造成了并发的现象。

获取当前线程对象

  • Thread.currentThread() (这个是静态的)获取当前线程对象
  • 调用getName()获取当前线程的名字
  • 调用setName()方法修改线程的名字
  • 如果没有设置线程的名字,线程默认的名字是Thread-0、Thread-1、Thread-2…
public class Thread02 extends Thread
{
	public static void main(String[] args){

			//获取当前的线程的名字,在main中就是主线程
			Thread currentThread = Thread.currentThread();
			//调用getName()方法输出此线程的名字
			System.out.println(currentThread.getName());
			Thread02 t = new Thread02();
			System.out.println(t.getName());
			Thread02 t1 = new Thread02();
			System.out.println(t1.getName());
			t.setName("分支t");
			System.out.println(t.getName());
			t.start();
			t1.start();

	}

	public void run(){
		for(int i = 0 ; i < 100 ; i++){

			//在分线程中调用就是分线程的名字,谁启动它就是谁的名字~~
			Thread cc = Thread.currentThread();
			System.out.println(cc.getName()+"----->"+i);
		}
	}
}

运行结果为:

在这里插入图片描述

关于线程的sleep()方法

  • 静态方法Thread.sleep(1000);
  • 参数是毫秒数
  • 作用是:让当前线程进入到休眠状态,进入到“阻塞状态”,并且放弃CPU时间片,将CPU时间片让给其他线程
  • 这代码出现在 A 的时候就让 A 线程睡眠,出现在 B 线程就会让 B 线程睡眠
  • Thread.sleep()方法,可以做到这种效果,间隔特定的时间,去执行特定的代码,每隔多久执行一次
public class Thread03  
{
	public static void main(String[] args){

		
	//	try{
			//让 main 线程睡眠1毫秒
			Thread.sleep(1000);
		/*}catch(InterruptedException e){
			e.printStackTrace();
		}*/
		System.out.println("5秒以后执行这个语句");

	}
}

注意:这个地方要抛异常,如果没有抛异常会出现以下情况:

在这里插入图片描述

关于Thread.sleep()的一个面试题

public class Thread04
{
	public static void main(String[] args){
		//多线程
		Thread t = new MyThread();
		t.setName("t");
		t.start();
		try{
			t.sleep(1000*5);//这里调用会不会让t线程睡眠5秒????
		}catch(InterruptedException e){
			e.printStackTrace();
		}

		System.out.println("hello world!");

	}
		
}

class MyThread extends Thread{
	public void run(){
		for(int i = 0 ; i < 100 ; i++){
			//输出当前线程的名字
			System.out.println(currentThread().getName()+"----->"+i);
		}
	}
}

答案是不会~~

因为sleep()方法是一个静态方法,它和MyThread对象没有关系,也和t没有关系,就算是t去调也是没用,还是会转化为Thread.sleep(1000*5),关于静态方法是否被继承和重写可以参考一下我下篇(传送门)这行代码的作用是让当前代码进入睡眠5秒,在main线程里面所以会让main线程睡眠5秒…

终止正在睡眠的线程

/*注意:这不是终断线程的执行,而是终止线程的睡眠*/

public class ThreadInterrupted
{
	public static void main(String[] args){
		Thread t = new Thread(new Runnables());
		t.setName("t");//将线程的名字设置为t
		t.start();//开启一个新的线程
		try{
			Thread.sleep(1000*5);//让mian线程睡眠5秒以后醒来
		
		}catch(InterruptedException e){
			e.printStackTrace();//打印异常信息
		}
		//终断t的睡眠,这个利用了java的异常机制
		t.interrupt();//相当于一盆冷水泼过去!!!是interrupt(),不是interrupted()
	}
}

class Runnables implements Runnable{
	//实现的接口的run方法
	public void run(){
		System.out.println(Thread.currentThread().getName()+"--------->begin");
		try{
			//sleep()方法是静态的,在哪个线程里面调,哪个线程就睡眠
			Thread.sleep(1000*60*60*24*365);//让此线程睡眠1年
		}catch(InterruptedException e){
			e.printStackTrace();//打印异常信息
		}
		//1年以后才会执行到这里
		System.out.println(Thread.currentThread().getName()+"--------->end");
		
	}
}

运行结果为:

在这里插入图片描述

如果不想看到异常,可以把抛异常里面的输出语句打印出来。

强行终止一个线程的执行

public class EndThread
{
	public static void main(String[] args){
		Thread t = new Thread(new MyRunnable());
		t.setName("t");
		t.start();
		//设置一个线程计数5秒
		try{
			Thread.sleep(1000*5);
		}catch(InterruptedException e){
			e.printStackTrace();
		}
		t.stop();// 已过时,强行终止线程
		
	}
}

class MyRunnable implements Runnable
{
	public void run(){
		for(int i = 0 ; i < 10 ; i++){
			System.out.println(Thread.currentThread().getName()+"------------>"+i);
			try{
				Thread.sleep(1000);//每一秒打印一个数
			}catch(InterruptedException e){
				e.printStackTrace();
			}
		}
	}
}

运行结果:

在这里插入图片描述

stop()方法的缺点:这种方式存在很大的缺点。因为这种方式是直接将线程杀死了,线程没有保存的数据将会丢失,不建议使用。

那怎么合理的终止一个线程执行的呢?

public class Thread05
{
	public static void main(String[] args){
		MyRunnable  r = new MyRunnable();
		Thread t = new Thread(r);
		t.setName("t");
		t.start();
		//计时5秒让线程终止
		try{
			Thread.sleep(5000);
		}catch(InterruptedException e){
			e.printStackTrace();
		}
		System.out.println("将flag设为false");
		r.flag = false;
		
	}
}

class MyRunnable implements Runnable
{
	//打一个boolean标记
	boolean flag = true;

	public void run(){
		
			for(int i = 0 ; i < 10 ; i++){
					if(flag){
				System.out.println(Thread.currentThread().getName()+"---->"+i);
				//让线程每一秒输出一个
					try{
						Thread.sleep(1000);
					}catch(InterruptedException e){
						e.printStackTrace();
					}
				}else{
				//在这里面输入保存数据的代码保存数据保证数据不丢失
				System.out.println("保存数据的代码.....");
				return;
			   }
		}
	}
}

线程的调度

1.1 常见的线程调度模型有哪些?

  • **抢占式调度模型:**哪个线程的优先级比较高,抢到的CPU时间片的概率就高一些(多一些)。java采用的就是抢占式调度模型。
  • **均分式调度模型:**平均分配CPU时间片。每个线程占有的时间片长度一样。平均分配,一切平等。

1.2 java中提供了哪些方法是和线程调度 有关系呢?

实例方法:

  • void setPriority(int newPriority):设置线程的优先级。
  • int getPriority():获取线程优先级。
  • 最低优先级1
  • 默认优先级5
  • 最高优先级10
  • 优先级比较高的获取CPU时间片可能会多一些。(但也不完全是,大概率是多的)。

静态方法:

  • static void yield():让位方法。
  • 暂停当前正在执行的线程对象,并执行其他线程。
  • yield():方法不是阻塞方法,是让当前线程让位,让给其它线程使用。
  • yield():方法的执行会让当前线程从“运行状态”回到“就绪状态”

实例方法:

void join()

合并线程

class MyThread1 extends Thread{

    public void fun(){
        MyThread t = new MyThread();
        t.join();//当前线程进入阻塞,t线程执行知道t线程结束,当前线程才可以执行

    }

}

class MyThread2 extends Thread{
    
}
    

1.3 线程的优先级

public class My_Thread
{
	public static void main(String[] args){
		Thread t = new Thread(new Threads());
		t.setName("t");//将线程的名字设置为t
		
		t.setPriority(10);//设置线程的优先级,(线程的优先级最高为10,最低为1,默认为5)
	
		//Thread myCurrentThread = Thread.currentThread();//获取当前线程
		Thread.currentThread().setPriority(1);//将main线程设置为优先级设置值为1、

		System.out.println(Thread.currentThread().getName()+"的优先级是" + Thread.currentThread().getPriority());
	
		t.start();//开启t线程
		//main默认的线程是5。不过上面设置为了1
		
		for(int i = 0 ; i < 10000 ; i++){
			System.out.println(Thread.currentThread().getName()+"---->main"+i);
		} 
	
	}
}

class Threads implements Runnable
{
	public void run(){
		System.out.println(Thread.currentThread().getName()+"的优先级为"+Thread.currentThread().getPriority());
		for(int i = 0 ; i < 10000 ; i++){
			System.out.println(Thread.currentThread().getName()+"---------------------->begin"+i);
		}
	}
}

1.4 线程让位

当前线程暂停,回到就绪状态,让给其他线程。

静态方法:Thread.yield();

public class ThreadYield
{
	public static void main(String[] args){
		Thread t = new Thread(new MyRunnable());
		t.setName("t");
		t.start();
		for(int i = 0 ; i < 10000; i++){
			System.out.println(Thread.currentThread().getName()+"---->"+i);
		}
	}
}
class MyRunnable implements Runnable
{
	public void run(){
		for(int i = 0 ; i < 10000 ; i++){
			if(i%100==0)//每让一次
				Thread.yield();
			System.out.println(Thread.currentThread().getName()+"---->"+i);
		}
	}
}

static void yield()让位方法

暂停当前正在实行的线程对象,并执行其他线程。

yield()方法不是阻塞方法。让当前线程让位,让给其它线程使用。

yield()方法的执行会让当前线程从“运行状态”回到“就绪状态”。

注意:在回到就绪之后,有可能还会再次抢到。

1.5线程合并

public class ThreadJoin
{
	public static void main(String[] args){
		Thread t = new Thread(new MyRunnable());
		t.setName("t");
		t.start();

		try{
			t.join();//t合并到当前线程中,当前线程受阻塞,t线程执行直到结束。
		}catch(InterruptedException e){
			e.printStackTrace();
		}

		System.out.println("main---over");
	}
}

class MyRunnable implements Runnable
{
	public void run(){
		for(int i = 0 ; i < 1000 ; i++){
				System.out.println(Thread.currentThread().getName()+"--->"+i);
		}
	}
}

运行结果:

在这里插入图片描述

注意:join()方法只是当前线程受阻,他们两个然后让调用join方法线程的方法先执行,等调用join方法的线程执行完毕以后再执行当前线程。他们的栈没有合并,而是栈之间协调了。

猜你喜欢

转载自blog.csdn.net/qq_37823003/article/details/107736721