当前位置:首页 > Java技术 > 关于Java多线程

关于Java多线程

2022年08月05日 10:35:51Java技术8

什么是多线程?

举个例子:
小明有个很不好的习惯,吃饭的时候喜欢讲话。他吃一口饭,说一句话,又吃一口饭,又说了三句话,他边吃饭边说话,而不是单纯的进行一项活动,这就是多线程。至于他想吃饭,还是想说话,完全看他的心情,多线程的运行也是如此,想运行哪个服务,完全看cpu的“心情”,我们能尽量改变cpu的“心情”,但不能强制它做决定,这就是线程优先级。
虽然看起来他是“边吃饭边说话”,但其实是在“交替进行”。线程也是如此,因为cpu处理速度极快,所以我们认为两个线程在同时进行。

java实现多线程的三种方式

第一种:继承Thread 类

不多说,直接上代码。

//第一种实现方式,继承Thread类
public class Thread01 extends Thread{
     
    @Override
    public void run() {
     
        for (int i = 0; i < 200; i++) {
     
            System.out.println("thread01线程执行--"+i);
        }
    }
}

我们新建一个主线程类,调用这个子线程,进行测试。

public class ThreadMain {
     
    public static void main(String[] args) {
     
        Thread01 thread01=new Thread01();
        thread01.start();
        for (int i = 0; i < 200; i++) {
     
            System.out.println("测试线程--"+i);
        }
    }
}

这里我们先调用thread01的start() 方法
测试结果:
关于Java多线程 _ JavaClub全栈架构师技术笔记这里我们可以看出,程序交替执行。

然后我们再调用thread01的run() 方法测试。

public class ThreadMain {
     
    public static void main(String[] args) {
     
        Thread01 thread01=new Thread01();
        thread01.run();
        for (int i = 0; i < 200; i++) {
     
            System.out.println("测试线程--"+i);
        }
    }
}

经过多次反复测试,发现每次都是先执行完子线程的for循环,又进行主线程的for循环。
关于Java多线程 _ JavaClub全栈架构师技术笔记
因此我们可以推导出,start()方法和run()方法的执行流程。
关于Java多线程 _ JavaClub全栈架构师技术笔记(start()方法的执行是并发执行,作图的时候写成了“并行”,不严谨)

第二种:实现Runnable接口(推荐使用)

新建线程thread02:

public class Thread02 implements Runnable{
     
    @Override
    public void run() {
     
        for (int i = 0; i < 2000; i++) {
     
            System.out.println("thread02线程执行--"+i);
        }
    }
}

测试线程:

public class ThreadMain {
     
    public static void main(String[] args) {
     
        Thread02 thread02=new Thread02();
        //这里执行start(),也是要通过Thread对象执行,thread02作为参数传入
        new Thread(thread02).start();
        for (int i = 0; i < 2000; i++) {
     
            System.out.println("测试线程--"+i);
        }
    }
}

这种方法避免了OOP的单继承局限性,相比较直接继承Thread类,推荐使用此方法。

第三种:实现Callable接口

新建线程thread03:


//这里需要自定义一个返回值,我在这里设置为boolean类型
public class Thread03 implements Callable<Boolean> {
     
    private String name;
    public Thread03(String name){
     
        this.name=name;
    }
    @Override
    public Boolean call() throws Exception {
     
        for (int i = 0; i < 2000; i++) {
     
            System.out.println(name+"线程执行--"+i);
        }
        return true;
    }
}

测试线程:

public class ThreadMain {
     
    public static void main(String[] args) throws Exception {
     
        Thread03 thread=new Thread03("thread---1---");
        Thread03 thread2=new Thread03("thread---2---");
        //创建一个容量为2的线程池
        ExecutorService executorService= Executors.newFixedThreadPool(2);
        //把两个thread放入线程池
        Future<Boolean> rs = executorService.submit(thread);
        Future<Boolean> rs2 = executorService.submit(thread2);
        //获取线程运行结果,对应Thread03.call()方法中的return
        Boolean aBoolean = rs.get();
        Boolean aBoolean2 = rs.get();
        System.out.println("执行结果1:"+aBoolean+",执行结果2:"+aBoolean2);
        //运行结束,不允许再往线程池中添加任务,但已经存在的任务会继续执行完
        executorService.shutdown();
    }
}

线程的五个状态

关于Java多线程 _ JavaClub全栈架构师技术笔记

