Java学习笔记-Day36 Java 多线程(一)



一、多线程的简介

1、程序、进程和线程


程序:是一组静态的计算机指令的集合,不占用系统运行资源,不能被系统调度,也不能作为独立运行的单位,它以文件的形式存储在磁盘上。

进程:是一个程序在其自身的地址空间中的一次执行活动。进程是资源申请、调度和独立运行的单位,因此,它使用系统中的运行资源。而程序不能申请系统资源,一个程序可以对应多个进程。

线程:是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。

进程 是系统运行程序的 基本单位,拥有独立的内存空间和系统资源。线程 是进程中执行运算的 最小单位,系统将处理器分配给线程,所以,真正在处理器上运行的是线程。

2、多线程的优点


(1)可以更好的实现并行。

(2)恰当地使用线程时,可以降低开发和维护的开销,并且能够提高复杂应用的性能。

(3)CPU在线程之间开关时的开销远比进程要少得多。因开关线程都在同一地址空间内,只需要修改线程控制表或队列,不涉及地址空间和其他工作。

(4)创建和撤销线程的开销较之进程要少。

3、Java在多线程应用中的优势


Java在语言级提供了对多线程程序设计的支持。多线程操作会增加程序的执行效率。各线程之间切换执行,时间比较短,看似是多线程同时运行,但对于CPU来说,某一个时刻只有一个线程在运行。

二、Java线程的生命周期


(1)新建状态:当利用new关键字创建线程对象实例后,它仅仅作为一个对象实例存在,JVM没有为其分配CPU时间片和其他线程运行资源。

(2)就绪状态:在处于创建状态的线程中调用start方法将线程的状态转换为就绪状态。这时,线程已经得到除CPU时间之外的其它系统资源,只等JVM的线程调度器按照线程的优先级对该线程进行调度,从而使该线程拥有能够获得CPU时间片的机会。

(3)运行状态:就绪状态的线程获得CPU就进入运行状态。

(4)等待/阻塞状态:线程运行过程中被剥夺资源或者等待某些事件就进入等待/阻塞状态,suspend()方法被调用,sleep()方法被调用,线程使用wait()来等待条件变量,线程处于I/O等待等等,调用suspend方法将线程的状态转换为挂起状态。这时,线程将释放占用的所有资源,但是并不释放锁,所以容易引发死锁,直至应用程序调用resume方法恢复线程运行。等待事件结束或者得到足够的资源就进入就绪状态。

(5)死亡状态:当线程体运行结束或者调用线程对象的stop方法后线程将终止运行,由JVM收回线程占用的资源。

三、三种创建线程的方式

1、Thread类


对于Java而言,每一个独立的线程都是java.lang.Thread类的一个对象,而线程中独立于其他线程所执行的指令代码由Thread类的run方法提供。因此,要想在Java中实现多线程协作运行,就需要创建多个独立的Thread类对象。

1.1、构造方法


(1) Thread():创建一个新的线程。

(2) Thread(String name):创建一个指定名称的线程。

(3) Thread(Runnable target) :利用Runnable对象创建一个线程,启动时将执行该对象的run方法。

(4) Thread(Runnable target, String name):利用Runnable对象创建一个线程,并指定该线程的名称。

1.2、Thread的部分方法

(1)public void start():启动该线程,使该线程开始执行,Java虚拟机调用该线程的run()方法,多次启动一个线程是不允许的,特别是当该线程已经结束后,就不能再重新启动。结果是两个线程同时运行:当前线程(从start方法的调用返回)和另一个线程(执行其run方法)。

注意:start方法会调用本地方法start0方法(JVM调用),JVM再调用run方法。

(2)public void run():如果该线程是使用独立的Runnable运行构造的,则调用该Runnable对象的run方法。如果这个类是一个线程类,只要启动线程,就会执行run()方法。

(3)public static void sleep(long millis) throws InterruptedException:使该线程睡眠(暂停执行)millis毫秒,此时,该线程不会丢失任何监听器所属权。

注意:同一个线程只能调用start()方法一次,多次调用会抛出java.lang.IllegalThreadStateException。而run()方法可以进行多次调用,因为它只是一种正常的方法调用。启动一个线程,需要调用start()方法而不是run()方法。此时,当前线程会被添加到线程组中,进入就绪状态,等待线程调度器的调用,若获取到了资源,则能进入运行状态,run()方法只是线程体,即线程执行的内容,若没调用start()方法,run()方法只是一个普通的方法。

1.3、通过Thread类创建线程类


(1)创建一个继承Thread类的自定义类。

(2)重写自定义类中的run方法。

(3)创建自定义类的对象。

(4)调用 start 方法来启动线程。

	/**
	 * 线程实现方式1:继承Thread类
	 */
	public class TestThread01 {
    
    
		public static void main(String[] args) {
    
    
			T1 t1 = new T1();//新线程
			t1.start();//进入就绪状态
			T2 t2 = new T2();//新线程
			t2.start();//进入就绪状态
		}
	}
	
	//T1继承Thread类
	class T1 extends Thread {
    
    
		/**
		 * 重写run方法
		 */
		@Override
		public void run() {
    
    
			System.out.println("t1 -> hello");
		}
	}
	
	//T2继承Thread类
	class T2 extends Thread {
    
    
		/**
		 * 重写run方法
		 */
		@Override
		public void run() {
    
    
			System.out.println("t2 -> world");
		}
	}

注意:

① 需要注意run()方法的签名。

② run()方法中的代码和其他方法中的代码区别在于:它可以和其他线程的run()方法代码 并行执行

