java多线程编程——多线程技能(代码片段)

Haust_Leone Haust_Leone     2023-02-17     526

关键词:

第一章 Java多线程技能


文章目录


前言

作为多线程编程的第一章,主要介绍Thread类的核心方法
线程如何启动
如何使线程暂停
线程的优先级
线程安全相关问题

提示:以下是本篇文章正文内容,下面案例可供参考

一、进程和多线程概述

进程是受操作系统管理的基本运行单元。

程序是指令序列,这些指令可以让CPU完成指定的任务。.java程序经编译后形成.class文件,在Windows中启动一个JVM虚拟机相当于创建了一个进程,在虚拟机中加载class文件并运行,在class文件中通过执行创建新线程的代码来执行具体的任务。测试用代码如下:

public class Test1 
    public static void main(String[] args) 
        try 
            Thread.sleep(Integer.MAX_VALUE);
           catch (InterruptedException e) 
            e.printStackTrace();
        
    

Test1类在重复运行3次后,可以在任务管理器的进程列表中看到创建了3个javaw.exe进程,说明每执行一次main()方法就创建一个进程,其本质就是JVM虚拟机进程。

线程可以理解为在进程中独立运行的子任务。

使用多线程有什么优点?可以大幅利用CPU的空闲时间来处理其他任务,使用多线程技术可以在同一时间执行更多不同的任务。

在什么场景下使用多线程技术?

1) 阻塞。一旦系统中出现了阻塞现象,则可以根据实际情况来使用多线程技术提高运行效率。
2)依赖。业务分为两个执行过程,分别是A和B。当A业务发生阻塞情况时,B业务的执行不依赖与A业务的执行结果,这时可以使用多线程技术来提高运行效率;如果B业务的执行依赖A业务的运行结果,则可以不适用多线程技术,按顺序进行业务的执行。

二、使用多线程

实现多线程编程主要有两种方式:一种是继承Thread类,另一种是实现Runnable接口。

1.继承Thread类

Thread类的声明结构:public class Thread implements Runnable
从上面的源代码中可以发现,Thread实现了Runnable接口,它们之间具有多态关系,多态结构的示例代码如下:
Runnable run1 = new Thread();
Runnable run2 = new MyThread();
Thread t1 = new MyThread();

使用Thread类的方式创建新线程时,最大的局限是不支持多继承,因为Java语言的特点是单根继承,所以为了支持多继承,完全可以实现Runnable接口。

创建一个自定义的线程类MyThread.java,此类继承自Thread,并且重写run()方法。在run()方法中添加新线程要执行的代码如下:

package mythread;

public class MyThread extends Thread 
    @Override
    public void run() 
        super.run();
        System.out.println("MyThread");
    

运行类代码如下:

package test;

import mythread.MyThread;

public class Run 
    public static void main(String[] args) 
        MyThread myThread = new MyThread();
        myThread.start();//耗时大
        System.out.println("运行结束!");//耗时小
    

上面代码使用start()方法启动一个线程,线程启动后会自动调用线程对象中的run()方法,run()方法中的代码就是线程对象要执行的任务,是线程任务的入口。
start()方法耗时的原因是执行了多个步骤,具体如下:

1)通过JVM告诉操作系统创建Thread
2)操作系统开辟内存并使用Windows SDK中的creatThread()函数创建Thread线程对象。
3)操作系统对Thread对象进行调度,以确定执行时机。
4)Thread在操作系统中被成功执行。
在使用多线程技术时代码的运行结果与代码的执行顺序和调用顺序是无关的。另外,线程是一个子任务,CPU以不确定的方式,或者说是以随机的时间来调用线程中的run()方法。

2.线程随机性的展现

创建自定义线程类MyThread.java的代码如下:

package mythread;

public class MyThread extends Thread 
    @Override
    public void run() 
        for (int i = 0; i < 10000; i++) 
            System.out.println("run=" + Thread.currentThread().getName());
        
    


再创建运行类Test.java代码:

package test;

import mythread.MyThread;

public class Test 
    public static void main(String[] args) 
        MyThread thread = new MyThread();
        thread.setName("myThread");
        thread.start();

        for (int i = 0; i < 10000; i++) 
            System.out.println("main=" + Thread.currentThread().getName());
        
    

