Java-多线程学习分享-1

进程与线程

进程:
进程是资源(CPU、内存等)分配的基本单位,它是程序执行时的一个实例。程序运行时系统就会创建一个进程,并为它分配资源,然后把该进程放入进程就绪队列,进程调度器选中它的时候就会为它分配CPU资源,程序开始真正运行。
线程
线程是一条执行路径,是程序执行时的最小单位,它是进程的一个执行流,是CPU调度的基本单位,一个进程可以由很多个线程组成,线程间共享进程的所有资源,每个线程有自己的堆栈来存储临时变量和局部变量。

线程的生命周期

在这里插入图片描述

  • 新建状态
    使用new关键字和Thread类或其子类建立一个线程对象后,该线程对象就处于新建状态。它保持这个状态直到程序start()这个线程。
  • 就绪状态
    当线程对象调用了start()方法之后,该线程就进入了就绪状态。就绪状态的线程处于就绪队列中,要等待JVM里线程调度器的调度。
  • 运行状态
    如果就绪状态的线程获取CPU资源,就可以执行run(),此时线程便处于运行状态。处于运行状态的线程最为复杂,它可以变成阻塞状态、就绪状态和死亡状态。
  • 阻塞状态
    如果一个线程执行了sleep(睡眠)、suspend(挂起)等方法,失去所占用资源之后,该线程就从运行状态进入了阻塞状态。在睡眠时间已到或获得设备资源后就可以重新进入就绪状态。
    • 等待阻塞:运行状态中的线程执行了wait()方法,使线程进入等待阻塞状态。
    • 同步阻塞:线程获取synchronized同步锁失败(因为同步锁被其他线程占用)。
    • 其他阻塞:通过调用线程的sleep()、join()、发出I/O请求时,线程就会进入到阻塞状态。当sleep()状态超时,join()等待线程终止或超时,或者I/O处理完毕,线程重新转入就绪状态。
  • 死亡状态
    一个运行状态的线程完成任务或者其他终止条件发生时,该线程就切换到终止状态。

创建线程的方式

参考链接

一、通过继承Thread类

Thread类中两个常用的构造方法是:

public Thread()//创建一个新的线程对象
public Thread(String threadName)//创建一个名称为threadName的线程对象

当一个类继承Thread类后,需要在该类中覆盖run()方法,将完成线程真正功能的代码写入run()方法中,然后调用Thread类中的start()方法启动线程。
例如:

class MyThread extends Thread {
	@Override
	public void run() {
		for (int i = 0;i < 20;i++) {
			System.out.println("一边听歌");
		}
	}
}

测试代码:

public class Test {
	public static void main(String[] args) {
		MyThread myThread = new MyThread();//创建实例
		myThread.start();//启动线程
		
		for (int i = 0;i < 20;i++) {
			System.out.println("一边敲代码");
		}
	}
}

运行结果:
在这里插入图片描述在main()方法中,启动线程需要调用Thread类中的start()方法,start()方法调用被覆盖的run()方法。如果不调用start()方法,线程永远都不会启动,在没有调用start()方法之前,线程对象处于新建状态。
调用start()方法后并不是立即执行多线程代码,而是使得该线程变为可运行状态,线程什么时候调度运行由操作系统决定的。
注意:如果不调用start()方法启动线程,而是直接调用run()方法,那么就不是多线程而只是一个简单的方法调用。

二、通过实现Runnable接口

通过一个类继承Thread类来实现一个线程具有单继承的局限性,该子类不能再继承其他类,为了解决单继承的局限性,推荐通过实现Runnable()接口来实现一个线程。
启动一个通过实现Runnable接口实现的线程也需要通过Thread类对象的start()方法,Thread类中还有另外两个构造方法:

public Thread(Runnable target);//通过一个Runnable类型的对象实例化一个Thread对象
public Thread(Runnable target,String name); //通过一个Runnable类型的对象实例化一个名称为name的Thread对象