线程的常用方法

  1. setPriority(int newPriority) :设置优先级,多线程优先级区间为 [1,10],默认优先级为5。
  2. sleep(long millis) :让指定线程休眠单位时间(毫秒)。
  3. join() :强启这个线程,直至执行完,再执行其他线程。
  4. yield():暂停这个线程,执行其他线程。
  5. interrupt():中断这个线程(不建议使用这种方式,推荐设计代码让线程运行结束自行停止)。
  6. isAlive():判断这个线程是否还处于活动状态。
  7. setDaemon(boolean bool):设置这个线程是否为守护线程。

用户线程和守护线程

线程分为两种,用户线程和守护线程。
用户线程就是一般用户定义的线程(当然,用户也可以定义守护线程)。
垃圾回收线程是典型的守护线程。
如果程序中所有的用户线程都退出了,那么所有的守护线程就都会被杀死,哪怕你的守护线程运行条件设置为无限循环,也会被停止。

线程同步方法之 synchronized 关键字

一个线程调用被synchronized修饰的资源时,会被分配一个“锁”,我更喜欢把它理解为“钥匙”,只有有这个“钥匙”的线程才能打开“锁”,操作资源。当此线程释放资源后,“钥匙”也会被释放,下一个线程才能拿到“钥匙”,对资源再进行操作。以此保证同一时间只有一个线程对同一资源进行操作,防止数据冲突产生严重错误。
Java中每一个对象都可以作为锁,这是synchronized实现同步的基础:

  1. 普通同步方法(实例方法),锁是当前实例对象 ,进入同步代码前要获得当前实例的锁。
  2. 静态同步方法,锁是当前类的class对象 ,进入同步代码前要获得当前类对象的锁。
  3. 同步方法块,锁是括号里面的对象,对给定对象加锁,一般选取需要操作的对象,进入同步代码库前要获得给定对象的锁。

Lock 锁 和 synchronized 的区别

两者区别:

1.首先synchronized是java内置关键字,在jvm层面,Lock是个java类;
2.synchronized无法判断是否获取锁的状态,Lock可以判断是否获取到锁;
3.synchronized会自动释放锁(a 线程执行完同步代码会释放锁 ;b 线程执行过程中发生异常会释放锁),Lock需在finally中手工释放锁(unlock()方法释放锁),否则容易造成线程死锁;
4.用synchronized关键字的两个线程1和线程2,如果当前线程1获得锁,线程2线程等待。如果线程1阻塞,线程2则会一直等待下去,而Lock锁就不一定会等待下去,如果尝试获取不到锁,线程可以不用一直等待就结束了;
5.synchronized的锁可重入、不可中断、非公平,而Lock锁可重入、可判断、可公平(两者皆可)
6.Lock锁适合大量同步的代码的同步问题,synchronized锁适合代码少量的同步问题。

什么是死锁

神评:“车钥匙在家里,家钥匙在车里”

比如有两个线程,每个线程要执行四个操作:

第一个线程:1.拿到筷子 2.拿到碗 3.吃饭 4.把碗筷放回餐柜
第二个线程:1.拿到碗 2.拿到筷子 3.吃饭 4.把碗筷放回餐柜

这个时候发生了这样一件事:第一个线程拿到了筷子的同时,第二个线程拿到了碗。由于第一个线程没有办法拿到碗,所以造成了线程阻塞,进而无法释放筷子资源。而第二个线程也无法拿到筷子,也造成了阻塞,无法释放碗资源。因此造成的后果,就叫做死锁。

关于junit4测试多线程结果不正确的问题

我在之前测试多线程时一直用的main方法而不是junit的@Test注解是有原因的。
我写了一个线程类进行测试:

public class Thread01 extends Thread{
     
    @Override
    public void run() {
     
        for (int i = 0; i < 1000; i++) {
     
            System.out.println("thread01线程执行--"+i);
        }
    }
}

输出结果:
关于Java多线程 _ JavaClub全栈架构师技术笔记
我们发现当输出到52的时候线程就停止了,并没有像我们想象的输出到1000。

这里我们看一下junit4的源代码:junit.textui.TestRunner 类

 public static void main(String args[]) {
     
        TestRunner aTestRunner = new TestRunner();
        try {
     
            TestResult r = aTestRunner.start(args);
            if (!r.wasSuccessful()) {
     
                System.exit(FAILURE_EXIT);
            }
            System.exit(SUCCESS_EXIT);
        } catch (Exception e) {
     
            System.err.println(e.getMessage());
            System.exit(EXCEPTION_EXIT);
        }
    }