Thread.java中的start()方法通知“线程规划器”——此线程已经准备就绪,准备调用线程对象的run()方法。如果调用thread.run()方法,而不是thread.start()方法,其实就不是异步执行了,而是同步进行。
多线程随机输出的原因是CPU将时间片分给不同的线程,线程获得时间片后就执行任务。时间片即CPU分配给各个程序的时间。每个线程被分配一个时间片,在当前的时间片内CPU去执行线程中的任务。CPU在不同的线程进行切换是需要耗时的,所以并不是创建的线程越多,运行效率就越高。

3.实现Runnable接口

如果想创建的线程类已经有一个父类了,就不能再继承Thread类,所以需要实现Runnable接口来解决这样的情况。
创建一个实现Runnable接口的MyRunnable类,代码如下:

package myrunnable;

public class MyRunnable implements Runnable
    @Override
    public void run() 
        System.out.println("运行中!");
    

运行类代码如下:

package test;

import myrunnable.MyRunnable;

public class Run 
    public static void main(String[] args) 
        Runnable runnable = new MyRunnable();
        Thread thread = new Thread(runnable);
        thread.start();
        System.out.println("运行结束!");
    

4.使用Runnable接口实现多线程的优点

首先创建业务A类:

package service;
public class AServer 
    @Override
    public void a_save_method() 
        System.out.println("a中的保存数据方法被执行");
    

再创建业务B类:

package service;
public class BServer extends AServer implements Runnable 
    @Override
    public void a_save_method() 
        System.out.println("b中的保存数据方法被执行");
    
    @Override
    public void run() 
		b_save_method();
	

通过实现Runnable接口,可间接地实现“多继承”的效果。在非多继承的情况下,使用继承Thread类和实现Runnable接口两种方式在去的程序运行的结果上没什么太大区别,一旦出现“多继承”的情况,则采用实现Runnable接口的方式来处理多线程问题是很必要的。

5.实例变量共享造成的非线程安全问题与解决方案

自定义线程类中的实例变量针对其他线程可以有共享和不共享之分,在多个线程之间交互时是很重要的技术点。

不共享数据的情况

每个线程都有各自的变量,各自控制自己的变量,变量不共享,不存在多个线程访问同一个变量的问题。

共享数据的情况

共享数据的情况就是多个线程可以访问同一个变量,不同线程可能同时对一个变量进行处理,就会产生“非线程安全问题”。可以通过在run()方法前加入synchronized关键字,使多个线程在执行run()方法时,以排队的方式进行处理。synchronized关键字可以对任意对象及方法加锁,而加锁的代码被称为“互斥区”或“临界区”。
当一个线程想要执行同步方法里面的代码时,就会首先尝试申请这把锁,如果申请到这把锁,则执行互斥区代码;若申请不到,就会一直申请这把锁,直到申请到为止,而且多个线程会争抢这把锁。

6.Servlet技术造成的非线程安全问题与解决方案

非线程安全问题主要是指多个线程对同一个对象中的同一个实例变量进行操作时会出现值被更改,值不同步的情况,影响程序执行流程。示例如下:

package controller;

public class LoginServlet 
    private static String usernameRef;
    private static String passwordRef;

    public static void doPost(String username, String password) 
        try 
            usernameRef = username;
            if (username.equals("a")) 
                Thread.sleep(5000);
            
            passwordRef = password;

            System.out.println("username=" + usernameRef + " password=" + password);
         catch (InterruptedException e) 
            // TODO Auto-generated catch block
            e.printStackTrace();
        
    


/*
运行结果1:
b bb
a aa
运行结果2:
a bb
a aa
*/

线程ALogin.java代码如下:

package extthread;
import controller.LoginServlet;

public class ALogin extends Thread 
    @Override
    public void run() 
        LoginServlet.doPost("a", "aa");
    

线程BLogin.java代码如下:

package extthread;

import controller.LoginServlet;

public class BLogin extends Thread
    @Override
    public void run() 
        LoginServlet.doPost("b", "bb");
    


运行类Run.java代码如下:

package test;

import extthread.ALogin;
import extthread.BLogin;

public class Run 
    public static void main(String[] args) 
        ALogin a = new ALogin();
        a.start();
        BLogin b = new BLogin();
        b.start();
    

