多线程(multithreading),是指从软件或者硬件上实现多个线程并发执行的技术。具有多线程能力的计算机因有硬件支持而能够在同一时间执行多于一个线程,进而提升整体处理性能。具有这种能力的系统包括对称多处理机、多核心处理器以及芯片级多处理或同时多线程处理器。在一个程序中,这些独立运行的程序片段叫作“线程”(Thread),利用它编程的概念就叫作“多线程处理”。
多线程的优点:
多线程的缺点: 导致程序运行效率降低。
进程:
线程:
因为进程是资源分配的基本单位,线程是程序执行的最小单元。所有我们应该从这两点以及进程与线程之间的健壮性来考虑。
相关概念:
垂直扩展:
提升单机的处理能力。
水平扩展:
集群、分布式都是水平的扩展方案。
具体操作为:
我们以一边看电视、一边吃饭为例、看看多线程情况下如何编写程序?
继承Thread类,重写run()方法(以下是当前线程的执行逻辑)。
class WatchTV extends Thread {
@Override
public void run() {
System.out.println("Watch TV");
}
}
class Eat extends Thread {
@Override
public void run() {
System.out.println("eating");
}
}
public class TestDemo {
public static void main(String[] args) {
Thread watchTV = new WatchTV();
Thread eat = new Eat();
watchTV.start(); //watchTV();
eat.start(); //eat();
}
}
实现Runnable接口,重写run()方法(以下是当前线程的执行逻辑)。
class WatchTV implements Runnable {
@Override
public void run() {
System.out.println("Watch TV");
}
}
class Eat extends implements Runnable {
@Override
public void run() {
System.out.println("eating");
}
}
public class TestDemo {
public static void main(String[] args) {
Thread watchTV = new Thread(new WatchTV());
Thread eat = new Thread(new Eat());
watchTV.start(); //watchTV();
eat.start(); //eat();
}
}
public class TestDemo {
public static void main(String[] args) {
new Thread("WatchTV") {
@Override
public void run() {
System.out.println("Watch TV");
}
}.start();
new Thread("Eat") {
@Override
public void run() {
System.out.println("eating");
}
}.start();
}
}
实现Callable接口,重写call()方法。
//a
class CallableDemo implements Callable<Integer> {
@Override
public Integer call() throws Exception {
int sum = 0;
for (int i = 0; i <= 10000; i++) {
sum += i;
}
return sum;
}
}
public class TestDemo {
public static void main(String[] args) {
/**
* 通过Callable和FutureTask创建线程
* a.创建Callable接口的实现类,同时实现call方法
* b.创建Callable实现类的对象,使用FutureTask包装当前Callable对象,
* FutureTask对象封装Callable对象中call方法的返回
* c.使用FutureTask对象作为Thread的参数创建并且启动线程
* d.调用FutureTask对象的get()来获取子线程执行的结果
*/
//b
Callable<Integer> callableDemo = new CallableDemo();
FutureTask<Integer> task = new FutureTask<>(callableDemo);
//c
Thread thread = new Thread(task);
thread.start();
try {
//d
Integer result = task.get();
System.out.println(result);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
API描述为:The java virtual machine exits when the only threads running are all daemon threads。 即:当JVM总没有一个非守护线程时,JVM进程会退出。
守护线程是一种特殊的线程,就和它的名字一样,它是系统的守护者,在后台默默完成一些系统性的服务,比如垃圾回收线程,JIT线程就可以理解为守护线程。
与守护线程相对的是用户线程(非守护线程),用户线程可以认为是系统的工作线程,它会完成这个程序要完成的业务员操作。
如果用户线程全部结束,则意味着这个程序无事可做。守护线程要守护的对象已经不存在了,那么整个应用程序就应该结束。因此,当一个Java应用内只有守护线程时,Java虚拟机自然退出。即:守护线程能够自动结束生命周期,而非守护则不具备这一特点。
以GC垃圾回收器为例,GC在从我们main()的主线程开始,就在扫描是否有垃圾,有垃圾就回收掉,一直到main()结束,此时JVM结束,所有线程结束。那要是GC不是守护线程时正常线程。首先我们是不知道一个main()主线程是什么时候结束的,所以GC我就得写个死循环,让它一直扫描是否有垃圾吧。那我们想一下,如果此时main()结束了,GC结束么?不结束啊,因为是死循环啊。所以GC这种从头到尾一直提供服务的线程,我们将它置为守护线程。从调用开始运作,一直到主线程结束,自己自动结束。
我们可以通过Thread.setDaemon
设置守护线程。不过需要注意的是守护线程必须在start之前设置,否则会报错。
例如:我们可以创建一个子线程,并让他持续进行睡眠,如下:
Thread thread = new Thread() {
@Override
public void run() {
try {
while (true) {
TimeUnit.MILLISECONDS.sleep(1);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
此时该线程为用户线程,即非守护线程,那么当主线程结束时,该程序并不会自动结束。
此时,如果想当主线程结束时,子线程自动结束,那么就可以将子线程设为守护线程,完整代码如下:
public class TestDemo {
public static void main(String[] args) {
Thread thread = new Thread() {
@Override
public void run() {
try {
while (true) {
TimeUnit.MILLISECONDS.sleep(1);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
//将子线程变为守护线程
thread.setDaemon(true);//在线程启动之前去调用
thread.start();
try {
TimeUnit.MILLISECONDS.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Main Thread Finished");
}
}
我们通过Thread源码发现,Java的线程生命周期有6种状态。
NEW(新建状态)
:new Thread()
创建线程对象。RUNNABLE(就绪状态)
:线程对象此时调用start()
方法,此时在JVM进程中创建了一个线程,线程并不是一经创建就直接得到执行,需要等待操作系统的其他资源,比如:处理器。BLOCKED(阻塞状态)
:等到一个监视器锁进入到同步代码块或者同步方法中,代码块/方法某一个时刻只允许一个线程去执行,其他线程只能等待,这种情况下等待的线程会从RUNNABLE状态
转换到BLOCKED状态
;例如Object.wait()
方法。WAITING(等待状态)
:调用Object.wait()/join()/LockSupport.park()
等方法,此时线程从RUNNABLE状态
转换到WAITING状态
。TIMED_WAITING(睡眠状态)
:调用带超时参数的Thread.sleep(long millis)/Object.wait(long timeout)/join(long millis)/LockSupport.parkNanos()/LockSupport.parkUntil
等方法都会使得当前线程进入到TIMED_WAITING状态
。TERMINATED(终止状态)
: 是线程的最终状态。
JVM crash
。现在我们就画一副图来理解一下线程生命周期的6种状态。
用来启动一个线程 将其添加一个线程组当中 此时线程就会处于Runnable就绪状态。
public class TestDemo {
public static void main(String[] args) {
new Thread(){
@Override
public void run() {
try {
Thread.sleep(5000);
System.out.println("子线程运行结束");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}.start();
}
}
sleep方法使得当前线程指定毫秒级的休眠,暂停执行,不会放弃monitor锁的使用权。
TimeUnit.HOURS.sleep(3); //睡眠3个小时
TimeUnit.MINUTES.sleep(27); //睡眠27分钟
TimeUnit.SECONDS.sleep(8); //睡眠8秒种
public class TestDemo {
public static void main(String[] args) {
new Thread(){
@Override
public void run() {
try {
Thread.sleep(5000);
System.out.println("子线程运行结束");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}.start();
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("主线程运行结束");
}
}
属于启发式的方法。线程A.yield()
会提醒调度器线程A愿意放弃本次的cpu资源,如果cpu资源不紧张,处理器有可能会忽略这种提示。
public class TestDemo {
public static void main(String[] args) {
new Thread(){
@Override
public void run() {
while (true){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}.yield();
try {
Thread.sleep(1000);
System.out.println("Main Thread Finished");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
含义:thread B中调用threadA.join(),此时thread B进入到等待状态,直到当前threadA结束自己的生命周期或者达到join方法的超时时间。
线程A自己调用A.join()方法没有任何用处。
//t1先输出0~9, t2再输出0~9,主线程最后输出0~9
public class TestDemo {
public static void main(String[] args) {
Thread t1 = new Thread("t1"){
@Override
public void run() {
for(int i=0; i<10; i++){
System.out.println("thread: "+Thread.currentThread().getName()+", "+i);
}
try {
TimeUnit.MILLISECONDS.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
Thread t2 = new Thread("t2"){
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println("thread: " + Thread.currentThread().getName() + ", " + i);
}
try {
TimeUnit.MILLISECONDS.sleep(100);
} catch (InterruptedException e) {
}
}
};
try {
t1.start();
t1.join();
t2.start();
t2.join();
for(int i=0; i<10; i++){
System.out.println("thread: "+Thread.currentThread().getName()+", "+i);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
interrupt()
:将java线程当中的中断状态位置为true
。
sleep()/join()/wait
方法后都会使得当前线程进入阻塞状态,而如果另外一个线程调用被阻塞线程的interrupt
方法(例如:在thread B :中调用:thread A对象.interrupt()
方法)会打断当前的这种阻塞状态,并抛出一个InterruptedException
的异常,这样的方法称之为可中断方法。interrupt()
方法并不是结束当前被阻塞线程的生命周期,只是打断了当前线程的阻塞状态。public class TestDemo {
public static void main(String[] args) {
Thread thread = new Thread(){
@Override
public void run() {
try {
TimeUnit.SECONDS.sleep(5);
} catch (InterruptedException e) {
System.out.println("I am be interrupted");
}
}
};
thread.start();
try {
TimeUnit.MILLISECONDS.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
thread.interrupt();
}
}
isInterrupted()
:判断中断状态位是否位true
。
interrupted()
:判断中断状态位是否为true
。
interrupted
方法调用之后会擦除掉线程的interrupt
标识。public class TestDemo {
public static void main(String[] args) {
Thread thread = new Thread() {
@Override
public void run() {
while (true) {
System.out.println(Thread.interrupted());
}
}
};
thread.setDaemon(true);
thread.start();
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
thread.interrupt();
// System.out.println(thread.isInterrupted());
// thread.interrupt();
// System.out.println(thread.isInterrupted());
}
}
首先这三个方法都是存在于Object
类中的方法。
wait
方法调用在synchronized
同步代码块或方法当中,使得当前线程进入阻塞状态。
而notify/notifyAll
方法能够唤醒当前线程的阻塞状态。
每个线程都有优先级,优先级默认为5,可设置的范围为1~10。并且Java线程优先级继承于创建它的线程。
//线程的优先级的相关方法:
setPriority(int grade); //设置当前线程的优先级
getPriority(); //获取当前线程的优先级
//设置当前线程的优先级
public final void setPriority(int newPriority) {
ThreadGroup g;
checkAccess();
if (newPriority > MAX_PRIORITY || newPriority < MIN_PRIORITY) {
throw new IllegalArgumentException();
}
if((g = getThreadGroup()) != null) {
if (newPriority > g.getMaxPriority()) {
newPriority = g.getMaxPriority();
}
setPriority0(priority = newPriority);
}
}
ThreadGroup
用于对一批线程进行统一管理。需要注意的是,优先级只会增加抢到CPU执行权的概率,而并不是优先级大的执行完再执行优先级小的。