【搞定Java并发编程】第3篇:多线程概述~上篇

上一篇:并发基础概述:https://blog.csdn.net/pcwl1206/article/details/84833911

目  录:

1、什么是线程

2、线程的创建

2.1、Thread和Runnable简介

2.2、Thread和Runnable的异同点

2.3、Thread和Runnable的多线程示例

3、Run方法和Start方法的区别

4、线程的状态


1、什么是线程

线程(Thread)是一个对象(Object)。现代操作系统调度的最小单元就是线程。Java 线程是 Java 进程内允许多个同时进行的任务。该进程内并发的任务称为为线程(Thread),一个进程里至少一个线程。

在一个进程里可以创建多个线程,这些线程都拥有各自的程序计数器、堆栈和局部变量等属性,并且能够访问共享的内存变量。

1.1  多线程

Java 程序采用多线程方式来支持大量的并发请求处理,程序如果在多线程方式执行下,其复杂度远高于单线程串行执行。那么多线程指的就是这个程序(一个进程)运行时产生了不止一个线程。

  • 为什么要使用多线程?

1、适合多核处理器:一个线程运行在一个处理器核心上,那么多线程可以分配到多个处理器核心上,更好地利用多核处理器;

2、更快的响应时间:将数据一致性不强的操作使用多线程技术(或者消息队列)加快代码逻辑处理,缩短响应时间;

3、更好的编程模型

  • 并发与并行的区别:

1、并发:类似单个 CPU ,通过 CPU 调度算法等,处理多个任务的能力;

2、并行:类似多个 CPU ,同时并且处理相同多个任务的能力;


2、线程的创建

Java创建线程对象有两种方法:

1、继承Thread类创建线程对象;

2、实现Runnable接口创建线程对象。

2.1、Thread和Runnable简介

  • Runnable:

Runnable是一个接口,该接口中只包含了一个run()方法,它的定义如下:

public interface Runnable{
    public abstract void run();
}

Runnable的作用是实现多线程。我们可以定义一个类A实现Runnable接口;然后,通过new Thread(new A())等方式新建线程。

  • Thread

Thread是一个类,它本身就实现了Runnable接口。它的声明如下:

public class Thread implements Runnable{
    // ...
}

Thread的作用是:实现多线程。

2.2、Thread和Runnable的异同点

如果一个类继承Thread,则不适合资源共享。但是如果实现了Runnable接口的话,很容易实现资源共享。

实现Runnable接口比继承Thread类具有以下优势:

1、可以避免Java中的单继承问题,Runnable的可扩展性更好;

2、Runnable还可以用于“资源共享”。即,多个线程都是基于某一个Runnable对象建立的,它们会共享Runnable对象上的资源。

2.3、Thread和Runnable的多线程示例

  • Thread的多线程示例
public class MyThread extends Thread {

	private int ticket = 10;
	
	public void run(){
		for(int i = 0; i < 20; i++){
			if(this.ticket > 0){
				System.out.println(this.getName() + " 卖票:ticket" + this.ticket--);
			}
		}
	}
	
	public static void main(String[] args) {
		
		// 启动三个线程,每个线程各卖10张票
		MyThread t1 = new MyThread();
		MyThread t2 = new MyThread();
		MyThread t3 = new MyThread();
		
		t1.start();
		t2.start();
		t3.start();
	}
}

运行结果: 

结果说明

1、MyThread继承于Thread,它是自定义线程。每个MyThread都会卖出10张票。

2、 主线程main创建并启动3个MyThread子线程。每个子线程都各自卖出了10张票。

  • Runnable的多线程示例
public class MyThread implements Runnable {

	private int ticket = 10;
	
	public void run(){
		for(int i = 0; i < 20; i++){
			if(this.ticket > 0){
				System.out.println(Thread.currentThread().getName() + " 卖票:ticket" + this.ticket--);
			}
		}
	}
	
	public static void main(String[] args) {
		
		MyThread mt = new MyThread();
		
		// 启动三个线程,每个线程各卖10张票
		Thread t1 = new Thread(mt);
		Thread t2 = new Thread(mt);
		Thread t3 = new Thread(mt);
		
		t1.start();
		t2.start();
		t3.start();
	}
}

运行结果:

结果说明

1、 和上面“MyThread继承于Thread”不同,这里的MyThread实现了Runnable接口。

2、 主线程main创建并启动3个子线程,而且这3个子线程都是基于“mt这个Runnable对象”而创建的。运行结果是这3个子线程一共卖出了10张票。这说明它们是共享了MyThread接口的。

  • 需要注意的几个点:

1、main方法也是一个线程。在Java中所有的线程都是同时启动的,至于哪个先执行、什么时候执行,完全看谁先得到CPU资源了;

2、在Java中,每次程序至少会启动2个线程。一个是main线程,另一个是垃圾收集线程。因为每当使用Java命令执行一个类的时候,实际上都会启动一个Jvm,每一个Jvm实际上就是在操作系统中启动了一个进程。


3、Run方法和Start方法的区别

1、start():它的作用是启动一个新线程,新线程启动后会执行相应的run()方法;start()方法不能被重复调用;

2、run():和普通的成员方法一样,可以被重复调用。单独调用run()的话,会在当前线程中执行run()方法,而不会启动新线程。

public class MyThread extends Thread{

	public MyThread(String name){
		super(name);
	}
	
	public void run(){
		System.out.println(Thread.currentThread().getName() + " is running");
	}
	