③ 线程不具备任何循环特性,一旦run()方法代码执行结束,线程的生命周期即会自动结束,因此如果需要在线程中循环执行某些代码需要自行声明循环控制。

④ 可以通过标记位结束线程。

1.4、继承Thread类实现线程的方法的局限性


由于Java是典型的单亲继承体系,因此一旦类继承Thread之后就不能再继承其他父类,对于一些必须通过继承关系来传播的特性显然会造成困扰。在上述情况下,可以通过实现 java.lang.Runnable 接口的方式来实现线程。

2、Runnable接口


在Runnable接口中只有一个签名和Thread中一致的run()方法,满足函数式接口的要求,可以使用Lambda表达式。

Runnable接口的子类并不是线程类,只是通过这种形式向线程类提供run()方法指令代码,最后还需借助Thread类和Runnable接口的依赖关系和Thread类的Thread(Runnable runnable)构造方法来创建线程对象。

切记Runnable不是线程类,而是一个实现线程为其提供run()方法指令代码的工具,因此创建其对象之后需要将其作为Thread类的构造方法参数以创建实际的线程对象。

1.1、通过Runnable接口实现线程


(1)创建一个实现Runnable接口的自定义类。

(2)在自定义类中实现Runnable接口的run方法。

(3)创建自定义类的对象。

(4)将自定义类的对象作为Thread类的构造方法的参数来创建线程对象。

(5)线程对象调用 start 方法来启动线程。

	public class TestRunnable {
    
    
		public static void main(String[] args) {
    
    
			//创建TR1对象
			TR1 tr = new TR1();
			//将TR1对象做为Thread构造方法的参数
			Thread t = new Thread(tr);
			t.start();
			
			TR2 tr2 = new TR2();
			Thread t2 = new Thread(tr2);
			t2.start();
		}
	
	}
	//实现Runnable接口
	class TR1 implements Runnable {
    
    
		@Override
		public void run() {
    
    
			while(true) {
    
    
				System.out.println("TestRunnable1");
			}
		}
	}
	//实现Runnable接口
	class TR2 implements Runnable{
    
    
		@Override
		public void run() {
    
    
			while(true) {
    
    
				System.out.println("TestRunnable2");
			}
		}
	}

1.2、实现方式

1.2.1、独立类

	public class TestRunnable {
    
    
		public static void main(String[] args) {
    
    
			//创建TR1对象
			TR1 tr = new TR1();
			//将TR1对象做为Thread构造方法的参数
			Thread t = new Thread(tr);
			t.start();
		}
	}
	//实现Runnable接口
	class TR1 implements Runnable {
    
    
		@Override
		public void run() {
    
    
			while(true) {
    
    
				System.out.println("TestRunnable1");
			}
		}
	}

1.2.2、匿名内部类

	public class TestRunnable {
    
    
		public static void main(String[] args) {
    
    
			// 匿名内部类
			Runnable r = new Runnable() {
    
    
				@Override
				public void run() {
    
    
					System.out.println("TestRunnable1");
				}
			};
			new Thread(r).start();
		}
	}

1.2.3、匿名内部类 (lambda表达式)


可以使用lambda表达式(在JDK1.8中)的条件:

① 在接口中只有一个抽象方法。

② 在接口前有 @FunctionalInterface 注解。

在这里插入图片描述

public class TestRunnable {
    
    
	public static void main(String[] args) {
    
    
		new Thread(r).start();
		// 匿名内部类(lambda表达式)
		Runnable r2 = () -> {
    
    
			System.out.println("TestRunnable2");
		};
		new Thread(r2).start();
		// 匿名内部类(lambda表达式),只有一行代码时可以去掉{}
		Runnable r3 = () -> System.out.println("TestRunnable3");
		new Thread(r3).start();
	}
}

3、Callable接口


Callable接口实际上是属于Executor框架中的功能类,Callable接口与Runnable接口的功能类似,但提供了比Runnable更加强大的功能。Callable可以在任务结束的时候提供一个返回值,Runnable无法提供这个功能。Callable的call方法分可以抛出异常,而Runnable的run方法不能抛出异常。

在这里插入图片描述

1.1、通过Callable接口实现线程


(1)创建一个实现Callable接口的自定义类。

(2)在自定义类中实现Callable接口的call方法。

(3)创建自定义类的对象。

(4)将自定义类的对象作为FutureTask类的构造方法的参数来创建FutureTask类的对象。

(5)将FutureTask类的对象作为作为Thread类的构造方法的参数来创建线程对象(FutureTask< T > 实现了RunnableFuture< T > 接口,RunnableFuture< T > 接口实现了Runnable接口)。

(6)线程对象调用 start 方法来启动线程。

	import java.util.concurrent.Callable;
	import java.util.concurrent.ExecutionException;
	import java.util.concurrent.FutureTask;
	
	public class TestCallable {
    
    
		public static void main(String[] args) throws InterruptedException, ExecutionException {
    
    
			Callable<String> mycall = new Mycall();
			FutureTask<String> ft = new FutureTask<String>(mycall);
			
			Thread t = new Thread(ft);
			t.start();
	
			String s = ft.get();
			System.out.println(s);
		}
	}
	
	class Mycall implements Callable<String>{
    
    
		@Override
		public String call() throws Exception {
    
    
			double random = Math.random();
			System.out.println("call...");
			return "call:random is "+random;
		}
	}

猜你喜欢

转载自blog.csdn.net/qq_42141141/article/details/110008468
今日推荐