Java多线程编程

1,进程与线程

  在Java语言里面最大的特点是支持多线程的开发(也是为数不多支持多线程的编程语言Golang、Clojure方言、Elixir),所以在整个的Java技术学习里面,如果你不能够对多线程有一个全面并且细致的了解,则在日后进行一些项目的设计过程之中尤其是并发访问的设计过程之中就会出现严重的技术缺陷。

  如果想要理解线程,那么首先就需要了解一下进程的概念,在传统的DOS系统的时代,其本身有一个特征:如果你电脑上出现了病毒,那么所有的程序将无法执行,因为传统的DOS采用的是单进程处理,而单进程处理的最大特点:在同一个时间段,只允许运行一个程序。

  到了Windows的时代就开启了多进程的设计,于是就表示一个时间段上可以运行多个程序,并且这些程序将进行资源的轮流抢占(当时只是单核CPU,《操作系统概论》时间片轮转算法),所以在一个时间段上会有多个程序依次执行,但是在同一个时间点上只会有一个进程执行,而后来到了多核CPU,由于可以处理的CPU多了,那么即便有再多的进程出现,也可以比单核CPU处理的速度有所提高。

  线程是在进程基础之上划分的最小程序单元,线程是在进程基础上创建的并且使用的,所以线程依赖于进程的支持,但是线程的启动速度要比进程快许多,所以当使用多线程进行并发处理的时候,其执行的性能要高于进程。

Java是多线程的编程语言, 所以Java在进行并发访问处理的时候可以得到更高的访问性能。

2,Thread类实现多线程

  如果要想在Java之中实现多线程的定义,那么就需要有一个专门的线程主体类进行线程的执行任务的定义,而这个主体类的定义是有要求的,必须实现特定的接口或者继承特定的父类才可以完成。

继承Thread类实现多线程

  Java里面提供有一个java.lang.Thread的程序类,那么一个类只要继承了此类就表示这个类为线程主体类,但是并不是说这个类就可以直接实现多线程处理了,因为还需要覆写Thread类中提供的一个run()方法【public void run()】,而这个方法就属于线程的主方法。

·范例:多线程主体类

 class MyThread extends Thread{
private String title;
public MyThread(String title){
this.title=title;
}
@Override
public void run() {
for(int x=0;x<10;x++){
System.out.println(this.title+"运行,x="+x);
}
}
}

·范例:直接使用run()启动多线程要执行的功能都应该在run()方法中进行定义。需要说明的是:在正常情况下如果想使用一个类中的方法,那么肯定要产生实例化对象,而后去调用类中提供的方法,但是run()方法是不能够直接调用的,因为这里面牵扯到一个操作系统的资源调度的问题,所以要想启动多线程必须使用start()方法完成【public void start()】。

 public class Main {
public static void main(String[] args) {
new MyThread("线程A").run();
new MyThread("线程B").run();
new MyThread("线程C").run();
}
}
 先执行线程A,A完毕后
执行线程B,B完毕后
执行线程C,C完毕后

·范例:使用start()启动

 public class Main {
public static void main(String[] args) {
new MyThread("线程A").start();
new MyThread("线程B").start();
new MyThread("线程C").start();
}
}

  ·疑问?为什么多线程的启动不直接使用run()方法,而必须使用Thread类中的start()方法呢?通过此时的调用你可以发现,虽然调用了是start()方法,但是最终执行的是run()方法,并且所有的线程对象都是交替执行的。执行顺序不可控,完全由他自己决定。

  如果想要清楚这个问题,最好的做法是查看一下start()方法的实现操作,可以直接通过源代码观察。

 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) {
}
}
}
private native void start0();

