一、线程概念
- 进程:程序运行资源分配的最小单位,每个进程都有自己独立的代码和数据空间,操作系统为进程分配各种资源。
- 线程:CPU调度的最小单位,也叫轻量级进程,每个线程都有各自的堆栈、计数器和局部变量等属性。
- 线程和进程关系:线程依赖于进程而存在,多个线程共享进程的内存空间。
异步和同步:
- 同步:在同一个线程中执行一段业务逻辑时,按顺序执行,在前面的结果没有返回时,后面的程序就不能往下执行,必须等待前一个结果返回时后面的才能往下执行。
- 异步:多线程是实现异步的一个手段,异步是当一个请求发送给被调用者,调用者可以不用等待结果返回而可以做其他的事情。
就像我们每天去到公司一样,先打开电脑,在电脑开机过程中可以去接点水喝,而不用等待电脑开机再去接水喝。
并发和并行:
- 并行:同一时刻可以处理事情的能力,比如一台四核的电脑,可以同时运行四个任务,我们就说这台电脑并行度是4
- 并发:在单位时间内可以处理的事情,主要还是看这台电脑时间分片的长短,如果这台电脑的时间分片为100ms,在1s内就可以处理10个任务,那么就说这台电脑的并发度是40。因为任务是在交替执行的,并发的任务就会让我们以为这些任务是同时执行的,其实还是顺序执行的。
二、线程的优势
现如今服务器多采用多处理器,CPU的基本调度单位是线程,如果一个程序只有一个线程的话,那么就只能发挥一个CPU的作用,其他CPU的资源将会闲置,这在很大程度上浪费了CPU的资源,如果能够多个CPU同时发挥作用,在设计正确的情况下,可以通过提高CPU资源的利用率来提高系统的吞吐率。
三、线程状态
通过Thread.state 可以查看线程的状态:
- NEW:新建状态。这种状态下线程还没有开始,也就是还没有调用start方法
- RUNNABLE:可运行状态。这个状态下的线程可能处于执行阶段,但是也有可能在等待来自操作系统的其他资源,例如等待CPU为其分配时间片。ready和running在这两种状态在Java中统称为RUNNABLE,分开写是为了更好的理解
- BLOCKED:阻塞状态。表示线程阻塞于锁
- WAITING:等待状态。进入等待状态的线程需要其他线程做出一些特定动作,例如通知或中断
- TIMED_WAITING:超时等待状态。该状态不同于WAITING状态,它可以在指定时间内自动返回。
- TERMINATED:终止状态。表示当前线程已经执行结束
t.start()之后并不代表线程已经启动,此时它只是在可运行池中,随时等待被CPU调度,一旦获取到CPU时间片才真正的处于可运行状态。
线程的优先级:
1、线程有1-10,10个优先级选择,最小优先级是1(MIN_PRIORITY),默认优先级5(NORM_PRIORITY),
最大优先级是10(MAX_PRIORITY)
代码语言:javascript复制/**
* The minimum priority that a thread can have.
*/
public final static int MIN_PRIORITY = 1;
/**
* The default priority that is assigned to a thread.
*/
public final static int NORM_PRIORITY = 5;
/**
* The maximum priority that a thread can have.
*/
public final static int MAX_PRIORITY = 10;
2、如果没有指定线程的优先级,默认是普通优先级,也就是NORM_PRIORITY
3、通过t.setPriority(int newPriority)为线程设定优先级
注意:不要试图通过设定优先级来指定线程的启动顺序,优先级的设定只是说明这个线程在CPU调度时有更大的概率被选中,而且这种优先级设定有不确定性,在某些操作系统中可能优先级的设定并不起作用。
方法介绍:
- join():在线程A中调用B的join()方法,线程B将会先于线程A执行,线程A必须等待线程B执行完成才能执行,有点类似于插队。同时join()方法还有两个重载的带有时间的方法,join(long millis)、join(long millis, int nanos),它表示如果线程B没有在指定时间内完成,则返回。相当于给插队线程加了时间限制,超时自动返回。
- yield():Thread的静态方法。当前线程让出CPU执行权,当前线程会从运行状态变为可运行状态,重新回到可运行线程池,但是他还是会可能被再次选中执行。
- sleep(long millis):Thread的静态方法,使当前线程t休眠n毫秒,如果当前线程t持有锁,休眠期间不会释放锁,其他线程将无法获得锁,当其他线程调用t.interrupt()唤醒休眠线程。当到了休眠时间当前线程t会自动进入RUNNABLE状态。
- wait():对象方法,使当前线程进入等待状态,它不会返回,除非我们调用t.notify()或者t.notifyAll()方法唤醒,唤醒之后将和其他线程竞争获得锁。调用wait()方法的前提是当前线程必须持有锁,否则会抛异常,在调用wait()方法之后会释放锁,让其他线程有机会获得锁。
sleep()和wait()的区别:
1、sleep()是Thread的静态方法,wait()是Object的方法。
2、当调用sleep()的线程获得锁时不会自动释放锁,调用wait()会释放锁。
3、sleep()方法需要其他线程调用当前线程的interrupt()方法或者时间过期才能唤醒,wait()方法需要调用t.notify()或者t.notifyAll()方法唤醒。
线程的中断:
如果线程执行完成或者抛出未处理的异常,线程就会终止。
线程中断的方法有stop()、resume()、suspend()和interrupt(),但是前三个已经废弃了,stop()会使线程不正确释放资源,resume()只是为了suspend()而存在,这两个方法会导致死锁。所以最后只剩下interrupt()了。
java线程是协作式的,意思就是当调用当前线程t的interrupt()时,当前线程t并不会立马终止,而是跟当前线程打个招呼,“兄弟,你死期到了,我跟你说一下,你爱死不死”,同时会把t的中断标志设置为true。当线程抛出InterruptedException时,线程中断标志将会清除,调用静态方法Thread.interrupted()可以判断线程是否中断,同时将中断标志将被清除并设置为false。
实例如下:
主函数
代码语言:javascript复制public static void main(String[] args) throws InterruptedException {
//创建两个线程
InterruptThread interruptThread = new InterruptThread();
interruptThread.setName("thread1");
InterruptThread2 interruptThread2 = new InterruptThread2();
interruptThread2.setName("thread2");
//启动两个线程
interruptThread.start();
interruptThread2.start();
//主线程休眠5秒 使两个线程充分运行
TimeUnit.SECONDS.sleep(5);
//中断InterruptThread
interruptThread.interrupt();
}
静态内部线程1:
代码语言:javascript复制private static class InterruptThread extends Thread{
@Override
public void run() {
interrupt(); //将中断标志设置为true
System.err.println("before thread1 interrupt status:" isInterrupted());
while (true){
try {
sleep(6000);
} catch (InterruptedException e) {
System.err.println("exception thread1 interrupt status:" isInterrupted());
}
}
}
}
静态内部线程2:
代码语言:javascript复制private static class InterruptThread2 extends Thread{
@Override
public void run() {
long start = System.currentTimeMillis();
while (true){
if (System.currentTimeMillis() - start > 2000){
interrupt(); //将中断标志设置为true
System.out.println("before thread2 interrupt status:" isInterrupted());
break;
}
}
Thread.interrupted(); //清除中断标志,置为false
System.out.println("after thread2 interrupt status:" isInterrupted());
}
}
1、在InterruptThread 中先把中断标志设置为true,并打印是否中断,在主线程中再次将处于休眠状态中的InterruptThread 再次中断,此时会抛出异常,再次打印是否中断。在这个类中我们要证明的是,抛出InterruptedException 线程中断标志会被清除。
2、在InterruptThread2 中我们让InterruptThread2 正常运行2s,并在循环中将中断标志置为true,正常退出循环时,调用Thread.interrupted(); 清除中断标志,并打印是否中断。在这个类中我们要证明调用当前线程Thread.interrupted();会将中断标志清除。
在这两个类中证明了,java线程中断是协作式的,将中断标志置为true并不会立马就中断线程。
打印结果:
代码语言:javascript复制before thread1 interrupt status:true
exception thread1 interrupt status:false
before thread2 interrupt status:true
after thread2 interrupt status:false
exception thread1 interrupt status:false
四、创建线程
创建线程有三种方法:
- 继承Thread类
public class CreateThread {
public static void main(String[] args) {
ExtendThread extendThread = new ExtendThread();
extendThread.start();
}
private static class ExtendThread extends Thread{
@Override
public void run() {
System.out.println("class extends thread start");
}
}
}
- 实现Runable接口
public class CreateThread {
public static void main(String[] args) {
Thread thread = new Thread(new ImplRunnable());
thread.start();
}
private static class ImplRunnable implements Runnable{
@Override
public void run() {
System.out.println("class implements Runnable start");
}
}
}
- 实现Callable接口
Callable是可以获取到返回值, 但是只能通过线程池来调用,它返回的是一个Future对象f,通过f.get()就能获取到结果。
代码语言:javascript复制public class CreateThread {
private static ExecutorService executorService = Executors.newFixedThreadPool(4);
public static void main(String[] args) {
Future<String> submit = executorService.submit(new Callable<String>() {
@Override
public String call() throws Exception {
return "success";
}
});
try {
String s = submit.get();
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
}
}
能力一般,水平有限,如有错误,请多指出。