解决如上“非线程安全”问题同样可以使用synchronized关键字,更改代码如下:

package controller;

public class LoginServlet 
    private static String usernameRef;
    private static String passwordRef;

    synchronized public static void doPost(String username, String password) 
        try 
            usernameRef = username;
            if (username.equals("a")) 
                Thread.sleep(5000);
            
            passwordRef = password;

            System.out.println("username=" + usernameRef + " password=" + password);
         catch (InterruptedException e) 
            // TODO Auto-generated catch block
            e.printStackTrace();
        
    

/*
输出结果:
a aa
b bb
*/

currentThread()方法

currentThread()方法可返回代码段正在被哪个线程调用。
创建MyThread类,代码如下:

package mythread;

public class MyThread extends Thread 
    public MyThread()
        System.out.println("构造方法的打印:" + Thread.currentThread().getName());
    

    @Override
    public void run() 
        System.out.println("run方法的打印:" + Thread.currentThread().getName());
    


运行类代码如下:

package run;

import mythread.MyThread;

public class Run 
    public static void main(String[] args) 
        MyThread myThread = new MyThread();
        myThread.start();
    


/*
构造方法的打印: main
run方法的打印: Thread—0
*/

MyThread.java类的构造函数是被main线程调用的,而run方法是被Thread-0线程调用的,run()方法是自动调用的方法。
更改代码如下:

package run;

import mythread.MyThread;

public class Run 
    public static void main(String[] args) 
        MyThread myThread = new MyThread();
        //myThread.start();
        myThread.run();
    

/*
构造方法的打印: main
run方法的打印: main
*/

执行方法run()和start()的区别

1)my.run();:立即执行run()方法,不启动新的线程。
2)my.start();:执行run()的时机不确定,启动新的线程

isAlive方法

isAlive()方法的功能是判断当前的线程是否存活.
测试代码如下:

package mythread;

public class MyThread extends Thread 
    @Override
    public void run() 
        System.out.println("run==" + this.isAlive());
    


package run;

import mythread.MyThread;

public class Run 
    public static void main(String[] args) 
        MyThread myThread = new MyThread();
        System.out.println("begin==" + myThread.isAlive());
        myThread.start();
        System.out.println("end==" + myThread.isAlive());
    

/*
begin==false
end==true
run==true
*/

isAlive()方法的作用是测试线程是否处于活动状态。线程已经启动且尚未终止的状态即活动状态。如果线程处于正在运行或准备开始运行的状态,就认为线程是“存活”的。对于代码:
System.out.println(“end==” + myThread.isAlive());
虽然其输出的值是true,但此值是不确定的,因为此时myThread线程还未执行完毕,如果将代码更改如下:

package run;

import mythread.MyThread;

public class Run 
    public static void main(String[] args) throws InterruptedException 
        MyThread myThread = new MyThread();
        System.out.println("begin==" + myThread.isAlive());
        myThread.start();
        Thread.sleep(1000);
        System.out.println("end==" + myThread.isAlive());
    

则代码System.out.println(“end==” + myThread.isAlive());
输出结果为false,因为myThread对象已经在1s之内执行完毕。

sleep(long millis)方法

sleep方法的使用是在指定的时间(毫秒)内让当前“正在执行的线程”休眠(暂停执行),这个“正在执行的线程”是指this.currentThread()返回的线程。
示例代码,类Mythread.java代码:

package mythread;

public class MyThread extends Thread 
    @Override
    public void run() 
        try 
            System.out.println("run threadName="
                    + this.currentThread().getName() + " begin="
                    + System.currentTimeMillis());
            Thread.sleep(2000);
            System.out.println("run threadName="
                    + this.currentThread().getName() + " end="
                    + System.currentTimeMillis());
         catch (InterruptedException e) 
            // TODO Auto-generated catch block
            e.printStackTrace();
        
    

运行类Run1.java代码如下:

package run;

import mythread.MyThread;

public class Run1 
    public static void main(String[] args) 
        MyThread myThread = new MyThread();
        System.out.println("begin=" + System.currentTimeMillis());
        myThread.run();
        System.out.println("end=" + System.currentTimeMillis());
    

/*
begin=1604938554575
run threadName=main begin=1604938554582
run threadName=main end=1604938556589
end=1604938556589
*/

运行类Run2.java代码如下:

