Java遨游在多线程的知识体系中

Posted 意愿三七

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java遨游在多线程的知识体系中相关的知识,希望对你有一定的参考价值。

前言

因为知识比较多,想把文章字数控制在一定范围之内,本文只有先发一篇多线程部分篇幅,之后的知识也会马上赶出来的,有什么有问题的地方经管提出,会虚心接受,并且认真改正。


一、第一个多线程程序

1.1、Thread用法一

创建一个子类继承自Thread,重写Thread中的run方法,这个方法内部就包含了这个线程要执行的代码(每一个线程都是一个独立的执行流,要执行一些代码),当线程跑起来了,就会依次执行这个run方法的代码。

class MyThread1 extends Thread    //自己定义的类
    @Override
    public void run() 
    	//这里才是具体要执行什么
        System.out.println("hello,Thread!");
    



public class Thread1   
    public static void main(String[] args) 
        //要想创建出这个线程,需要做的两件事
        //1.创建Thread实例,此处创建的是MyThread1

        MyThread1 t = new MyThread1();
        //2.调用Thread的start方法,才是真正在系统内部,创建线程
        t.start();
    


大家要记住在继承了Thread类里面,重写run方法,run方法里面才是具体要执行什么。重写方法快捷键 ctrl+O (光标在那个继承的多线程类)

调用Thread的start方法,才是真正在系统内部创建线程!!! 调用start就会在系统中创建出一个新的线程!

mian()方法自身也是通过一个线程来执行的(一个进程里面不可能一个线程也没有,只是得有一个线程),现在通过 t.start()就又创建了一个新的线程


上面的图,是先执行main的线程,然后调用了t.start()就进start线程输出语句,这个两个线程之间是并发执行的关系(宏观上同时执行)


我们怎么知道他是并发执行的呢,接下来我们可以做个小实验,main线程和MyThread2线程都加上死循环,同时跑就可以看的出来

class  MyThread2 extends Thread
    @Override
    public void run() 
        while(true)
            System.out.println("Hello,thread");
            //由于会很快执行下去,我们加一个小延迟
            try 
                Thread.sleep(1000);
             catch (InterruptedException e) 
                e.printStackTrace();
            
        
    


public class Thread2 
    public static void main(String[] args) 
        //这是主线程
        MyThread2 t2= new MyThread2();
        t2.start();  //执行t2线程

        //以下是执行主线程
        while(true)
            System.out.println("hello , main");
            try 
                Thread.sleep(1000);
             catch (InterruptedException e) 
                e.printStackTrace();
            
        
    











执行流程:

我们也可以看的出它并不是你执行一次,我执行一次,偶尔会你两条我两条,多个线程之间执行的先后顺序并不是完全确定的,当1s时间到了之后,到底系统先唤醒哪个线程,不确定的!!(取决操作系统内部调度代码的具体实现)

如果多线程之间没有手动控制先后顺序这个时候让我多个线程之间执行是:“随机顺序”,这个是多线程编程的万恶之源。


1.2 、Jconsole工具使用

我们除了上面看线程的运行, 还有什么可以直观的看见这两个代码的线程吗

使用JDK自带的Jconsole工具 ,JDK中包含了很多的exe程序,这些都是开发/运行/调试 所 实用的一些工具

下面的图罗列的是计算机上(代码要运行起来才可以看见)的java进程,没有罗列所有,只是java进程(毕竟是java的JDK)

没有名字的一般是系统搞的进程,现在自己的代码连接


jconsole其实相当一个“监控程序”,能够看到一个java进程内部很多的详细信息,类似于医院的x光片一样

和我们代码相关的就只有2个其他是jvm自带的:

这俩线程,是代码密切相关的,其他是jvm自动创建的线程。这些线程就完成了一些辅助工作,比如统计jvm内部一些情况信息,并通过网络的形式通过给其他的程序,再比如进行垃圾回收的操作,也需要在这里通过一些特定的线程来完成。


我们点击线程主要看的是状态和堆栈追踪


类似与进程,线程也是有自己的状态的,TIMED_WAITING 这个状态就是“阻塞状态”(睡眠的状态)这个状态就是由sleep方法引起的。

