欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

Java学习笔记(46)——线程

程序员文章站 2022-03-26 15:46:40
这里将自己学习java及其应用的一些笔记、积累分享一下,如果涉及到了文章、文字侵权,请联系我删除或调整。一、线程1.1 概述进程、线程、操作系统的关系。进程 操作系统中,并行执行的任务线程 进程内部,并行执行的任务操作系统、进程、线程之间的关系可简单描述如下:​​​​​​​二、创建线程2.1 概述创建线程,主要包括以下两种方式:继承 Thread 实现 Runnable ​​​​​​​​​​​​​​需要了解的是,在JVM虚拟机启动后,会自动启动一个...

这里将自己学习java及其应用的一些笔记、积累分享一下,如果涉及到了文章、文字侵权,请联系我删除或调整。


一、线程

1.1 概述

进程、线程、操作系统的关系。

  • 进程

操作系统中,并行执行的任务

Java学习笔记(46)——线程

  • 线程

进程内部,并行执行的任务

Java学习笔记(46)——线程

  • 操作系统、进程、线程之间的关系可简单描述如下:

Java学习笔记(46)——线程

二、创建线程

2.1 概述

创建线程,主要包括以下两种方式:

  • 继承 Thread
  • 实现 Runnable

需要了解的是,在JVM虚拟机启动后,会自动启动一个main线程。

​​​​​​​​​​​​​​​​​​​​​2.2 继承 Thread 方式

通过继承 Thread方式创建线程,其大致过程如下:
  • 定义 Thread 的子类
  • 重写 run() 方法
  • run() 方法中的代码,是与其他代码并行执行的代码
  • 线程启动后,自动自动执行 run() 方法

​​​​​​​​​​​​​​Java学习笔记(46)——线程

​​​​​​​​​​​​​​​​​​​​​2.2.1 练习:通过继承Thread创建线程

在下例中,我们通过继承 Thread 的方式,创建了2个线程,并重写了 run() 方法,该方法实现了循环打印。由于线程的执行是基于cpu的时间片机制的,因此,我们在实际执行时,可以看到每个线程的打印内容,并不一定是连续的(如若不明显,可增加循环次数,例如1-1000,因为由于循环次数较短,可能在一个时间片内就完成了)。

Java学习笔记(46)——线程

package 线程;

public class Test_线程创建测试 {
	// JVM虚拟机启动后,会自动启动一个main线程
	public static void main(String[] args) {
		T1 t1 = new T1();
		T1 t2 = new T1();
		
		// 线程启动后,run方法自动执行
		// .start()方法启动线程
		t1.start();
		t2.start();
		System.out.println("main");
		
	}
	
	static class T1 extends Thread{
		@Override
		public void run() {
		// 获得线程名字,setName可以设置任意线程名,默认名字Thread-0...
			String n = getName();
		// 打印1-5
			for(int i=1 ;i<=5;i++) {
				System.out.println(n+":"+i);
			}
		}
	}
}

2.3 实现 Runable 接口方式

通过实现 Runable接口方式创建线程,其大致过程如下:
  • 定义Runnable子类
  • 实现run() 方法
  • Runnable 对象,放入 Thread 线程对象启动
  • 线程启动后,执行 Runnable 对象的 run() 方法

​​​​​​​​​​​​​​Java学习笔记(46)——线程

2.3.1 练习:通过实现Runable接口创建线程

package 线程;

public class Test_Runable接口_线程 {
	public static void main(String[] args) {
		R1 r1 = new R1();
		R1 r2 = new R1();
		
        // 把Runnable 对象,放入 Thread 线程对象启动
		Thread t1 = new Thread(r1);
		Thread t2 = new Thread(r1);
		// 线程启动后,自动执行r1.run方法
		t1.start();// 虽然都是传入的r1,但却各自维护这个一个r1的run  
		t2.start();
		System.out.println("main");
	}
	
	static class R1 implements Runnable{
		@Override
		public void run() {
			// 获得正在执行这行代码的线程对象
			Thread t = Thread.currentThread();// Thread静态方法
			// 因为没有继承Thread的方法,所以不能直接调用getName
			String name = t.getName();
			for(int i = 1;i<=10;i++) {
				System.out.println(name+":"+i);
			}
		}
	}
}

三、线程的状态

3.1 概述

线程的状态可简单分为“新生”、“可执行”、“阻塞”、“执行”、“消亡”。我们可以通过如下一个简图来了解状态之间的关系。

Java学习笔记(46)——线程

四、线程的方法

4.1 方法简介

  • Thread.currentThread()

获得正在执行这行代码的线程对象

  • Thread.sleep(毫秒值)

当前线程暂停指定的毫秒时长

  • Thread.yield()

让步,当前线程放弃时间片,让出cpu资源

  • getName(),setName()

​​​​​​​​​​​​​​​​​​​​​获取线程名称、设置线程名称

  • start()

​​​​​​​​​​​​​​​​​​​​​启动线程

  • iterrupt()

打断一个线程的暂停状态,被打断的线程出现InterruptedException

  • join()

当前线程,等待被调用的线程结束

Java学习笔记(46)——线程

  • getPriority(), setPriority()

获取 / 设置优先级,110,默认5

  • setDaemon(true)

调用Thread对象的setDaemon(true)方法,可将指定线程设置成后台线程、守护线程。