package run;

import mythread.MyThread;

public class Run 
    public static void main(String[] args) 
        MyThread myThread = new MyThread();
        System.out.println("begin=" + System.currentTimeMillis());
        myThread.start();
        System.out.println("end=" + System.currentTimeMillis());
    

/*
begin=1604938758015
end=1604938758021
run threadName=Thread-0 begin=1604938758021
run threadName=Thread-0 end=1604938760033
*/

直接调用run()方法,main线程和MyThread线程同步执行。
使用start()方法启动线程,由于main线程与MyThread线程是异步执行的,所以首先输出的信息为begin和end,而MyThread线程是后执行的,在最后两行间隔了2s输出run…begin和run…end相关的信息。

sleep(long millis, int nanos)方法

sleep(long millis, int nanos)方法的作用是在指定的毫秒数加指定的纳秒数内让当前正在运行的线程休眠(停止执行),此操作受到系统计时器和调度程序的精度和准确性的影响。

StackTraceElement[] getStackTrace()方法

StackTraceElement[] getStackTrace()方法的作用是返回一个表示该线程堆栈跟踪元素的数组。如果该线程尚未启动或已经终止,则该方法返回一个零长度数组。如果返回的数组不是零长度的,则其第一个元素代表堆栈顶,它是该数组中最新的方法调用。最后一个元素代表堆栈底,是该数组中最旧的方法调用。

static void dumpStack()方法

static void dumpStack()方法的作用的将当前线程的堆栈跟踪信息输出至标准错误流。该方法仅用于调试。

getId()方法

getId()方法用于取得线程的唯一标识。

三、停止线程

停止一个线程意味着在线程处理完任务之前停止正在做的操作,也就是放弃当前的操作,必须做好防范措施,以便达到预期的效果。停止一个线程可以使用Thread.stop()方法,但是这个方法是不安全的,而且是被弃用的。
大多数情况下,停止一个线程使用Thread.interrupt()方法,但这个方法不会终止一个正在运行的线程,还需要加入一个判断才可以完成线程的停止。
在Java中有 3 种方法可以使正在运行的线程终止运行:
  1)使用退出标志使线程正常退出。
  2)使用stop()方法强制终止线程。
  3)使用interrupt()方法终止线程。

1. 终止不了的线程

调用interrupt()方法仅仅是在当前线程中做了一个停止的标志,不是真正的停止线程。

2. 判断线程是否为停止状态

Thread.java 类提供了两个判断方法:
  1)public static boolean interrupted(): 测试currentThread() 是否已经中断。
  2)public boolean this.isInterrupted(): 测试 this 关键字所在类的对象是

java多线程编程核心技术(代码片段)

java多线程编程核心技术前言第1章Java多线程技能1.1进程和多线程概述1.2使用多线程1.2.1继承Thread类1.2.2使用常见命令分析线程信息前言写在前面:这篇文章会不断更新针对于自己对《java多线程编程核心技术》这本书的总结与... 查看详情

《java多线程编程核心技术》多线程技能

最近阅读了一本《Java多线程编程核心技术》,总结了一下每章的知识点:第一章,java多线程技能知识点:1,实现多线程编程的方式主要有两种:一是继承Thread类,重新run方法,二是实现Runnable接口... 查看详情

java多线程编程

参考书籍:Java多线程编程核心技术(高洪岩)一、java多线程技能  1.线程的启动进程:QQ.exe线程:QQ发一条消息由一个线程处理,QQ传输文件由一个线程处理创建线程:使用多线程的时候,代码运行结果与代码执行顺序和调用... 查看详情

java进阶多线程编程,程序员必备技能!

文章目录多线程编程线程的工作过程Java线程拥有优先级创建多线程1.继承Thread类,重写run()方法2.实现接口Runnable接口,实现run方法3.实现Callable接口,实现call方法线程的生命周期结语个人主张自学,但是也不能忘记了讨论的重要... 查看详情

最全java并发编程技能:多线程+线程池+线程锁+并发工具+并发容器

推荐:一线大厂多线程面试真题:由多线程向互联网三高架构的演变,高薪程序员必知必会的技术栈https://www.bilibili.com/video/BV1Yh411b7zDJava并发编程的技能基本涵括以下5方面:多线程线程池线程锁并发工具类并发容... 查看详情