使用Runnable()接口启动新线程的步骤如下:

  1. 自定义类实现Runnable接口,并实现该接口的run()方法
  2. 用实现Runnable接口的对象作为参数(target)实例化一个Thread对象
  3. 调用Thread对象的start()方法

例如:

class MyRunnable implements Runnable {
	@Override
	public void run() {
		for (int i = 0;i < 10;i++) {
			System.out.println("一边听歌");
		}
	}
}

测试代码:

public class Test {
	public static void main(String[] args) {
        MyRunnable myRunnable = new MyRunnable();
        MyThread myThread = new MyThread(myRunnable);
		myThread.start();
		for (int i = 0;i < 10;i++) {
			System.out.println("一边敲代码");
		}
	}
}

运行结果:

其实不管是通过继承Thread类还是通过实现Runnable接口来实现线程,最终都是通过Thread对象的API来控制线程(代理设计模式)。
在这里插入图片描述

Thread类本质上也是实现了Runnable接口,启动线程的唯一方法就是通过Thread类的start()方法。start()方法是一个native(本地)方法,它将与操作系统交互,执行run()方法(Thread类中提供的run()方法是一个空方法)。

注意:一个Native Method就是一个Java调用非Java代码的接口。我们把这类接口称为JNI(Java Native Interface),它提供了若干的API实现了Java和其他语言的通信(主要是C&C++),它允许Java代码和其他语言写的代码进行交互。Thread类的start()方法是一个native method,它是与操作系统进行交互。
在这里插入图片描述

三、通过Callable和Future创建线程

通过实现Callable接口,重写call()方法来实现一个线程。Callable接口是juc(java.util.concurrent)包下的一个泛型接口,只有一个call()方法。
Callable接口与Runnable接口的功能类似,但提供了比Runnable更强大的功能:

  1. Callable可以在线程结束后提供一个返回值,Runnable不能提供这个功能
  2. Callable接口中的call()方法可以抛出异常,而Runnable接口中的run()方法不能抛出异常,必须在run()方法中捕获。
  3. java5提供了Future接口来代表Callable接口里的call()方法的返回值,并为Future接口提供了一个FutureTask实现类,该实现类实现了Future接口,并实现了Runnable接口,所以可以作为Thread的target。

使用Callable接口实现线程的步骤如下:

  1. 自定义类实现Callable接口,并实现该接口的call()方法
  2. 用实现Callable接口的对象作为参数实例化一个FutureTask对象
  3. 用FutureTask对象作为参数(target)实例化一个Thread对象
  4. 调用Thread对象的start()方法
  5. 调用FutureTask对象的get()方法获取结果

例如:

class MyCallable implements Callable {
	private String str;
	public MyCallable(String str) {
		this.str = str;
	}
	
	@Override
	public Boolean call() throws Exception {
		for (int i = 0;i < 10;i++) {
			System.out.println("一边"+str);
		}
		return true;
	}
}

测试代码:

public class Test {
	public static void main(String[] args) throws InterruptedException, ExecutionException {
		MyCallable mc1 = new MyCallable("唱歌");
		MyCallable mc2 = new MyCallable("敲代码");
		
        FutureTask futureTask1 = new FutureTask(mc1);
        FutureTask futureTask2 = new FutureTask(mc2);
		
        new Thread(futureTask1).start();
        new Thread(futureTask2).start();
		//获取结果
		boolean r1 = futureTask1.get();
		boolean r2 = futureTask2.get();
		
		System.out.println("线程1执行结果:" + r1);//打印结果
		System.out.println("线程2执行结果:" + r2);//打印结果
	}
}

四、线程池

请看这篇文章

五、简化线程的实现

  1. 内部类实现简化
    对于只使用一次并且逻辑较简单的线程,为了提升性能我们可以使用内部类的方式对它的表示进行简化。
  2. Lambda表达式实现简化
    lambda表达式就是在匿名内部类的基础上把接口名和方法名都删掉,只保留方法体。

注意:内部类只在外部类使用时才会加载,外部类不使用时不会加载,所以可以提升性能。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值