堆栈跟踪的信息是你点击的那一瞬间信息


1.3、Thread用法二

创建一个类实现Runnable接口(也是标准库自带的一个接口),也是重写run方法

创建Thread实例,然后把刚才的Runnable实例给设置进去

//Runnable接口表示一个”任务“
class MyRunnable implements Runnable
    @Override
    public void run() 
        //重新写run方法 描述了任务具体要进行的工作
        while(true)
            System.out.println("hello thread");
            try 
                Thread.sleep(1000);
             catch (InterruptedException e) 
                e.printStackTrace();
            
        
    


public class Demo3 

    public static void main(String[] args) 
        MyRunnable myRunnable = new MyRunnable();
        Thread t = new Thread(myRunnable);
        t.start();


        while(true)
            System.out.println("hello main");
            try 
                Thread.sleep(1000);
             catch (InterruptedException e) 
                e.printStackTrace();
            
        
    


也可以直接把MyRunnable放进去

这种写法和上面的写法十分相似 第一种写法是通过继承Thread实现的,第二种是通过实现Runnable实现的(通过Runnable这种方式,相当于把要执行的任务和Thread类进行了分离(解耦合)) 一般建议采取第二种写法。


1.4、Thread用法三 / 用法四

其实和上面的用法一和用法二没有本质的区别,写法换了个模样(匿名内部类)

public class Demo4 
    public static void main(String[] args) 
        Thread t = new Thread()
            @Override
            public void run() 
               while(true) 
                    System.out.println("hello thread");
                    try 
                        Thread.sleep(1000);
                     catch (InterruptedException e) 
                        e.printStackTrace();
                    
                
            
        ;
        
        t.start();
        
        while (true)
            System.out.println("hello main");

            try 
                Thread.sleep(1000);
             catch (InterruptedException e) 
                e.printStackTrace();
            
        
    

注意分号位置

这个就是创建了一个匿名子类(匿名内部类)继承自Thread,接下来调用start()


此处是Runnable的匿名内部类并创建出了实例 直接交给Thread来进行使用

public class Demo5 
    public static void main(String[] args) 
        Thread t  = new Thread(new Runnable() 
            @Override
            public void run() 
                System.out.println("hello thread");
                try 
                    Thread.sleep(1000);
                 catch (InterruptedException e) 
                    e.printStackTrace();
                
            
        );

        t.start();

        System.out.println("hello main");
        try 
            Thread.sleep(1000);
         catch (InterruptedException e) 
            e.printStackTrace();
        
    



1.5、Thread用法五

还可以使用lambda表达式

public class Demo6 
    public static void main(String[] args) 
        Thread t=  new Thread(()->
            while(true) 
                System.out.println("hello thread");
                try 
                    Thread.sleep(1000);
                 catch (InterruptedException e) 
                    e.printStackTrace();
                
            
        );
        t.start();
        while(true)
            while(true) 
                System.out.println("hello main");
                try 
                    Thread.sleep(1000);
                 catch (InterruptedException e) 
                    e.printStackTrace();
                
            
        
    


使用lambda表达式代替了Runnable 更简洁一些


以上这些用法总结在一起都是一样的

  1. 描述清楚要执行的任务是什么
  2. 把这个任务加到一个Thread实例中,并调用start方法

上面的写法都是调用start引起了run的执行,如果咋们直接调用run会怎么样呢?

public class Demo7 
    public static void main(String[] args) 
        Thread t = new Thread()
            @Override
            public void run() 
                while (true)
                    System.out.println("hello thread");
                    try 
                        Thread.sleep(1000);
                     catch (InterruptedException e) 
                        e.printStackTrace();
                    
                
            
        ;

        //不直接start 直接run
        t.run();

        while (true)
            System.out.println("hello main");

            try 
                Thread.sleep(1000);
             catch (InterruptedException e) 
                e.printStackTrace();
            
        
    

一直打印的是hello thread,没有打印hello main,因为当前run只是一个普通方法的调用,没有创建出新的线程,

当前代码只有一个main这样的主线程,要main线程执行完第一个循环才可以执行后续的代码,

