一、程序、进程、线程
1、区别
(1)程序是一段静态的代码,为应用程序执行的蓝本。
(2)进程为程序的一次动态执行过程,包括代码的加载、执行以及执行完毕的一个完整过程。
(3)线程是进程中的一个执行单元,一个进程在执行过程中可以产生多个线程(至少有一个线程 )。
2、关系
(1)进程负责的是应用程序的空间的标识,线程负责的是应用程序的执行顺序。
(2)进程拥有一个包含了某些资源的内存区域,多个线程间共享进程的内存。
(3)线程的中断与恢复相比于进程可以节省系统的开销。
(4)进程是资源分配的基本单位,线程是调度和执行的基本单位。
3、为什么使用线程
(1)使用多线程可以减少程序的响应时间。(把耗时的线程让一个单独线程去解决)
(2)线程创建与切换的开销比进程小。
(3)在能运行多线程的机器上运行单线程,会造成资源的浪费。
(4)多线程能简化程序结构,使其便于理解。
二、多线程
1、多线程指的是一个进程中同时存在几个执行体(线程),按照不同的执行顺序共同工作的现象。
2、多线程并不是同时发生,系统在任何时刻只能执行一个线程,只是java虚拟机快速的将控制从一个线程切换到另一个线程(多个线程轮流执行),造成同时发生的错觉。
3、每个java程序都有一个缺省的主进程,当JVM启动时,发现main方法后,会启动一个主线程(main线程),用于执行main方法。若main方法中没有创建其他线程,那么当main执行完最后一个语句时,JVM会结束java应用程序。若main方法中创建了其他进程,那么JVM会等到所有线程结束后再结束java应用程序。
4、同步与异步:
(1)同步就是指一个线程要等待上一个线程执行完之后才开始执行当前的线程。异步指的是一个线程的执行不需要在意其他线程的执行。
(2)同步要解决的问题是当多个线程访问同一个资源时,多个线程间需要以某种顺序来确保该资源在某时刻只被一个线程使用,解决办法是获取线程对象的锁,得到锁,则这个线程进入临界区(访问互斥资源的代码块),且锁未释放前,其他线程不能进入此临界区。但同步机制会带来巨大的系统开销,甚至死锁,所以要尽量避免无谓的同步。
(3)简单的讲,同步就是A喊B去吃饭,如果B听到,就和A一起去吃饭,若B没听到,则A就一直喊,直到B听到,然后在一起去吃饭。 而异步是A喊B去吃饭,然后A自己去吃饭,不管B接下来的动作,B可能与A一起吃饭,也可能过了几个小时再去吃饭。
(4)说的直白点,同步就是执行有先后顺序,A执行完B再执行,异步就是各干各的,A执行一部分,B执行一部分,A与B间没有联系。
5、并行与并发:
(1)并行:多个cpu实例或者多台机器同时执行一段处理逻辑,是真正的同时执行。
(2)并发:通过cpu调度算法,让各线程快速切换。使程序看上去是同时执行的,
6、线程安全与不安全:
(1)线程安全:指在并发条件下,代码经过多线程的调用,各线程的调度顺序 不影响代码执行结果。
(2)线程不安全:即指线程的调度顺序影响代码执行结果。
7、Java的内存模型(JMM):
(1)并发程序中,确保数据访问的一致性以及安全性非常重要。在了解并行机制的前提下,并定义一种规则,保证多个线程间可以有效地、正确地协同工作,从而产生了JMM。
(2)Java内存模型规范了Java虚拟机与计算机内存是如何协同工作的。JMM的核心均围绕多线程的原子性、可见性、有序性来建立的。
(3)Java内存模型规定了如何和何时可以看到由其他线程修改过后的共享变量的值,以及在必须时如何同步的访问共享变量。
8、原子性、可见性、有序性:
(1)原子性:指的是一个操作是不可中断的。可以理解为一个操作要么执行成功,要么不执行。
(2)可见性:指的是一个线程修改了某一个共享变量的值,则其他线程能立即知道这个修改。
(3)有序性:并发执行时,程序经过 指令重排后(提高cpu处理性能),程序的执行可能会乱序。通过指定 指令重排规则,使程序的逻辑有序,即不改变代码逻辑。
注:指令重排:执行代码的顺序与编写代码的顺序不一致,即虚拟机优化代码,改变代码顺序。
9、线程的run()与start()方法的区别:
(1)系统通过调用start()方法来启动一个线程,此时该线程处于就绪状态,需要等JVM调度,然后调用run()方法,线程才进入运行状态。即调用start()方法是一个异步调用的过程。
(2)若直接调用run()方法,则相当于调用一个普通方法,不能实现多线程。即调用run()方法是一个同步的过程。
举例:
System.out.println("1");
Thread thread= new Thread(newRunnable() {
@Overridepublic voidrun() {
System.out.println("2");
}
});
thread.start();//启动线程,等待JVM调度,不一定会立即执行。
System.out.println("3");
此时,异步,输出1,3, 2System.out.println("1");
Thread thread= new Thread(newRunnable() {
@Overridepublic voidrun() {
System.out.println("2");
}
});
thread.run();//不启动线程,立即执行。
System.out.println("3");
此时,同步,按顺序执行,输出1,2, 3
三、线程的生命周期
1、java使用Thread类及其子类的对象来表示线程。
2、线程的生命周期通常为 新建状态,就绪状态,运行状态,阻塞状态,死亡状态。
3、新建状态:
当Thread类及其子类的对象被声明并创建后,新线程处于新建状态,此时该线程有了相应的内存空间和其他资源。
4、就绪状态:
调用Thread类的start()方法,用于启动线程,使线程进入线程队列排队等待JVM调度。
5、运行状态:
线程被创建后,就具备了运行条件。但该线程仅仅占有内存空间,JVM管理的线程中还没有这个线程。需要调用start()方法(从父类继承的方法),通知JVM有新的线程等待切换。切换成功后,该线程会脱离创建他的主线程,并开始新的生命周期。如果此线程是Thread的子类创建的,由于Thread类中的run()方法没有具体内容,所以其子类必须重写run()方法(run()方法包含线程运行的代码)。
6、阻塞状态(三种):
如果一个线程执行了sleep(睡眠)、suspend(挂起,不推荐使用)等方法,失去所占用资源之后,该线程就从运行状态进入阻塞状态。在睡眠时间已到或获得设备资源后可以重新进入就绪状态。
(1)等待阻塞状态:
运行状态中的线程执行 wait() 方法,使线程进入到等待阻塞状态。等待阻塞状态不会主动进入线程队列排队等待,需要由其他线程调用notify()方法通知它,才能进入线程队列排队等待。
(2)同步阻塞状态:
线程在获取 synchronized 同步锁失败(因为同步锁被其他线程占用)。
(3)其他阻塞状态:
通过调用线程的 sleep() 或 join() 发出了 I/O 请求时,线程就会进入到阻塞状态。当sleep() 状态超时,join() 等待线程终止或超时,或者 I/O 处理完毕,线程重新转入就绪状态。
7、死亡状态(两种):
死亡状态指线程释放了实体,即释放分配给线程对象的内存。
(1)正常运行的线程完成run()方法。
(2)线程被强制结束run()方法。
四、多线程的创建
1、 方法一:继承Thread(不常用),由子类复写run方法。
使用子类创建优缺点:可以在子类中拓展新的成员变量与新方法,使线程具备某种特性与功能。但不能再扩展其他类,因为java不支持多继承。即单继承局限性。
步骤:
1、定义类去继承Thread类;
2、复写run方法,将线程运行的代码写在run方法中。
3、通过创建Thread类的子类对象(实现多态),创建线程对象。
4、调用线程的start方法,开启线程,并执行run方法。
【定义】class MyThread extendsThread{
@Overridepublic voidrun() {
}
}
【执行】
Thread myThread1= newMyThread();
myThread1.start();
【举例】class Demo extendsThread {
@Overridepublic voidrun() {
System.out.println("1");
}
}public classTest {public static voidmain(String[] args) {
Thread demo= newDemo();
demo.start();
}
}
2、方法二:实现一个接口Runnable(常用)
实现Runnable接口避免单继承的局限性。
步骤:
1、定义类实现Runnable接口。
2、覆盖接口中的run方法,封装线程要运行的代码。
3、通过Thread类创建对象。
4、将实现了Runnable接口的子类对象作为实际参数传递给Thread类中的构造函数。
5、调用Thread对象的start方法,开启线程,执行接口中的run方法。
【定义】class MyRunnable implementsRunnable{
@Overridepublic voidrun() {
}
}
【执行】
Runnable myRunnable= newMyRunnable();
Thread myThread1= newThread(myRunnable);
myThread1.start();
【举例】public classTest {public static voidmain(String[] args) {
Thread thread= new Thread(newRunnable() {
@Overridepublic voidrun() {
System.out.println("1");
}
});
thread.start();
}
}
3、方法三:通过Callable和Future创建线程
前两种的缺点:在执行完任务之后无法获取执行结果。如果需要获取执行结果,就必须通过共享变量或者使用线程通信的方式来达到效果,这样使用起来就比较麻烦。
自从Java 1.5开始,就提供了Callable和Future接口,通过它们可以在任务执行完毕之后得到任务执行结果。
创建Callable接口的实现类,并实现call()方法。并使用FutureTask类(实现了Runnable,Future接口)来包装Callable实现类的对象,且以此FutureTask对象作为Thread对象的target来创建线程。通过FutureTask类的get()方法可以获取处理结果。
【定义】class MyCallable implements Callable{
@Overridepublic Integer call() throwsException {return null;
}
}
【执行】
Callable myCallable = newMyCallable();
FutureTask ft = new FutureTask(myCallable);
Thread myThread1= newThread(ft);
myThread1.start();
【举例】importjava.util.concurrent.Callable;importjava.util.concurrent.ExecutionException;importjava.util.concurrent.FutureTask;class Demo implements Callable{
@Overridepublic Integer call() throw