Java多线程核心技术-基本介绍及常用Api

多线程优点

使用单线程会有什么情况出现呢?在同一时间只能执行一个任务,CPU利用率大幅度降低
使用多线程可以在同一时间内执行更多不同的项目,系统和CPU的运行效率大大提升。使用多线程就是使用异步。

那么什么时候使用多线程呢?

  1. 出现阻塞,系统中出现阻塞线程,可以使用多线程提高效率
  2. 依赖,当两个业务的执行不会互相依赖时可以使用多线程提高效率,如果互相依赖则可以不使用多线程,按顺序进行业务的执行

使用多线程

每当有一个进程正在运行时至少有一个线程在运行
例如我们常见的psvm

public class Test {
    public static void main(String[] args) {
        System.out.println(Thread.currentThread().getName());
    }
}

就会输出main,这里的main并不是main方法名,而是一个名称为main的线程

那么怎么实现多线程编程

  1. 继承Thread类
  2. 实现Runnable接口

通过我们查看Thread的源码可以得知,Thread类实现了Runnable接口,他们之间具有多态的关系
如果我们通过继承Thread来创建新线程时,最大的局限是不支持多继承,因为Java语言的特点是单根继承,所以为了多继承,可以实现Runnable接口,但这两种方式从创建线程的功能是一样的,没有本质区别

public class MyThread extends Thread{
    @Override
    public void run(){
        super.run();
        System.out.println("MyThread");
    }
}
public class Test {
    public static void main(String[] args) {
        MyThread myThread = new MyThread();
        myThread.start();//耗时大
        System.out.println("运行结束");//耗时小
    }
}

上面的例子就使用了start()方法启动一个线程,线程启动后会自动调用线程对象的run方法,run()里面就是线程对象要执行的任务,即是线程执行任务的入口

那么为什么start耗时长呢?
因为执行了四个步骤,其中涉及JVM和操作系统

  1. 通过JVM告诉操作系统创建Thread
  2. 操作系统开辟内存并使用windows SDK的createThread()方法来创建Thread线程对象
  3. 操作系统对Thread进行调度,确定执行时机
  4. Thread被成功执行了

但是线程的执行具有随机性,所以使用多线程技术是,代码的运行结果与代码的执行顺序或调用顺序是无关的

查看线程状态

这里提供三种命令

  • jps 查看java进程+jstack.exe
  • jmc.exe
  • jvisualvm.exe

线程的随机性

下面我举一个例子

package randomness;

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

public class Test {
    public static void main(String[] args) {
        MyThread myThread = new MyThread();
        myThread.setName("MyThread");
        myThread.start();//耗时大
        for(int i = 0;i<10000;i++){
            System.out.println("main="+Thread.currentThread().getName());
        }
    }
}

运行后的结果很明显可以看出是随机输出的,这是因为start方法通知了线程规划器。其实就是让系统安排一个时间来随机调用Thread中的run方法,即让线程执行具体的任务,具有随机顺序执行的效果

多线程随机输出的原因是CPU将时间片分给了不同的线程,线程获得时间片后就执行任务,但并不是创建的线程越多,效率就越高,线程数过多反而会让软件的执行效率降低

  • 执行start的顺序不代表线程启动run的顺序

synchronized

共享数据的情况就是多个线程可以访问同一个变量,例如,实现投票功能时,多个线程同时操作同一个人的票数,但是这种时候很容易出现线程不安全,所以我们要加入synchronized关键字,使多个线程以排队的方式进行处理,称同步

  1. 首先要判断run方法有没有被上锁
  2. 必须等其他线程对run调用结束后才能执行run

停止线程

三种方法:

  1. 使用退出标志使线程正常退出
  2. 停止xiancheng可以使用Thread.stop(),但不建议,虽然它可以停止一个线程,但是这个方法不安全,而且是被弃用的
  3. 使用interrupt()中断线程

调用interrupt()仅仅是在线程中做了一个停止的标记,并不是真正停止线程
但是interrupt()和sleep()一起出现就会异常

线程优先级

  • 优先级越高得到的CPU资源较多,优先级分为1~10,优先级越过界限就会抛出异常
  • 线程优先级具有继承性
  • 优先级高的线程大部分先执行完,即CPU尽量将执行资源让给优先级比较高的线程
  • 线程优先级还有随机性,即线程优先级高的线程不一定先执行完

线程类型

守护线程

典型的守护线程是垃圾回收线程,当非守护线程不存在则守护线程随着JVM自动销毁。守护Daemon线程是为其他提供便利服务,最典型的应用就是GC(垃圾回收器)
凡是setDaemon(true)代码并且传入true值的线程才是守护线程,要在start方法前执行,不然会出现异常

用户线程(非守护线程)

API

currentThread()

查看当前线程

isAlive()

测试线程是否处于活动状态,线程已经启动且尚未终止就是活动状态

sleep(long millis)

在指定的时间(毫秒)内让当前线程休眠

StackTraceElement[] getStackTrace()

返回一个表示该线程堆栈跟踪元素数组
返回的第一个元素代表堆栈顶,它是数组中最新的方法调用。最后一个元素代表堆栈底,是该数组中最旧的方法调用

dumpStack()

将当前线程的堆栈信息输出至标准错误流中

Map<Thread,StackTraceElement[]> getAllStackTraces

返回所有活动线程的堆栈跟踪的一个映射,键是线程,值是一个StackTraceElement数组

getId()

获取线程的唯一标识

interrupted()

判断currentThread()是否已经中断,执行后具有清除状态标志值

this.isInterrupted()

判断this关键字所在类的对象是否中断,不清楚状态标志

stop()

删除线程,但容易造成业务处理的不确定性,太暴力了
调用时会抛出java.lang.ThreadDeath异常,此异常不需要显式地捕捉

interrupt()

停止线程
与return;语句结合使用也能停止线程,较抛异常法在代码结构上可以更加方便的停止线程

suspend()

暂停线程

resume()

恢复线程

suspend()与resume()使用不当,极易容易造成公共同步对象被独占,
其他线程无法访问共同同步对象的结果,这两个方法也是作废的

yeild()

放弃当前的CPU,让其他任务去占用CPU执行时间,有可能刚刚放弃,马上就又获得CPU时间片

setPriorty()

设置线程优先级。

setDaemon(true)

设置守护线程


版权声明:本文为weixin_51438023原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。