	public static void main(String[] args) {
		
		Thread myThread = new MyThread("myThread");
		
		System.out.println(Thread.currentThread().getName() + " call myThread.run()");
		myThread.run();
		
		System.out.println(Thread.currentThread().getName() + " call myThread.start()");
		myThread.start();
	}
}

运行结果:

结果说明

1、Thread.currentThread().getName()是用于获取“当前线程”的名字。当前线程是指正在cpu中调度执行的线程。

2、 myThread.run()是在“主线程main”中调用的,该run()方法直接运行在“主线程main”上。

3、myThread.start()会启动“线程mythread”,“线程mythread”启动之后,会调用run()方法;此时的run()方法是运行在“线程myThread”上。

  • Thread.java中start()方法的源码:
public synchronized void start() {
    // 如果线程不是"就绪状态",则抛出异常!
    if (threadStatus != 0)
        throw new IllegalThreadStateException();

    // 将线程添加到ThreadGroup中
    group.add(this);

    boolean started = false;
    try {
        // 通过start0()启动线程
        start0();
        // 设置started标记
        started = true;
    } finally {
        try {
            if (!started) {
                group.threadStartFailed(this);
            }
        } catch (Throwable ignore) {
        }
    }
}

说明:start()实际上是通过本地方法start0()启动线程的。而start0()会新运行一个线程,新线程会调用run()方法。

private native void start0();
  • Thread.java中run()方法的源码码如下:
public void run() {
    if (target != null) {
        target.run();
    }
}

说明:target是一个Runnable对象。run()就是直接调用Thread线程的Runnable成员的run()方法,并不会新建一个线程。

  • 线程的运行

在运行上面两种创建线程的代码后,JVM 执行了 main 函数线程,然后在主线程中执行创建了新的线程。正常情况下,所有线程执行到运行结束为止。除非某个线程中调用了 System.exit(1) 则被终止。

在实际开发中,一个请求到响应式是一个线程。但在这个线程中可以使用线程池创建新的线程,去执行任务。


4、线程的状态

首先推荐一篇文章:Java线程到底有多少种状态

本人也查看了Thread的源码,源码中定义了6种状态:NEW、RUNNABLE、BLOCKED、WAITING、TIMED_WAITING、TERMINATED

Thread源码中对线程状态的定义

新建 MyThread类,打印线程对象属性,代码如下:

public class MyThread extends Thread{

	@Override 
    public void run() {
        System.out.println("MyThread的线程实例正在执行任务");
    }
 
    public static void main(String[] args) {
        MyThread thread = new MyThread();
        thread.start();
 
        System.out.print("MyThread的线程对象 \n"
                + "线程唯一标识符:" + thread.getId() + "\n"
                + "线程名称:" + thread.getName() + "\n"
                + "线程状态:" + thread.getState() + "\n"
                + "线程优先级:" + thread.getPriority());
    }
}

运行结果:

线程是一个对象,它有唯一标识符 ID、名称、状态、优先级等属性。线程只能修改其优先级和名称等属性 ,无法修改 ID 、状态。ID 是 JVM 分配的,名字默认也为 Thread-XX,XX是一组数字。线程初始状态为 NEW。

线程优先级(priority)的范围是 1 到 10 ,其中 1 是最低优先级,10 是最高优先级,默认的优先级是5。不推荐改变线程的优先级,如果业务需要,自然可以修改线程优先级到最高,或者最低。可以通过:setPriority(int)方法来修改优先级。但是需要说明的是操作系统可能不会理会你设置的优先级,因此,程序的正确性不能依赖线程的优先级高低

线程的状态实现通过 Thread.State 常量类实现,有 6 种线程状态:new(新建)、runnnable(可运行)、blocked(阻塞)、waiting(等待)、time waiting (定时等待) terminated(终止)

Java线程的状态
状态名称 说明
NEW 初始状态,线程被构建,但是还没有调用start()方法
RUNNABLE 运行状态,Java线程将操作系统中的就绪和运行两种状态笼统地称为“运行中”
BLOCKED 阻塞状态,表示线程阻塞与锁
WAITING 等待状态,表示线程进入等待状态,进入该状态表示当前线程需要等待其他线程做出一些特定动作(通知或中断)
TIME_WAITING 超时等待状态,该状态不同于WAITING,它是可以在指定的时间自行返回的
TERMINATED 终止状态,表示当前线程已经执行完毕

状态转换图如下

线程状态流程大致如下:

1、新建了一个线程对象,进入new状态。例如,Thread thread = new Thread();

2、runnable叫“就绪状态”。线程新建后,其他线程(比如main线程)调用了该对象的start()方法,从而来启动该线程。Runnable状态的线程位于可运行线程池中,等待被线程调度选中,获取CPU的使用权;

3、runnable叫”可运行状态“的线程获得了CPU时间片(timeslice),执行代码。需要注意的是,线程只能从就绪状态进入到运行状态;

4、如果线程执行sleep、wait、join方法或者IO阻塞将会进入wait状态或者blocked状态;

5、线程执行完毕后,线程被线程队列移除。最后为terminated状态。


上一篇:并发基础概述:https://blog.csdn.net/pcwl1206/article/details/84833911

参考及推荐:

1、并发基础与Java多线程:https://blog.csdn.net/a724888/article/details/60867044

2、Java多线程系列目录:https://www.cnblogs.com/skywang12345/p/java_threads_category.html

猜你喜欢

转载自blog.csdn.net/pcwl1206/article/details/84837530