·范例:一个线程类的对象重复启动,抛出异常发现在start()方法里面会跑出一个“IllegalThreadStateException”的异常类对象,但是整个的程序并没有使用throws或者明确的try...catch处理,因为该异常一定是RuntimeException的子类,每一个线程类的对象只允许启动一次,如果重复启动则就抛出此异常,例如:下面的代码就会抛出此异常。

 public class Main {
public static void main(String[] args) {
MyThread myThread=new MyThread("线程A");
myThread.start();
myThread.start();
}
}

  在Java程序执行的过程之中考虑到对于不同层次开发者的需求,所以其支持有本地的操作系统函数调用,而这项技术就被称为JNI(Java Native Inteface)技术,但是Java开发过程之中并不推荐这样使用,利用这项技术可以使用一些操作系统提供的底层函数进行一些特殊的处理,而在Thread类里面提供提供的start0()就表示需要将此方法依赖于不同的操作系统实现。

  任何情况下,只要定义了多线程,多线程的启动永远只有一种方法:Thread类中的start()方法。

3,Runnable接口实现多线程

虽然可以通过Thread类的继承实现多线程的定义,但是在Java程序里面面对于继承永远都是存在有单继承的局限的,所以在Java里面又提供第二种多线程的主体定义结构形式:实现java.lang.Runnable接口,此接口定义如下。

 @FunctionalInterface
//从JDK1.8引入了Lambda表达式之后就变为了函数式接口
public interface Runnable{
public void run();
}

·范例:通过Runnable实现多线的主体类

 class MyThread implements Runnable{
private String title;
public MyThread(String title){
this.title=title;
}
@Override
public void run() {
for(int x=0;x<10;x++){
System.out.println(this.title+"运行,x="+x);
}
}
}

·构造方法:public Thread(Runnable target);但是此时由于不再继承Thread父类了,那么对于此时的MyThread类中也就不再支持有start()方法,可是如果不使用Thread.start()方法是无法进行多线程启动,那么这个时候就需要观察一下Thread类所提供的构造方法。

·范例:启动多线程

 public class Main {
public static void main(String[] args) {
Thread threadA=new Thread(new MyThread("Runnable线程对象A"));
Thread threadB=new Thread(new MyThread("Runnable线程对象B"));
Thread threadC=new Thread(new MyThread("Runnable线程对象C"));
threadA.start();//启动多线程
threadB.start();
threadC.start();
}
}

  可以发现从JDK1.8开始,Runnable接口使用了函数式的接口定义,所以也可以直接利用Lambda表达式进行线程类实现。这个时候多线程实现里面可以发现,由于只是实现了Runnable接口对象,所以此时线程主体类上就不再有单继承的局限。

·范例:利用Lambda实现多线程定义

 public class Main {
public static void main(String[] args) {
for(int x=0;x<3;x++){
String title="线程对象-"+x;
Runnable run=()->{
for(int y=0;y<10;y++){
System.out.println(title+"运行,y="+y);
}
};
new Thread(run).start();
}
}
}

  在以后的开发之中对于多线程的实现,优先考虑的就是Runnable接口实现,并且永恒都是通过Thread类对象启动多线程。

·范例:直接运行

 public class Main {
public static void main(String[] args) {
for(int x=0;x<3;x++){
String title="线程对象-"+x;
new Thread(()->{
for(int y=0;y<10;y++){
System.out.println(title+"运行,y="+y);
}
}).start();
}
}
}

4,Thread与Runnable关系

  经过一些列的分析之后可以发现,在多线的实现过程之中已经有了两种做法:Thread类、Runnable接口。如果从我们代码的本身来讲肯定使用Runnable是最方便的,因为其可以避免单继承的局限,也可以更好的进行功能的扩充。

但是从结构上也需要来观察Thread与Runnable的联系,打开Thread类定义。

 public class Thread extends Object implements Runnable{}

  发现现在Runnable接口的子类,那么现在之前继承Thread类的时候实际上覆写的还是之前的Runnable接口Runnable的Run()方法,于是此时来观察一下程序的类结构。

  多线程的设计之中,使用了代理设计模式的结构,用户自定义的线程主体只是负责项目核心功能的实现,而所有的辅助实现全部交由Thread类来处理。

  在进行Thread启动多线程的时候调用的是start()方法,而后找到的是run()方法,当通过Thread类的构造方法传递了一个Runnable接口对象的时候,那么该接口对象将被Thread类中的target属性所保存,在start方法执行的时候会调用Thread类中的run()方法,而这个run()方法去调用Runnable接口子类被覆写过的run()方法。

  多线程开发的本质实质上是在于多个线程可以进行同一资源的抢占,那么Thread主要描述的是线程,而资源的描述是通过Runnable完成的。