但是第一个循环是死循环,永远执行不到第二个循环了,(不是并发执行了,而是串行执行)

start这个方法创建了新线程,新线程中执行run方法,run方法自身不具备创建线程的能力,仍然在旧的线程中执行。

从Jconsole里看只有main线程,没有Thread-0了

经典面试题:start和run有啥区别?


1.6、多线程的优势-增加运行速度

并发编程到底有什么用呢?能够解决什么问题?

并发编程(多线程)最明显的优势,就是针对“cpu密集型”的程序,能够提高效率

public class Demo8 
    //串行执行
    public static void serial()
        //针对两个整数进行反复的自在增操作
        //通过currentTimeMills可以记录当前的系统时间戳(毫秒级)
        long  beg = System.currentTimeMillis();
        long a  =0;
        for (long i = 0 ; i<10_0000_0000;i++)  //10_0000_0000 (十亿)
            a++;
        

        long b  =0;
        for (long i = 0 ; i<10_0000_0000;i++)
            b++;
        
        long  end = System.currentTimeMillis();
        System.out.println("消耗时间:"+(end - beg) + "ms");
    


    public static void main(String[] args) 
        serial();
    


经过几次运行,时间差不多在640多ms(串行执行)

多线程执行结果:

public class Demo8 
    //串行执行
    public static void serial()
        //针对两个整数进行反复的自在增操作
        //通过currentTimeMills可以记录当前的系统时间戳(毫秒级)
        long  beg = System.currentTimeMillis();
        long a  =0;
        for (long i = 0 ; i<10_0000_0000;i++)
            a++;
        

        long b  =0;
        for (long i = 0 ; i<10_0000_0000;i++)
            b++;
        
        long  end = System.currentTimeMillis();
        System.out.println("消耗时间:"+(end - beg) + "ms");
    

    public static void concurrency() throws InterruptedException 

        //这里的记时要记录两个线程执行的最慢时间(整体执行结束时间)
        long  beg = System.currentTimeMillis();

        Thread t1 = new Thread(()->
            long a  =0;
            for (long i = 0 ; i<10_0000_0000;i++)
                a++;
            
        );

        t1.start();

        Thread t2 = new Thread(()->
            long b  =0;
            for (long i = 0 ; i<10_0000_0000;i++)
                b++;
            
        );

        t2.start();


        //join的效果等待线程结束, t1.join意思就是等到t1执行完了,才会返回继续往下走

        //等上面的两个线程执行完 在往下走进行计时
        t1.join();
        t2.join();
        //因为是并发执行t1 t2 没有执行完毕就到end了 所以要换一种

        long end = System.currentTimeMillis();
        System.out.println("消耗时间:"+(end  - beg) + "ms");
    

    public static void main(String[] args) throws InterruptedException 
        //serial();
        concurrency();
    

可以看见时间要比串行少,注意代码中的注释中 join方法,之后也会继续讲解

在实际开发中,有时候,如果执行的任务时间太长了就可以考虑是否能使用多线程来进行任务拆分,提高执行的速度!!,


二、Thread 类及常见方法

2.1、Thread 的常见构造方法

Thread(String name ) 通过name属性就可以给线程起个名字,方便程序猿来调试.(name对于代码执行没有任何)

public class Demo9 
    public static void main(String[] args) 
        Thread t = new Thread(() -> 
            while (true) 
                System.out.println("hello , thread");
                try 
                    Thread.sleep(1000);
                 catch (InterruptedException e) 
                    e.printStackTrace();
                
            
        , "mythread");

        t.start();

        while (true) 
            System.out.println("hello,main");

            try 
                Thread.sleep(1000);
             catch (InterruptedException e) 
                e.printStackTrace();
            
        

    



可以看见我们取的名字

以上是关于Java遨游在多线程的知识体系中的主要内容,如果未能解决你的问题,请参考以下文章

Java遨游在多线程的知识体系中

Java遨游在多线程的知识体系中

随机类线程安全吗?

为什么 Random.Shared 是线程安全的

Python - 在多线程中使用随机数

java 多线程中synchronized 机制