Java基础-多线程-①线程的创建和启动
简单阐释进程和线程
对于进程最直观的感受应该就是“windows任务管理器”中的进程管理:
(计算机原理课上的记忆已经快要模糊了,简单理解一下):一个进程就是一个“执行中的程序”,是程序在计算机上的一次运行活动。程序要运行,系统就在内存中为该程序分配一块独立的内存空间,载入程序代码和资源进行执行。程序运行期间该内存空间不能被其他进程直接访问。系统以进程为基本单位进行系统资源的调度和分配。何为线程?线程是进程内一次具体的执行任务。程序的执行具体是通过线程来完成的,所以一个进程中至少有一个线程。回忆一下 HelloWrold 程序中main方法的执行,其实这时候,Java虚拟机会开启一个名为“main”的线程来执行程序代码。一个进程可以包含多个线程,这些线程共享数据空间和资源,但又分别拥有各自的执行堆栈和程序计数器。线程是CPU调度的基本单位。
多线程
一个进程包含了多个线程,自然就叫做多线程。拥有多个线程就可以让程序看起来可以“同时”处理多个任务,为什么是看起来呢?因为CPU也分身乏术,只能让你这个线程执行一会儿,好了你歇着,再让另一个线程执行一会儿,下次轮到你的时候你再继续执行。这里的“一会儿”实际上时间非常短,感觉上就是多个任务“同时”在执行。CPU就这样不停的切来切去…既然CPU一次也只能执行一个线程,为什么要使用多线程呢?当然是为了充分利用CPU资源。一个线程执行过程中不可能每时每刻都在占用CPU,CPU歇着的时候我们就可以让它切过来执行其他的线程。
比如QQ聊天的时候,跟一个人正聊着呢,另一个消息过来了。如果是单线程,不好意思,等我跟这一个聊完说拜拜之后再去理你吧。多线程呢,消息窗口全打开,这个窗口说完话了,总得等人家回吧,趁这个空闲时候,处理另一个窗口的消息。这样看起来不就是同时进行了么,每一个窗口的另一边都以为你只在跟他一个人聊天…
Java中的多线程
Java中启用多线程有两种方式:①继承Thread类;②实现Runnable接口。
There are two ways to create a new thread of execution. One is to declare a class to be a subclass of
Thread
. This subclass should override therun
method of classThread
. An instance of the subclass can then be allocated and started.The other way to create a thread is to declare a class that implements theRunnable
interface. That class then implements therun
method. An instance of the class can then be allocated, passed as an argument when creatingThread
, and started.
继承Thread类
创建一个类,继承java.lang.Thread,并覆写Thread类的run()方法,该类的实例就可以作为一个线程对象被开启。
/**
* Dog类,继承了Thread类
* @author lt
*/
class Dog extends Thread {
/*
* 覆写run()方法,定义该线程需要执行的代码
*/
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(i);
}
}
}
线程创建好了,怎么让它作为程序的一个独立的线程被执行呢?创建一个该类的实例,并调用start()方法,将开启一个线程,并执行线程类中覆写的run()方法。
public class ThreadDemo {
public static void main(String[] args) {
Dog dog = new Dog();
dog.start();
}
}
看不出什么端倪,如果我们直接调用实例的run()方法,执行效果是完全一样的,见上图。
public class ThreadDemo {
public static void main(String[] args) {
Dog dog = new Dog();
dog.run();
}
}
如果一切正常,这时候程序中应该有两个线程:一个主线程main,一个新开启的线程。run()方法中的代码究竟是哪个线程执行的呢?Java程序中,一个线程开启会被分配一个线程名:Thread-x,x从0开始。我们可以打印当前线程的线程名,来看看究竟是谁在执行代码。
class Dog extends Thread {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println("当前线程:" + Thread.currentThread().getName() + "---" + i);
}
}
} public class ThreadDemo {
public static void main(String[] args) {
System.out.println("当前线程:" + Thread.currentThread().getName());
Dog dog = new Dog();
dog.start();
}
}
可以看到,确实开启了一个新的线程:Thread-0,main()方法的线程名就叫main。
同一个实例只能调用一次start()方法开启一次,多次开启,将报java.lang.IllegalThreadStateException异常:
我们再创建一个实例,开启第三个线程:
public class ThreadDemo {
public static void main(String[] args) {
System.out.println("当前线程:" + Thread.currentThread().getName());
Dog dog = new Dog();
Dog dog2 = new Dog();
dog.start();
dog2.start();
}
}
这时候我们已经能够看到多线程的底层实现原理:CPU切换处理、交替执行的效果了。
run和start
上面我们直接调用run()方法和调用start()方法的结果一样,现在我们在打印线程名的情况下再来看看:
public class ThreadDemo {
public static void main(String[] args) {
System.out.println("当前线程:" + Thread.currentThread().getName());
Dog dog = new Dog();
Dog dog2 = new Dog();
dog.run();
dog2.run();
}
}
可以看到,这时候并没有开启新的线程,main线程直接调用执行了run()方法中的代码。所以start()方法会开启新的线程并在新的线程中执行run()方法中的代码,而run()方法不会开启线程。查看start()的源代码,该方法调用了本地方法 private native void start0();即调用的是操作系统的底层函数:
public synchronized void start() {
if (threadStatus != 0)
throw new IllegalThreadStateException(); group.add(this); boolean started = false;
try {
start0();
started = true;
} finally {
try {
if (!started) {
group.threadStartFailed(this);
}
} catch (Throwable ignore) {
/* do nothing. If start0 threw a Throwable then
it will be passed up the call stack */
}
}
} private native void start0();
实现Runnable接口
第二种方式,实现Runnable接口,并覆写接口中的run()方法,这是推荐的也是最常用的方式。Runnable接口定义非常简单,就只有一个抽象的run()方法。
//Runnable接口源码
public interface Runnable {
public abstract void run();
}
class Dog implements Runnable {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println("当前线程:" + Thread.currentThread().getName() + "---" + i);
}
}
}
这时候的Dog类看起来跟线程什么的毫无关系,也没有了start()方法,怎么样开启一个新的线程呢?直接调用run()方法?想想也不行。这时候我们需要将一个Dog类的实例,作为Thread类的构造函数的参数传入,来创建一个Thread类的实例,并通过该Thread类的实例来调用start()方法从而开启线程。
public class ThreadDemo {
public static void main(String[] args) {
System.out.println("当前线程:" + Thread.currentThread().getName());
Dog dog = new Dog();
Thread thread = new Thread(dog);
thread.start();
}
}
这时候如果要开启第三个线程,需要创建一个新的Thread类的实例,同时传入刚才的Dog类的实例(当然也可以创建一个新的Dog实例)。这时候我们就可以看到跟继承Thread类的方式的区别:多个线程可以共享同一个Dog类的实例。
public class ThreadDemo {
public static void main(String[] args) {
System.out.println("当前线程:" + Thread.currentThread().getName());
Dog dog = new Dog();
Thread thread = new Thread(dog);
Thread thread2 = new Thread(dog);
thread.start();
thread2.start();
}
}
两种方式的比较
继承Thread类的方式有它固有的弊端,因为Java中继承的单一性,继承了Thread类就不能继承其他类了;同时也不符合继承的语义,Dog跟Thread没有直接的父子关系,继承Thread只是为了能拥有一些功能特性。而实现Runnable接口,①避免了单一继承的局限性,②同时更符合面向对象的编程方式,即将线程对象进行单独的封装,③而且实现接口的方式降低了线程对象(Dog)和线程任务(run方法中的代码)的耦合性,④如上面所述,可以使用同一个Dog类的实例来创建并开启多个线程,非常方便的实现资源的共享。实际上Thread类也是实现了Runnable接口。实际开发中多是使用实现Runnable接口的方式。
Java基础-多线程-①线程的创建和启动的更多相关文章
- Java基础-多线程-③线程同步之synchronized
使用线程同步解决多线程安全问题 上一篇 Java基础-多线程-②多线程的安全问题 中我们说到多线程可能引发的安全问题,原因在于多个线程共享了数据,且一个线程在操作(多为写操作)数据的过程中,另一个线程 ...
- java基础-多线程-线程组
线程组 * Java中使用ThreadGroup来表示线程组,它可以对一批线程进行分类管理,Java允许程序直接对线程组进行控制. * 默认情况下,所有的线程都属于主线程组. * public fi ...
- java基础-多线程线程池
线程池 * 程序启动一个新线程成本是比较高的,因为它涉及到要与操作系统进行交互.而使用线程池可以很好的提高性能,尤其是当程序中要创建大量生存期很短的线程时,更应该考虑使用线程池.线程池里的每一个线程代 ...
- 重学JAVA基础(四):线程的创建与执行
1.继承Thread public class TestThread extends Thread{ public void run(){ System.out.println(Thread.curr ...
- Java基础-多线程-②多线程安全问题
什么是线程的安全问题? 上一篇 Java基础-多线程-①线程的创建和启动 我们说使用实现Runnable接口的方式来创建线程,可以实现多个线程共享资源: class Dog implements Ru ...
- Java基础 继承的方式创建多线程 / 线程模拟模拟火车站开启三个窗口售票
继承的方式创建多线程 笔记: /**继承的方式创建多线程 * 线程的创建方法: * 1.创建一个继承于Thread 的子类 * 2.重写Thread类的run()方法 ,方法内实现此子线程 要完成的功 ...
- Java线程:创建与启动
Java线程:创建与启动 一.定义线程 1.扩展java.lang.Thread类. 此类中有个run()方法,应该注意其用法: public void run() 如果该线程是使用独立的 R ...
- Java多线程——线程的创建方式
Java多线程——线程的创建方式 摘要:本文主要学习了线程的创建方式,线程的常用属性和方法,以及线程的几个基本状态. 部分内容来自以下博客: https://www.cnblogs.com/dolph ...
- 备战金三银四!一线互联网公司java岗面试题整理:Java基础+多线程+集合+JVM合集!
前言 回首来看2020年,真的是印象中过的最快的一年了,真的是时间过的飞快,还没反应过来年就夸完了,相信大家也已经开始上班了!俗话说新年新气象,马上就要到了一年之中最重要的金三银四,之前一直有粉丝要求 ...
随机推荐
- 关于java中生产者消费者模式的理解
在说生产者消费者模式之前,我觉得有必要理解一下 Obj.wait(),与Obj.notify()方法.wait()方法是指在持有对象锁的线程调用此方法时,会释放对象锁,同时休眠本线程.notify() ...
- JFreeChart入门
JFreeChart主要用来各种各样的图表,这些图表包括:饼图.柱状图(普通柱状图以及堆栈柱状图).线图.区域图.分布图.混合图.甘特图以及一些仪表盘等等 (源代码下载) 示例程序运用的jar包: j ...
- 前端开发必须知道的JS(一) 原型和继承
原型和闭包是Js语言的难点,此文主要讲原型及原型实现的继承,在(二)中会讲下闭包,希望对大家有所帮助.若有疑问或不正之处,欢迎提出指正和讨论. 一. 原型与构造函数 Js所有的函数都有一个protot ...
- uva11983扫描线k次覆盖
自己做的是从下往上扫描的,一直wa,不知道坑在哪里..但是作为模板.我还是找了份不错的ac代码 /* 被覆盖不低于k次的点 每个点对应了一个单位面积,本题把点转面积即是被覆盖不低于k次的面积 可以当做 ...
- poj1511,线段树扫描线面积
经典题,线段树扫描线其实类似区间更新,一般的做法是想象一根扫描线从上扫到下或者从左扫到右,本题的做法是从上扫到下 只要扫到了一根水平线,就将其更新到线段树对应区间中,区间和它的子区间是独立更新的 #i ...
- python 全栈开发,Day79(Django的用户认证组件,分页器)
一.Django的用户认证组件 用户认证 auth模块 在进行用户登陆验证的时候,如果是自己写代码,就必须要先查询数据库,看用户输入的用户名是否存在于数据库中: 如果用户存在于数据库中,然后再验证用户 ...
- java基础篇---HTTP协议
java基础篇---HTTP协议 HTTP协议一直是自己的薄弱点,也没抽太多时间去看这方面的内容,今天兴致来了就在网上搜了下关于http协议,发现有园友写了一篇非常好的博文,博文地址:(http: ...
- visual studio 2015百度云下载
visual studio 2015百度云下载 https://pan.baidu.com/s/1b198Zo3mX5_zA2VX3xRRfw 提取码: 关注公众号[GitHubCN]回复2015获取
- k8s 使用
转自:https://blog.csdn.net/zyc88888/article/details/79281954
- 【Java】 剑指offer(68) 树中两个结点的最低公共祖先
本文参考自<剑指offer>一书,代码采用Java语言. 更多:<剑指Offer>Java实现合集 题目 输入两个树结点,求它们的最低公共祖先. 思路 该题首先要和面试 ...