邂逅多线程:java多线程编程(代码片段)

目录1.创建线程2.线程同步3.线程通信  Java提供了丰富的API来支持多线程编程,包括Thread类、Runnable接口、ThreadLocal类、Lock类、Condition接口等。本文将介绍Java多线程编程的几个重要方面。1.创建线程  Java中创建线程的方式... 查看详情

java多线程总结(代码片段)

Java多线程在Java的JDK开发包中,已经自带了对多线程技术的支持,可以很方便地进行多线程编程。实现多线程编程的方式有两种,一种是继承Thread类,另一种是实现Runnable接口。使用继承Thread类创建线程,最大的局限就是不能多... 查看详情

java多线程编程——对象及变量的并发访问(代码片段)

Java多线程编程——对象及变量的并发访问文章目录Java多线程编程——对象及变量的并发访问前言一、synchronized同步方法1.方法内的变量为线程安全2.实例变量非线程安全问题与解决方案3.同步synchronized在字节码指令中的原理4.多... 查看详情

java多线程(代码片段)

...程序。什么是进程:程序的同时多运行称为进程。什么是线程:程序中不同的执行路经称为线程,线程是程序的最小执行单位。多线程:如果在一个进程中同时运行了多个线程,用来完成不同的工作,则称为”多线程”多... 查看详情

java多线程并发编程(代码片段)

多线程并发在多核CPU中,利用多线程并发编程,可以更加充分地利用每个核的资源在Java中,一个应用程序对应着一个JVM实例(也有地方称为JVM进程),如果程序没有主动创建线程,则只会创建一个主线... 查看详情

java多线程编程的两种方式(代码片段)

上一篇文章讲了多线程的同步但是发现好多同学对多线程的如何编程有点疑惑,今天根据网上的一些资料和方法来简单根据我之前的示例代码来演示一些,多线程编程实现的两种方法:1、定义类继承thread类并且重写... 查看详情

java并发多线程编程——cyclicbarrier(代码片段)

...意思是可循环(Cyclic)使用的屏障(barrier)。CyclicBarrier让一组线程到达一个屏障(也可以叫做同步点)时被阻塞,直到最后一个线程到达屏障时,屏障才会开门,所有被屏障拦截的线程才会继续干活。线程进入屏障通过CyclicBa 查看详情

《java多线程编程核心技术》

第一章java多线程技能  1.1进程和多线程的概念及线程的优点  1.2使用多线程    1.2.1继承thread类    1.2.2实现runnable接口    1.2.3实例变量与线程安全    1.2.4留意i--与system.out.println()的异常  1.3currentthr... 查看详情

java面试知识点1——多线程和并发编程(代码片段)

多线程和并发编程在Java面试中,java多线程和并发编程是必问面试点,主要围绕多线程的基本概念及原理以及并发编程中线程安全、线程同步等方面展开,因此需要掌握基本的概念点,本文也将详细介绍。如果面... 查看详情

廖雪峰java11多线程编程-3高级concurrent包-1reentrantlock(代码片段)

线程同步:是因为多线程读写竞争资源需要同步Java语言提供了synchronized/wait/notify编写多线程同步很困难所以Java提供了java.util.concurrent包:更高级的同步功能简化多线程程序的编写JDK>=1.5java.util.locks.ReetrantLock用于替代synchronized... 查看详情

java自用高级编程-1.多线程(代码片段)

...下👇👇👇开始高级编程部分喽~~~~🤭多线程一、程序、进程、线程的理解1.程序(programm)概念:是为完成特定任务、用某种语言编写的一组指令的集合。即指一段静态的代码。 查看详情

java并发多线程编程——countdownlatch(代码片段)

...模拟员工下班关门案例)一、CountDownLatch概述让一些线程阻塞直到另外一些完成后才被唤醒。CountDownLatch主要有两个方法:(1ÿ 查看详情

androidnativeapp开发笔记:多线程编程(代码片段)

文章目录目的Java中的多线程ThreadRunnableTimerAndroid中的多线程HandlerAsyncTask总结目的Android中UI线程对于开发者和用户来说都是最主要接触到的线程。一般来说为了UI流畅、不卡顿,耗时操作是不推荐放在UI线程中的。但是耗时操... 查看详情