·范例:利用买票程序来实现多个线程的资源并发访问

 class MyThread implements Runnable{//线程主体
private int ticket=5;
@Override
public void run() {
for(int x=0;x<100;x++){
if(this.ticket>0){
System.out.println("买票,ticket="+this.ticket--);
}
}
}
}
public class Main {
public static void main(String[] args) {
MyThread mt=new MyThread();
new Thread(mt).start();//第一个线程启动
new Thread(mt).start();//第二个线程启动
new Thread(mt).start();//第三个线程启动
}
}

通过内存分析图来分析本程序的执行结构。

5,Callable接口实现多线程

从最传统的开发来讲如果要进行多线程的实现肯定依靠的就是Runnable,但是Runnable接口有一个缺点:当线程执行完毕之后无法获取一个返回值。所以从JDK1.5之后就提出了一个新的线程实现接口:java.util.concurrent.Callable接口,首先来观察这个接口的定义:

 @FunctionalInterface
public interface Callable<V>{
public V call() throws Exception;
}

  可以发现Callable定义的时候可以设置一个泛型,此泛型的类型就是返回数据的类型,这样的好处是可以避免向下转型所带来的安全隐患。

·范例:使用Callable实现多线程处理

 import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;
class MyThread implements Callable<String> {//线程主体
@Override
public String call() throws Exception {
for(int x=0;x<10;x++){
System.out.println("******线程执行、x="+x);
}
return "线程执行完毕";
}
}
public class Main {
public static void main(String[] args) throws Exception{
FutureTask<String> task=new FutureTask<>(new MyThread());
new Thread(task).start();
System.out.println("【线程返回数据】"+task.get());
}
}
/*
面试题:请解释Runnable和Callable的区别? ①Runnable是在JDK1.0的时候提出的多线程的实现接口,而Callable是在JDK1.5之后提出的; ②java.lang.Runnable接口之中只提供有一个run()方法,并且没有返回值; ③java.util.concurrent.Callable接口提供有call()方法,可以有返回值;
*/

6,多线程运行状态

  对于多线程的开发而言,编写程序的过程之中总是按照:定义线程的主体类,而后通过Thread类进行线程的启动,但是并不意味着你调用start()方法,线程就已经开始运行了,因为整体的线程处理有自己的一套运行状态。

  ①任何一个线程的对象都应该使用Thread类来封装,所以线程的启动使用的是start(),但是启动的时候实际上若干个线程都将进入到一种就绪状态,现在并没有执行;

  ②进入到就绪状态之后就需要等待进行资源调度,当某一个线程调度成功之后则进入到运行状态【run()方法】,但是所有的线程不可能一直持续的执行下去,中间需要产生一些暂停的状态,例如:某个线程执行一段时间之后就需要让出资源;而后这个线程进入阻塞状态,随后重新回到就绪状态;

  ③当run()方法执行完毕之后,实际上该线程的主要任务也就结束了,那么此时就可以直接进入到停止状态。

