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 Files\Java\jdk-11.0.1\bin\java.exe" "-javaagent:C:\Program Files\JetBrains\IntelliJ IDEA 2019.1.3\lib\idea_rt.jar=60024:C:\Program Files\JetBrains\IntelliJ IDEA 2019.1.3\bin" -Dfile.encoding=UTF-8 -classpath C:\learn\java\practice\out\production\threadTest 第一章.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的

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

 

posted @ 2019-07-31 14:52  _Ennio  阅读(716)  评论(0编辑  收藏  举报