注意!后台线程有个特征:如果所有的前台线程都消亡了,后台线程会自动消亡。当整个虚拟机中只剩下后台线程时,程序就没有继续运行的必要了,虚拟机也就退出了。JVM的垃圾回收线程就是典型的后台线程。main线程默认是前台线程,但并不是所有的线程默认都是前台线程,有些线程默认就是后台线程——前台线程创建的子线程默认是前台线程,后台线程创建的子线程默认是后台线程。

4.2 练习:Threadsleep_Interrupted测试

package 线程;

import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Scanner;

public class Test_Threadsleep_Interrupted测试 {
	public static void main(String[] args) {
		T1 t1= new T1();
		t1.start();
		
		// 在main线程中,打断t1线程的暂停状态
		System.out.println("回车打断t1线程");
		// 因为t1与main是两个并行的线程,所以,互不影响
		// 因此,“等待输入回车”与时间每秒打印互不影响
		new Scanner(System.in).nextLine();
		t1.interrupt();
		
	}
	
	static class T1 extends Thread{
		@Override
		public void run() {
			SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss");
			while(true) {
				String s = sdf.format(new Date());
				System.out.println("当前时间:"+s);
				// 停1秒,因为CPU效率极高,在一秒内可以打印多次
				try {
					Thread.sleep(1000);
				} catch (InterruptedException e) {
					// 忽略了该异常,暂不处理
					System.out.println("t1进程被打断!");
					break;
				}
			}
		}
	}
}

4.3 练习:Join测试​​​​​​​

package 线程;
/*
 *	PC端,一个CPU的情况下,多线程依旧时间效率更高,原因是
 *	操作系统会给线程更多的进程任务分配更多的系统资源 
 */
public class Test_Join测试 {
	public static void main(String[] args) throws InterruptedException {
		// 1000万内的质数数量,多线程进行分段计算
//		System.out.println("单线程");
//		f1();

		System.out.println("5个线程");
		f2();
	}
	// 单线程计算1000万以内的质数
	private static void f1() throws InterruptedException {
		long t = System.currentTimeMillis();
		
		T1 t1 = new T1(0, 10000000);
		t1.start();
		/*	.start()方法启用线程,不可替代
		 *	
		 *	此处,如果用.start()启动线程,则质数打印结果不准确,
		 *	为1或几个。原因是,线程启动后,由于CPU分片机制,导致
		 *	main线程的指令也可能分到时间片而执行后续的代码,此时,
		 *	count被赋值且执行了打印语句 
		 */
		t1.join();// .join()方法,当前线程等待,调用对象线程优先执行至结束
		int count = t1.count;// 质数个数
		
		t = System.currentTimeMillis()-t;
		System.out.println("单线程用时"+t);
		System.out.println("质数个数为:"+count);
	}

	// 多线程计算1000万以内的质数
	private static void f2() throws InterruptedException {
		long t = System.currentTimeMillis();
		T1[] t2 = new T1[5];
		
		for(int i = 0;i<t2.length;i++) {
			t2[i] = new T1(2000000*i,2000000*(i+1));
			t2[i].start();
		}
		
		// main线程等待,5个线程先行计算
		// join与start分开,是为了保证5个线程并行
		int count =0;// 统计每个线程的质数数量
		for(int i = 0;i<t2.length;i++) {
			t2[i].join();
			count += t2[i].count;
		}
		
		t = System.currentTimeMillis()-t;
		System.out.println("5线程用时"+t);
		System.out.println("质数个数为:"+count);
	}

	static class T1 extends Thread{
		int start,end;
		int count;// 统计质数的个数
		// 构造方法
		public T1(int start, int end) {
			this.start = start;
			this.end = end;
			
			if(start<=2 && end >=3) {
				count = 1;// 0-3之间的一个已知质数
				this.start = 3;
			}
		}
		
		@Override
		public void run() {
			// 循环i从[start,end)
			for(int i =start;i<end;i++) {
				if(isPrime(i)) {
					count++;
				}
			}
		}
		
		// 判断一个数字是不是质数
		private boolean isPrime(int i) {
			double max = 1+Math.sqrt(i);
			for(int j = 2;j<max;j++) {
				if(i%j==0) {
					return false;
				}
			}
			return true;
		}	
		
	}
}

4.4 练习:setDaemon后台线程

package 线程;

import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Scanner;

public class Test_setDaemon后台线程 {
	public static void main(String[] args) {
		T1 t1 = new T1();
		t1.start();
		
		// 创建匿名内部类对象
		Thread t2 = new Thread() {
			@Override
			public void run() {
				// 在main线程中,打断t1线程的暂停状态
				System.out.println("回车打断t1线程");
				// 因为t1与main是两个并行的线程,所以,互不影响
				// 因此,“等待输入回车”与时间每秒打印互不影响
				new Scanner(System.in).nextLine();
				t1.interrupt();
			}
		};
		// 设置t2为后台进程,JVM虚拟机不会等待t2进程结束
		t2.setDaemon(true);
		t2.start();

	}

	static class T1 extends Thread {
		@Override
		public void run() {
			SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss");
			for(int i = 0;i<10;i++) {
				String s = sdf.format(new Date());
				System.out.println("当前时间:" + s);
				// 停1秒,因为CPU效率极高,在一秒内可以打印多次
				try {
					Thread.sleep(1000);
				} catch (InterruptedException e) {
					// 忽略了该异常,暂不处理
					System.out.println("t1进程被打断!");
					break;
				}
			}
		}
	}

}

 

本文地址:https://blog.csdn.net/AlieNeny/article/details/113850641