进阶Java编程(1)多线程编程的更多相关文章

  1. Java中的 多线程编程

    Java 中的多线程编程 一.多线程的优缺点 多线程的优点: 1)资源利用率更好2)程序设计在某些情况下更简单3)程序响应更快 多线程的代价: 1)设计更复杂虽然有一些多线程应用程序比单线程的应用程序 ...

  2. Java基础知识➣多线程编程(五)

    概述 Java 给多线程编程提供了内置的支持.一个多线程程序包含两个或多个能并发运行的部分.程序的每一部分都称作一个线程,并且每个线程定义了一个独立的执行路径.使用多线程也是为了充分的利用服务器资源, ...

  3. [Java][读书笔记]多线程编程

    前言:最近复习java,发现一本很好的资料,<J​a​v​a​2​参​考​大​全​ ​(​第​五​版​)​> ​ ​H​e​r​b​e​r​t​.Schildt.书比较老了,06年的,一些 ...

  4. Java多线程编程(2)--多线程编程中的挑战

    一.串行.并发和并行   为了更清楚地解释这三个概念,我们来举一个例子.假设我们有A.B.C三项工作要做,那么我们有以下三种方式来完成这些工作:   第一种方式,先开始做工作A,完成之后再开始做工作B ...

  5. 廖雪峰Java13网络编程-1Socket编程-3TCP多线程编程

    TCP多线程编程 一个ServerSocket可以和多个客户端同时建立连接,所以一个Server可以同时与多个客户端建立好的Socket进行双向通信. 因此服务器端,当我们打开一个Socket以后,通 ...

  6. SDK编程之多线程编程

    本课中,我们将学习如何进行多线程编程.另外我们还将学习如何在不同的线程间进行通信. 理论:前一课中,我们学习了进程,其中讲到每一个进程至少要有一个主线程.这个线程其实是进程执行的一条线索,除此主线程外 ...

  7. linux编程之多线程编程

    我们知道,进程在各自独立的地址空间中运行,进程之间共享数据需要用mmap或者进程间通信机制,有些情况需要在一个进程中同时执行多个控制流程,这时候线程就派上了用场,比如实现一个图形界面的下载软件,一方面 ...

  8. python核心编程(多线程编程)

    1.全局解释器锁 2.threading模块 thread类

  9. Java多线程编程(4)--线程同步机制

    一.锁 1.锁的概念   线程安全问题的产生是因为多个线程并发访问共享数据造成的,如果能将多个线程对共享数据的并发访问改为串行访问,即一个共享数据同一时刻只能被一个线程访问,就可以避免线程安全问题.锁 ...

随机推荐

  1. java实现自定义同步组件的过程

    实现同步组件twinsLock:可以允许两个线程同时获取到锁,多出的其它线程将被阻塞. 以下是自定义的同步组件类,一般我们将自定义同步器Sync定义为同步组件TwinsLock的静态内部类. 实现同步 ...

  2. Leetcode题目78.子集(回溯-中等)

    题目描述: 给定一组不含重复元素的整数数组 nums,返回该数组所有可能的子集(幂集). 说明:解集不能包含重复的子集. 示例: 输入: nums = [1,2,3] 输出: [ [3],   [1] ...

  3. echarts中国地图3D各个城市标点demo

    <!DOCTYPE html><html><head>    <meta charset="UTF-8">    <meta ...

  4. HTML属性操作

    属性名 属性值 相关操作:读与取 一.属性读操作:元素.属性,其实在就是找到等号右边的值 代码为: <!DOCTYPE html> <html lang="en" ...

  5. go代理设置

    在Go 1.13中,我们可以通过GOPROXY来控制代理,以及通过GOPRIVATE控制私有库不走代理. 设置GOPROXY代理: go env -w GOPROXY=https://goproxy. ...

  6. 使用pyinstaller 打包python程序

    1.打开PyCharm的Terminal,使用命令pip install pyinstaller安装pyinstaller 2.打包命令:pyinstaller --console --onefile ...

  7. 错误代码 2003不能连接到MySQL服务器在*.*.*.*(10061)

    错误代码 2003不能连接到MySQL服务器在*.*.*.*(10061) 错误代码 2003不能连接到MySQL服务器在*.*.*.*(10061)哪位大侠知道怎么解决啊? 在线等!!! [[i] ...

  8. vsftpd 配置用户及根目录及其参数详解

    vsftpd 常用功能参数配置及参数详解 Table of Contents 1. 配置超级服务 2. 配置匿名用户 3. 配置本地用户登录 4. 配置虚拟用户登录 5. 使用SSL登入 6. 日志文 ...

  9. Oracle 对某张表中的某一列进行取余,将结果集分为多个集合

    比如分为 5个集合,那么就用某一列和5 取余 ,分别可以取  余数为 0.1.2.3.4 的结果集,那么就把集合分为5个小的集合了 1.取余数为 0 的集合 select * from (select ...

  10. 一百二十四:CMS系统之首页导航条和代码抽离

    模板抽离 由于前后台的模板有些需要的元素如,js,css是相同的,这里抽离出来做base模板 {% from "common/_macros.html" import static ...