zoukankan      html  css  js  c++  java
  • java多线程核心api以及相关概念(一)

    这篇博客总结了对线程核心api以及相关概念的学习,黑体字可以理解为重点,其他的都是我对它的理解

    个人认为这些是学习java多线程的基础,不理解熟悉这些,后面的也不可能学好滴

    目录

    1.什么是线程以及优点

    二,多线程如何使用

    三,线程安全问题,

    四,synchronized执行过程叙述

    五,几个API:

    六,停止线程,暂停线程  

    七,线程的优先级

    八,守护线程

    一,首先搞清楚什么是线程以及他的优点我觉得一句话就就可以说清楚,线程就是一个进程的许多子任务。就比如你打开浏览器之后可能即浏览网页又在下载东西,你虽然你好像是同时做了两件事情,但其实是cpu不停的在两个任务之间切换,只是切换的速度很快,感觉上是同时运行的,另外,线程是异步滴。

    理解这个概念之后,来看线程的优点,都说多线程能够让任务的执行速度变快,我觉得这句话不完全准确,并且也只是其中的一点。

    首先多线程提交执行速度是建立在一个基础上的,多个任务的时间执行时间是不等的,什么意思呢?比如你现在有两个任务,任务一用时10秒,任务二用时1秒,如果你顺序执行这两个任务,那么任务一需要10秒,任务二执行了1秒,但是任务二的等待时间还有10秒,所以看作是执行了11秒,这总共就是21秒的执行时间,但是如果你使用多线程呢?可能是这样子的,先执行任务一1秒,然后切换到任务二,执行任务2一秒,之后再切换回任务一,这样子的话,很明显任务二的等待时间缩短,执行时间也就更短了,相应的也就是我们的执行速度变快了。当然啦只是我自己的理解,如果你执行时间相等的话,其实切换来切换去并没有提交效率,但是这时候还是要使用多线程,为撒子?

    因为多线程的另一个优点就是能够同时运行多个任务,总不可能让浏览器每次只能打开一个窗口吧,很简单,就这两个优点。

    二,多线程如何使用

    1.使用多线程,两种办法,很简单,第一继承Thread类,第二种实现Runnable接口,这两个种其实就是java不支持多继承,使用前者方法的话,你就不能继承其他类了,但是实现Runnable的话还是可以的,并且你也可以实现其他的接口,更为灵活一些,本质上是没有区别的,你打开Thread的源码就会发现他也是实现了Runnable接口的,另外要注意实现Runnable接口的话,一般使用时候都是实例化一个该类,用它来初始化一个Thread类,start这个start类。如下:

     

           MyRunnable m = new MyRunnable ();   //实现了Runnable接口
            Thread m1 = new Thread(m);
            m1.setName("我的线程");
            m1.start();

     

     

     

    2.继承之后,你需要做的就是书写run() 方法以及他的其他可能需要的方法,启动线程时使用Thread.start() ,注意使用了这个方法之后,cpu就会将这个任务加入到执行列当中去,之后根据一定规则分配资源进行调度该任务,调度该任务的意思就是执行该线程当中的 run() 方法。

    3.这里一定要理解一个点,相同优先级的线程之间调度顺序是随机的,和代码顺序无关,这个就不用说了,很简单滴,不相信自己写个程序测试下就行。

    三,线程安全问题,

    我们一直提到线程会不安全什么的,这个不安全是这个意思:首先有一个大前提,无论你有多少个线程,这些线程都是对同一个对象进行操作或者是由同一个线程初始化的,下面这种情况会不安全:

     

    ackage 第一章;

    import javax.swing.plaf.TableHeaderUI;

    class MyRunnable implements Runnable{
    int count = 10;
    public void run(){
    count--;
    System.out.println(Thread.currentThread().getName() + " " + count);
    }
    }
    public class test1{
    public static void main(String[] args){
    MyRunnable m = new MyRunnable();
    Thread m1 = new Thread(m, "A");
    Thread m2 = new Thread(m,"B");
    Thread m3 = new Thread(m,"c");
    Thread m4 = new Thread(m,"D");
    Thread m5 = new Thread(m,"E");
    m1.start();
    m2.start();
    m3.start();
    m4.start();
    m5.start();
    }
    }

     

    输出的结果每一次都是不一样的,我们期望的是count从10减到5并输出每一个数值,但是事实上的话,可能B线程已经获取到了count的值,这时候准备执行减一操作并输出,但是程序跳到A线程,执行了count--操作,B可能本来获取的值是10,之后准备count--,然后输出9,但是在这两部操作之间,count被A线程又给减一了,那么B线程获取到count的值时,就获取到了A线程更改之后的值,8,但是他原本获取到的值是9,。也就是说原本输出9,现在输出了8,这很明显是不安全的。。。。这样说不知道清楚不。输出结果如下:

    "C:Program FilesJavajdk-11.0.1injava.exe" "-javaagent:C:Program FilesJetBrainsIntelliJ IDEA 2019.1.3libidea_rt.jar=60024:C:Program FilesJetBrainsIntelliJ IDEA 2019.1.3in" -Dfile.encoding=UTF-8 -classpath C:learnjavapracticeoutproduction	hreadTest 第一章.test1
    A  8
    B  8
    D  6
    c  5
    E  7
    
    Process finished with exit code 0

    下面这种情况是安全的:

    就是将count变量写在run() 函数内部,让他成为局部变量,这样每一个线程虽然是由同一个对象初始化的,但是他们的count是属于各自的,你无法在A线程里更改B线程的count的值,这时候就是安全的,就这样,不难理解,这里就不放代码啦。

    这里为了解决我们的不安全问题,引出了synchronized概念,

    四,synchronized执行过程叙述

    这个关键字其实就是给一个对象的之中的某个方法上锁,每当一个线程访问该对象的该方法时,他会首先拿到这个锁,然后开始执行里面的代码,之后如果有另一个线程再来执行同一个对象的该方法,他会先尝试拿到锁,拿不到的话就会一直等着,直到锁被释放,再拿到锁执行代码,这样子就解决了安全问题,因为相当于每一个线程进入该方法之后,就是它自己的领地了,对count变量的操作都是以它自己为标准的,其他线程不能在中途更改count的值,直到该线程执行完毕。

    五,几个API:

    1.CurrentThread()   :获取当前线程,当前线程指的是当前代码片段是在哪一个线程上被调用的,比如下面的代码,构造函数MyRunnable()  是在main  线程当中被调用的,但是run()函数却是在线程A中调用的,  另外注意和this,getName()  的区别,后者是针对于当前this对象而言的Name

    class MyRunnable implements Runnable{
        int count = 10;
        MyRunnable(){
            System.out.println(Thread.currentThread().getName());    //输出   main
        }
        synchronized public void run(){
                count--;
                System.out.println(Thread.currentThread().getName() + "  " + count);   //输出  A 
        }
    }
    public class test1{
        public static void main(String[] args){
            MyRunnable m = new MyRunnable();
            Thread m1 = new Thread(m, "A");
            m1.start();
        }
    }

    2.isAlive():当前线程是否处于活动状态,简单地说就是是否start()了并且run()  函数还没有运行完  ,用法:Thread.CurrentThread.isAlive()

    3.sleep():  让当前线程休眠一段时间,单位毫秒,注意这里这个当前线程指的是正在运行的线程,就是当前代码片段处于哪一个线程当中,注意放在try语句中,用法:Thread.sleep()

    4.getId():  获取当前线程唯一标识

    六,停止线程,暂停线程  

    概述:停止线程简单来说就是让cpu放弃当前线程的操作,先去执行其他线程,但是如果使用不当,会有数据不同步,独占锁等问题

    终止线程的方法:

    1.线程run函数调用结束,自动结束

    2.调用  interrupt 函数

     Thread.interrupt() : 这个函数并不是说立刻停止停止线程,让线程停止操作,而是说在当前位置给线程加一个标识,表示该线程是应该停止的,但是并不会立即停止,那如果想要真正的停止该怎么办呢?使用  isTerrupted()和interrupted(),简单来讲,就是使用interrupt()停止线程添加标识之后,再使用相应的函数判断是否存在这个停止标识,存在的话就不执行某一些代码了,这样来达到停止的效果,但是这两个函数是不同的,看下面

    interrupted(): 测试当前线程是否中断,如果中断了,就将中断标识,或者说状态清除掉。简单来讲就是执行完这个函数,线程一定是处于运行状态的

    isInterrupted():  测试线程是否中断,没有清楚中断状态的功能,并且注意判断的并不是当前线程

    理解两点:1,当前线程和线程的区别,2.是否会清除中断状态,

    看下面例子:

    Thread.interrupt() :

     

    class MyRunnable implements Runnable{
        int count = 10;
        MyRunnable(){
            System.out.println(Thread.currentThread().getName());
        }
        public void run(){
            for(int i=0;i<1000;i++)
            {
                System.out.println(i);
            }
        }
    }
    public class test1{
        public static void main(String[] args){
            MyRunnable m = new MyRunnable();
            Thread m1 = new Thread(m, "A");
            m1.start();
            m1.interrupt();                      //注释该行,不注释下一行,输出true,false
            //Thread.currentThread().interrupt();
            System.out.println("测试是否中断1:"+Thread.interrupted());    //输出false
            System.out.println("测试是否中断2:"+Thread.interrupted());   //输出false 
         }
    }

     

    根据运行结果false来讲,我们停止了m1,线程,但是输出为false,因为调用interrupted方法代码处于的线程是main线程,main并没有停止,所以输出false,但是如果将注释去掉,将m1.interrupt()注释掉,会输出true ,false,为什么第二是false?这就是因为该函数具有清除中断状态的作用,第一次执行之后main就不再是中断状态了,所以输出false

    isterrupted()方法:不具有清除状态作用,检测的是线程对象,一个具体的对象,不是当前线程,如下

    package 第一章;
    
    import javax.swing.plaf.TableHeaderUI;
    
    class MyRunnable implements Runnable{
        int count = 10;
        MyRunnable(){
            System.out.println(Thread.currentThread().getName());
        }
        public void run(){
            for(int i=0;i<1000;i++)
            {
                System.out.println(i);
            }
        }
    }
    public class test1{
        public static void main(String[] args){
            MyRunnable m = new MyRunnable();
            Thread m1 = new Thread(m, "A");
            m1.start();
            m1.interrupt();
            //Thread.currentThread().interrupt();
            System.out.println("测试是否中断1:&&&&&&&&&&&&&&&&&&&&&&&"+m1.isInterrupted());  //输出true
            System.out.println("测试是否中断2:&&&&&&&&&&&&&&&&&&&"+m1.isInterrupted());       //输出true
        }
    }

    都输出true,就是因为他们调用的是对象m1的,m1被停止了,所以肯定输出true,不过注意,这个true会输出在任意位置,cpu调用决定的,所以你如果测试的话加点标识找起来方便。

    所以,结合来讲的话,停止线程,run方法可以修改为如下:

    public void run(){
            for(int i=0;i<1000;i++)
            {
                if(Thread.interrupted()){
                    System.out.println("停止啦");
                    break;
                }
                System.out.println(i);
            }
        //如果给这里加上语句,不判断是否停止,他还是会运行的 }

    执行结果,可以看到输出了一些数字之后就停止了

    。。。
    205 206 207 208 209 210 211 212 停止啦 Process finished with exit code 0

    当然这是一种软性的停止,你需要判断他是否停止了,下面介绍一种硬性的停止,抛出异常,很简单,将run 方法改为如下:

        public void run(){
            try{
            for(int i=0;i<1000;i++)
            {
                if(Thread.interrupted()){
                    System.out.println("停止啦");
                    throw new InterruptedException();
                }
                System.out.println(i);     
        }
        //for循环外面有语句也不会执行了,捕捉到异常
        }catch (InterruptedException e){ e.printStackTrace(); } }

    一种情况,在沉睡中停止线程,

    什么意思呢?假设你现在线程中断了,然后你再去调用Thread.sleep()函数让线程休眠,或者线程已经休眠了,你再去调用interrrupt()函数,那就会产生异常,并清除线程的中断状态。这两种情况是对立矛盾的,所以会产生异常,并且因为产生了异常,所以线程一定是被中断的

    比如下面的代码,m1.start()之后,m1线程进行了sleep(),之后在main中又中断了m1线程,这明显是不合理的,所以会输出异常之中的内容

    class MyRunnable implements Runnable{
        int count = 10;
        MyRunnable(){
            System.out.println(Thread.currentThread().getName());
        }
        public void run(){
            try{
            for(int i=0;i<1000;i++)
            {
                System.out.println(i);
                Thread.sleep(1000);
            }}catch (InterruptedException e){
                System.out.println("先休眠再停止");
                e.printStackTrace();
            }
        }
    }
    public class test1{
        public static void main(String[] args){
            MyRunnable m = new MyRunnable();
            Thread m1 = new Thread(m, "A");
            m1.start();
            try {                 //加这个语句是为了让m1的中断执行在睡眠之后
                Thread.sleep(10);
            }catch (InterruptedException e){
                e.printStackTrace();
            }
            m1.interrupt();         
        }
    }

    输出如下:

    main
    0
    先停止再休眠
    java.lang.InterruptedException: sleep interrupted
        at java.base/java.lang.Thread.sleep(Native Method)
        at 第一章.MyRunnable.run(test1.java:15)
        at java.base/java.lang.Thread.run(Thread.java:834)
    
    Process finished with exit code 0

    3.使用stop

    这种方法已经被废除了,会造成很多问题

    最简单的一个问题,数据不同步,假设你去银行,取1000,存1000,然后给你操作时候,这两个操作时在一个方法里面的,并且这个方法是上了锁的。在程序执行完了取1000之后,程序去上了个厕所,休眠了1秒,休眠期间,你使用了stop函数将线程强行停止,释放了对象锁,相当于后面存1000这个操作就没有做了,这很明显是不合理的,数据不同步了。

    4.暂停线程

    暂停线程是说将当前线程的操作暂停,过一会再回来继续执行,注意和中断的区别,它并不会释放对象锁

    suspend()暂停函数,      

    resume()恢复函数

    这两个函数也已经被废除了,因为可能造成独占锁和不同步的问题,废除我们也要知道他为撒子废除了,不想看的朋友可以跳过下面

    独占锁问题:

    不想放太多的代码,放一点,然后用文字说明,更能表达思路

    有这样一个对象,比较简洁,不想写没用的代码,意思就是碰到名字为a的线程,就暂停当前线程

    
    
    class test{
      synchronized public void printStr() { if(Thread.currentThread().getName().equals("a")){
           System.out.println("打印。。。。。"); Thread.currentThread().suspend(); } }
    }
    class MyRunnable implements Runnable{
        int count = 10;
        MyRunnable(){
            System.out.println(Thread.currentThread().getName());
        }
        public void run() {
           //调用test实例的printStr方法
        }
    }

    现在,你初始化了a,b两个线程,使用了一个MyRunnable实例初始化的,然后你先start了a线程,a线程执行了printStr()方法,获取到了test实例的对象锁,然后被suspend了,但是它并没有释放test实例的对象锁,这时候如果b线程开启,他是无法执行printStr()方法的,因为它他拿不到对象锁,只能等着,这是不合理的。而且这里有一个有意思的问题,如果你这里使用了suspend的话,你如果想要在其他线程里面使用System.out.println()  方法,是不可以的,因为out对象的println()方法已经被锁定了。就是说你无法拿到他的锁,使用不了这个方法了,看下面的例子

    class MyThread extends Thread{
        private  int i=0;
        public void run(){
            while(true){
                System.out.println(i);    //占用了println() 方法
                i++;
            }
        }
    }
    public class test1{
        public static void main(String[] args){
             MyThread m = new MyThread();
             m.start();
            try {
                Thread.sleep(10);
            }catch (InterruptedException e){
                e.printStackTrace();
            }
            m.suspend();
            System.out.println("main");       //不会打印
        }
    }

    输出结果就是打印到某一个数字然后停止,永远不会打印  main  ,其实你看一下println()方法源码就明白了:里面的代码被锁住了,a线程独占了这个锁

       public void println(int x) {
            synchronized (this) {
                print(x);
                newLine();
            }
        }

    不同步问题:

    这个我觉得和前面stop的问题是一样的,不说了

    七,线程的优先级

    概述:前面我们看到的main线程还有自己创建的线程,代码执行都是随机的,55开滴,但是现实中线程肯定是有轻重缓急的,所以就有了线程优先级这一个概念,线程优先级高的执行的概率会大一点,但是也不是一定的,只是相对来讲大一点。

    个人感觉这块没撒说的,就几个概念也都很好理解,自己写一些代码看看就行。

    1.setPriotity(int )  设置优先级

    2.getPriotity()   获取优先级

    3.优先级具有继承性:比如你在A线程里启动了B线程,则B线程的优先级是和A线程一样的,除非你手动进行了设置。很简单吧

    4.优先级具有随机性:这个也很理解,优先级高的不一定每一次都比优先级低先执行完,因为cpu在调度的时候肯定是以概率的方式来调度线程的,概率嘛,什么都有可能,只能说样本足够大时,优先级高的一定先执行完。

    八,守护线程

    java里面有两种线程,用户线程和守护线程,用户线程就是我之前定义的那些,当然也包括main线程,守护线程,举一个最简单的例子,java的垃圾回收机制,它是在什么时候回收的呢?你会跟随着用于线程,有没有delete的空间了,就清除掉它,直到所有线程都结束之后,它才会自动销毁。简单来说,就是一直陪伴着用户线程,帮助用户线程做一些事情,用户线程全部结束了,它也就结束了。

    设置方法:thread.setDaemon(true);   默认是false的

    个人觉得可能做一些需要不停的做的事情会用到该线程。

  • 相关阅读:
    快排
    Single Number II
    简单工厂和工厂方法
    Implement strStr()
    Linked List Cycle II
    Linked List Cycle
    适配器模式
    Struts2的ActionContext
    javaScript学习随笔
    Tomcat 基本配置(转)
  • 原文地址:https://www.cnblogs.com/eenio/p/11276264.html
Copyright © 2011-2022 走看看