java多线程(待补充)
一、线程与进程、并发和并行
进程:每个进程都有独立的代码和数据空间(进程上下文),进程间的切换会有较大的开销,一个进程包含1–n个线程。(进程是资源分配的最小单位)
线程:同一类线程共享代码和数据空间,每个线程有独立的运行栈和程序计数器(PC),线程切换开销小。(线程是cpu调度的最小单位)
进程是操作系统资源分配的基本单位,而线程是处理器任务调度和执行的基本单位。
一个进程至少有一个线程,一个进程可以运行多个线程,多个线程可共享数据。Java 程序是多线程程序,每启动一个Java程序至少我们知道的都会包含一个主线程和一个垃圾回收线程。
而且启动的时候,每条线程可以并行执行不同的任务。
– 并行、并发、串行
并行:前提是在多核CPU,多个线程同时被多个CPU执行,同时执行的线程并不会抢占CPU资源。
串行:前提是在单核CPU条件下,单线程程序执行,不能同时执行,也不能去切换执行,也就是在同一时间段只能做一件事,如果需要做多件事情需要排队执行。
并发:前提是多线程条件下,多个线程抢占一个CPU资源,多个线程被交替执行。因为CPU运算速度很快,所以用户感觉不到线程切换的卡顿。
无论并行、并发,都可以有多个线程执行,如果是多个线程抢占一个CPU就成了并发,多个CPU同时执行多个线程就是并行。
二、多线程的实现方法
2.1 继承Thread类重写run()方法:
class ExtendThread extends Thread { // 继承自Threadprivate String name;public ExtendThread(String name) {this.name = name;}@Overridepublic void run() { // 必须重写run方法,并且将线程任务放到run里执行for (int i = 0; i < 5; i++) {System.out.println(name + i);try {Thread.sleep(999);} catch (InterruptedException e) {e.printStackTrace();}}}
}public class Example {public static void main(String[] args) {ExtendThread t1 = new ExtendThread("线程A");ExtendThread t2 = new ExtendThread("线程B");t1.start();t2.start();}
}
2.2 实现Runnable接口并创建Thread对象(常用)
class MyRunnable implements Runnable { // 必须要实现Runnable接口private String name;public MyRunnable(String name) {this.name = name;}public void run() { // 必须要有run方法,并且将需要执行的任务放到run方法里for (int i = 0; i < 5; i++) {System.out.println(name + i);try {Thread.sleep(400);} catch (InterruptedException e) {e.printStackTrace();}}}
}public class Example {public static void main(String[] args) {MyRunnable run1 = new MyRunnable("线程A");MyRunnable run2 = new MyRunnable("线程B");Thread t1 = new Thread(run1);Thread t2 = new Thread(run2);t1.start();t2.start();}
}
2.3 实现Callable接口,通过FutureTask包装器来创建Thread线程
前面2种方式都有一个缺陷:在执行完任务之后无法获取执行结果。如果需要获取执行结果,就必须通过共享变量或者使用线程通信的方式来达到效果,这样使用起来就比较麻烦。而自从Java 1.5开始,就提供了Callable和Future,通过它们可以在任务执行完毕之后得到任务执行结果。
Callable和Future一个产生结果,一个拿到结果。 Callable接口类似于Runnable,从名字就可以看出来了,但是Runnable不会返回结果,并且无法抛出返回结果的异常,因而Callable功能更强些,被线程执行后,可以返回值,这个返回值可以被Future拿到,也就是说,Future可以拿到异步执行任务的返回值。Callable位于java.util.concurrent包下,和Runnable一样,它也是一个接口,在它里面也只声明了一个方法call(),这是一个泛型接口,call()函数返回的类型就是传递进来的V类型。
public interface Callable<V> {V call() throws Exception;
}public class SomeCallable<V> extends OtherClass implements Callable<V> {@Overridepublic V call() throws Exception {// TODO Auto-generated method stubreturn null;}
}Callable<V> oneCallable = new SomeCallable<V>(); // 由Callable<Integer>创建一个FutureTask<Integer>对象:
FutureTask<V> oneTask = new FutureTask<V>(oneCallable); // 注释:FutureTask<Integer>是一个包装器,它通过接受Callable<Integer>来创建,
// 它同时实现了Future和Runnable接口。
// 由FutureTask<Integer>创建一个Thread对象:
Thread oneThread = new Thread(oneTask);
oneThread.start();
//至此,一个线程就创建完成了。
//Callable要采用ExecutorSevice的submit方法提交,而不是execute方法,因为execute方法没有返回值,在ExecutorService接口中有若干个submit方法的重载版本。<T> Future<T> submit(Callable<T> task);
<T> Future<T> submit(Runnable task, T result);
Future<?> submit(Runnable task);
//第一个submit方法里面的参数类型就是Callable。一般情况下我们使用第一个submit方法和第三个submit方法,第二个submit方法很少使用。//Future是一个接口,它可以对Callable任务的执行结果进行操作。可以说Future提供了三种功能:判断任务是否完成;能够中断任务;能够获取任务执行结果。使用ExecutorService、Callable、Future实现有返回结果的线程
Future中的方法
cancel()方法用来取消任务,如果取消任务成功则返回true,如果取消任务失败则返回false,参数mayInterrupt表示是否允许取消正在执行却没有执行完毕的任务,如果设置true,则表示可以取消正在执行过程中的任务。如果任务已经完成,则无论mayInterrupt为true还是false,此方法肯定返回false,即如果取消已经完成的任务会返回false;如果任务正在执行,若mayInterrupt设置为true,则返回true,若mayInterrupt设置为false,则返回false;如果任务还没有执行,则无论mayInterrupt为true还是false,肯定返回true。
isCancelled()方法表示任务是否被取消成功,如果在任务正常完成前被取消成功,则返回 true
isDone()方法表示任务是否已经完成,若任务完成,则返回true;
get()方法用来获取执行结果,这个方法会产生阻塞,会一直等到任务执行完毕才返回;
get(long timeout, TimeUnit unit)用来获取执行结果,如果在指定时间内,还没获取到结果,就直接返回null
Callable与Runnable的具体应用
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;public class AtomicIntegerFieldUpdaterTest {public static <T> void main(String[] args) {ExecutorService newFixedThreadPool = Executors.newSingleThreadExecutor();Future<String> submit = newFixedThreadPool.submit(new Callable<String>() {@Overridepublic String call() throws Exception {// TODO Auto-generated method stubreturn "我是执行的结果";}});try {System.out.println("我要获取结果结果了:"+submit.get());} catch (InterruptedException e) {// TODO Auto-generated catch blocke.printStackTrace();} catch (ExecutionException e) {// TODO Auto-generated catch blocke.printStackTrace();}}}
2.4 线程池
1:降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消耗。
2:提高响应速度。当任务到达时,任务可以不需要等到线程创建就能立即执行。
3:提高线程的可管理性。线程是稀缺资源,如果无限制地创建,不仅会消耗系统资源,
还会降低系统的稳定性,使用线程池可以进行统一分配、调优和监控。但是,要做到合理利用 线程池,必须深入了解其实现原理。
newCachedThreadPool创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。线程池的规模不存在限制。
newFixedThreadPool 创建一个固定长度线程池,可控制线程最大并发数,超出的线程会在队列中等待。
newScheduledThreadPool 创建一个固定长度线程池,支持定时及周期性任务执行。
newSingleThreadExecutor 创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。
newCachedThreadPool:创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。
2.5总结
继承Thread的类,不适合资源共享。实现了Runable接口的话,则很容易的实现资源共享。
总结:
实现Runnable接口比继承Thread类所具有的优势:
1):适合多个相同的程序代码的线程去处理同一个资源
2):可以避免java中的单继承的限制
3):增加程序的健壮性,代码可以被多个线程共享,代码和数据独立
4):线程池只能放入实现Runable或callable类线程,不能直接放入继承Thread的类
三、线程安全
定义:线程安全是编程中的术语,指某个函数、函数库在并发环境中被调用时,能够正确地处理多个线程之间的共享变量,使程序功能正确完成。
3.1产生线程不安全的原因
(1)线程之间都是抢占式执行的,导致两个线程里面操作的先后顺序无法确定,这是操作系统内核实现的,我们也无法改变。这样的随机性,就是导致线程安全问题的根本所在。
(2)多个线程修改同一个变量
一个线程修改同一个变量、多个线程读取同一个变量、多个线程修改不同的变量是不会出现线程安全问题的。
(3)原子性
因为多个线程访问共享资源时没加限制,A访问的时候,B也可以访问,不满足原子性,我们可以给加上锁来保证原子性。
(4)内存可见性
在多线程的情况下,一个线程修改了共享变量的值,另一个线程却没有拿到最新的值
所谓的可见性就是一个线程修改了共享变量的值,能够及时地被其他线程看到
(5)有序性
有序性是指,编译过程中,JVM调用本地接口,CPU执行指令过程中,指令的有序性.一段代码是这样的进行编写的:
- 老师发布作业 (到学生)
- 学生一天完成作业并提交 (到老师)
- 老师收到作业 (到老师) 我们按照这个顺序编写成功并执行了代码,但是JVM在执行的过程中,并不是按照这个顺序进行执行的,因为这样的效率很低,所以CPU进行了指令的重排序,重排序之后代码是按照1–>3–>2的顺序进行执行的,但是这个代码的逻辑不发生改变,也就是重排序前和重排序后的代码运行的结果是一样的,但提高了程序的效率.
这样进行了指令的重排序,在单线程的情况下是一定正确的,但是在多线程的情况下就未必是正确的