这段代码的大概意思就是:主线程(main)成功执行结束后,执行 System.exit(SUCCESS_EXIT),也就是直接停止jvm。jvm停止了,子线程肯定无法继续运行了。

作者:程未满、
来源链接:https://blog.csdn.net/chengweimam/article/details/104965128

版权声明:
1、JavaClub(https://www.javaclub.cn)以学习交流为目的,由作者投稿、网友推荐和小编整理收藏优秀的IT技术及相关内容,包括但不限于文字、图片、音频、视频、软件、程序等,其均来自互联网,本站不享有版权,版权归原作者所有。

2、本站提供的内容仅用于个人学习、研究或欣赏,以及其他非商业性或非盈利性用途,但同时应遵守著作权法及其他相关法律的规定,不得侵犯相关权利人及本网站的合法权利。
3、本网站内容原作者如不愿意在本网站刊登内容,请及时通知本站(javaclubcn@163.com),我们将第一时间核实后及时予以删除。


本文链接:https://www.javaclub.cn/java/17915.html

分享给朋友:

“关于Java多线程” 的相关文章

Java空指针异常解决java.lang.NullPointerException解决心得

Java空指针异常解决java.lang.NullPointerException解决心得

今天做课设的时候运行程序报出以下错误 java.lang.NullPointerException 首先要理解的是此错误并不会在 程序中报错,只会在运行的时候报错。 是由于某个参数(集合,数组等数据)可能出现一个null值而导致后面的程序不能运行时...

两年前写的Java基础总结书

两年前写的Java基础总结书

想法衍生 两年前的我,突发奇想,把自己学的Java基础进行规范化的整理,因为自己的文档编辑能力有限,所以写的排版不是很好,参照图书排版的形式,将书籍进行整理,可以供学习Java基础的朋友参考,由于时间有限,可能也会有问题,请指出。下载地址在最后 截图如下:...

java基础知识讲解(一)数据类型和运算符

java基础知识讲解(一)数据类型和运算符

Java是一种强类型语言,每个变量都必须声明其数据类型。 Java的数据类型可分为两大类:基本数据类型(primitive data type)和引用数据类型(reference data type)。 Java中定义了3类8种基本数据类型 数值型- b...

Java 内存模型

Java 内存模型

📦 本文以及示例源码已归档在 javacore Java 内存模型(Java Memory Model),简称 JMM。 JVM 中试图定义一种 JMM 来屏蔽各种硬件和操作系统的内存访问差异,以实现让 Java 程序在各种平台下都能达到一致的内存访问效果。...

Java日志框架那些事儿

Java日志框架那些事儿

在项目开发过程中,我们可以通过 debug 查找问题。而在线上环境我们查找问题只能通过打印日志的方式查找问题。因此对于一个项目而言,日志记录是一个非常重要的问题。因此,如何选择一个合适的日志记录框架也非常重要。在Java开发中,常用的日志记录框架有JDKLog、Log4J、LogBack、SLF4J...

JDBC连接时所犯错误1.字符集设置不合适2.连接MySQL8.0社区版时时区不一致3..包名不能以Java.命名4.驱动被弃用

Microsoft JDBC Driver 的主页为:https://msdn.microsoft.com/en-us/data/aa937724.aspx 下载所需驱动 今天连接时报了四次错,记录下来 1.java.sql.SQLException:...

冒泡排序的原理,思路,以及算法分析(Java实现)

冒泡排序的原理,思路,以及算法分析(Java实现)

冒泡排序 如果遇到相等的值不进行交换,那这种排序方式是稳定的排序方式。 1.原理:比较两个相邻的元素,将值大的元素交换到右边 2.思路:依次比较相邻的两个数,将比较小的数放在前面,比较大的数放在后面。 (1)第一次比较:首先比较第...

枚举法 之Java实现凑硬币

问题? 如何利用1元五元十元凑硬币 Scanner in=new Scanner(System.in); int amout ; amout=in.nextInt(); for(int one =0;one<=amout;one+...

Java实现阶乘运算

n!=123*…n 学习编程就是要了解从问题到程序是如何实现的 Scanner in=new Scanner(System.in); int n ; n=in.nextInt(); // int i=1; int factor=1;...

java之整数的分解可以理解为倒序输出

Scanner in=new Scanner(System.in); int number ; number=in.nextInt(); int result=0; do{ int diget=number%10;...

发表评论

访客

◎欢迎参与讨论,请在这里发表您的看法和观点。