多线程优点
使用单线程会有什么情况出现呢?在同一时间只能执行一个任务,CPU利用率大幅度降低
使用多线程可以在同一时间内执行更多不同的项目,系统和CPU的运行效率大大提升。使用多线程就是使用异步。
那么什么时候使用多线程呢?
- 出现阻塞,系统中出现阻塞线程,可以使用多线程提高效率
- 依赖,当两个业务的执行不会互相依赖时可以使用多线程提高效率,如果互相依赖则可以不使用多线程,按顺序进行业务的执行
使用多线程
每当有一个进程正在运行时至少有一个线程在运行
例如我们常见的psvm
public class Test {
public static void main(String[] args) {
System.out.println(Thread.currentThread().getName());
}
}
就会输出main,这里的main并不是main方法名,而是一个名称为main的线程
那么怎么实现多线程编程
- 继承Thread类
- 实现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和操作系统
- 通过JVM告诉操作系统创建Thread
- 操作系统开辟内存并使用windows SDK的createThread()方法来创建Thread线程对象
- 操作系统对Thread进行调度,确定执行时机
- 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关键字,使多个线程以排队的方式进行处理,称同步
- 首先要判断run方法有没有被上锁
- 必须等其他线程对run调用结束后才能执行run
停止线程
三种方法:
- 使用退出标志使线程正常退出
- 停止xiancheng可以使用Thread.stop(),但不建议,虽然它可以停止一个线程,但是这个方法不安全,而且是被弃用的
- 使用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)
设置守护线程