一、进程
1、什么是进程
通过任务管理器看到了进程的存在,只有运行的程序才会出现进程
进程:
- 就是正在运行的程序,进程是系统进行资源分配和调用的独立单位。
- 每一个进程都有它自己的内存空间和系统资源。
2、多进程有什么意义?
(1)单进程的计算机只能做一件事,而我们现在的计算机都可以做多件事情。
- 举例:一边玩游戏,一遍听音乐
- 现在的计算机都是支持多进程的,就可以在一个时间段内执行多个任务
- 提高CPU的使用率
二、线程
1、什么是线程
在同一个进程内又可以执行多个任务,而这每一个任务就可以看成是一个线程
- 线程是进程的执行单元(执行路径)。是程序使用cpu的基本单位
- 单线程:程序只有一条执行路径
- 多线程:程序有多条执行路径
2、多线程有什么意义
(1)提高程序的使用率。
- 程序的执行其实都是在抢CPU的资源,cpu的使用权。
- 多个进程是在抢这个资源,而其中的某一个进程如果执行路径比较多,就会有更高的几率抢到cpu的执行权
- 线程的执行有随机性
3、线程的生命周期
(1)新建:创建线程
(2)就绪:有执行资格,没有执行权
(3)运行:有执行资格,有执行权
- 阻塞:由于一些操作让线程处于了该状态。没有执行资格,没有执行权。而另一些操作却可以把它给激活后处于就绪状态
(4)死亡:线程对象变成垃圾,等待被回收
图解:
4、实现多线程的两种方式
(1)继承Thread类
- 自定义类MyThread类继承Thread类
- 在MyThread类中重写run()
- 创建MyThread类的对象
- 启动线程对象
package cn.itcast_01; public class MyThread extends Thread { public void run() { for (int i = 0; i < 100; i++) { System.out.println(getName() + ":" + i); } } }
package cn.itcast_01; public class ThreadDemo { public static void main(String[] args) { MyThread t1 = new MyThread(); MyThread t2 = new MyThread(); t1.setName("线程1"); t2.setName("线程2"); t1.start(); t2.start(); } }
问题:
- 为什么重写run()方法?
run()里面封装的是被线程执行的代码
- 启动线程对象用的是哪个方法?
start()
- run()和start()方法的区别?
run直接调用仅仅是普通方法
start()先启动线程,再由jvm调用run()方法
(2)实现Runnable接口
- 自定义类MyRunnable实现Runnable接口
- 在MyRunnable里面重写run()
- 创建MyRunnable类的对象
- 创建Thread类的对象,并把上一步骤的对象作为构造参数传递
package cn.itcast_02; public class MyRunnable implements Runnable { public void run() { for(int i = 0; i<100;i++){ System.out.println(Thread.currentThread().getName()+":"+i); } } }
package cn.itcast_02; public class MyRunnableDemo { public static void main(String[] args) { MyRunnable my = new MyRunnable(); Thread t1 = new Thread(my, "线程1"); Thread t2 = new Thread(my,"线程2"); t1.start(); t2.start(); } }
5、有了方式1,为什么还要方式2
实现接口的好处
- 可以避免由于Java单继承带来的局限性
- 可以适合多个相同程序的代码去处理同一个资源的情况,把线程同程序的代码,数据有效分离,较好的体现了面向对象的设计思想。
6、线程安全问题
(1)出现的原因
- 是否是多线程环境
- 是否有共享数据
- 是否有多条语句操作共享数据
(2)如何解决线程安全问题
- 同步代码块
synchronized(对象){
需要同步的代码; }
注意:同步可以解决安全问题的根本原因就在那个对象上,该对象如同锁的功能
多个线程同一把锁,锁对象是任意对象
- 同步方法
把同步加在方法上
锁对象:this
- 静态方法
把同步加载方法上
这里的锁对象是当前类的字节码文件对象
7、死锁问题
两个或者两个以上的线程在争夺资源的过程中,发生的一种相互等待的现象
package cn.itcast_02; public class DieLock extends Thread { private boolean flag; public DieLock(boolean flag) { this.flag = flag; } public void run() { if(flag){ synchronized (MyLock.objA){ System.out.println("if objA"); synchronized (MyLock.objB) { System.out.println("if objB"); } } }else{ synchronized (MyLock.objB){ System.out.println("else objB"); synchronized (MyLock.objA) { System.out.println("else objA"); } } } } }
package cn.itcast_02; public class DieLockDemo { public static void main(String[] args) { DieLock dl1 = new DieLock(true); DieLock dl2 = new DieLock(false); dl1.start(); dl2.start(); } }
(1)死锁产生的原因
- 系统资源的竞争:
通常系统中拥有的不可剥夺资源,其数量不足以满足多个进程运行的需要,使得进程在运行过程中,会因争夺资源而陷入僵局,如磁带机,打印机等。
只有对不可剥夺资源的竞争才可能产生死锁,对可剥夺资源的竞争是不会产生死锁的
- 进程推进顺序法
进程在运行过程中,请求和释放资源的顺序不当,也同样会导致死锁
信号量使用不当也会造成死锁。进程间相互等待对方发来的消息,结果也会使得这些进程间无法向前推进。
例如,进程A等待进程B发的消息,进程B又在等待进程A发的消息,可以看出进程A和进程B不是因为竞争同一资源,而是在等待对方的资源导致死锁
(2)产生死锁的四个必要条件:
- 互斥条件:一个资源每次只能被一个进程使用
- 请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放
- 不剥夺条件:进程已获得的资源,在末使用完之前,不能强行剥夺
- 循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系
(3)如何避免死锁
- 加锁顺序(线程按照一定的顺序加锁)
当多个需要相同的一些锁,但是按照不同的顺序加锁,死锁就很容易产生
如果能确保所有的线程都是按照相同的顺序获得锁,那么死锁就不会发生
- 加锁时限(线程尝试获取锁的时候加上一定的时限,并释放自己占有的锁)
若一个线程没有在给定的时限内成功获得所有需要的锁,则会进行回退并释放所有已经获得的锁,然后等待一段随机的时间再重试,这段随机的等待时间让其它线程有机会尝试获取相同的这些锁,并且让该应用在没有获得锁的时候可以继续运行(加锁超时后可以先继续运行干点其它事情,再回头来重复之前加锁的逻辑)
- 死锁检测
主要针对那些不可能实现按序加锁并且锁超时也不可行的场景
每当一个线程获得了锁,会在线程和锁相关的数据结构中(map、graph等等)将其记下。除此之外,每当有线程请求锁,也需要记录在这个数据结构中。
当一个线程请求锁失败时,这个线程可以遍历锁的关系图看看是否有死锁发生
做法:
释放所有锁,回退,并且等待一段随机的时间后重试。这个和简单的加锁超时类似,不一样的是只有死锁已经发生了才回退,而不会是因为加锁的请求超时了。虽然有回退和等待,但是如果有大量的线程竞争同一批锁,它们还是会重复地死锁 (编者注:原因同超时类似,不能从根本上减轻竞争)。
一个更好的方案是给这些线程设置优先级,让一个(或几个)线程回退,剩下的线程就像没发生死锁一样继续保持着它们需要的锁。如果赋予这些线程的优先级是固定不变的,同一批线程总是会拥有更高的优先级。为避免这个问题,可以在死锁发生的时候设置随机的优先级。
8、多线程状态转换图
常见的情况:
- 新建---就绪---运行---死亡
- 新建---就绪---运行---就绪---运行---死亡
- 新建---就绪---运行---其他阻塞---就绪---运行---死亡
- 新建---就绪---运行---同步阻塞---就绪---运行---死亡
- 新建---就绪---运行---等待阻塞---同步阻塞---就绪---运行---死亡
9、线程池
程序启动一个新线程成本是比较高的
使用线程池可以很好的提高性能
线程池里的每一个线程代码结束后,并不会死亡,而是再次回到线程池中成为空闲状态,等待下一个对象来使用
方法一:
public class MyRunnable implements Runnable { @Override public void run() { for(int i = 0; i< 100; i++) System.out.println(Thread.currentThread().getName()+":"+i); } }
/* * 线程池的好处: * 如何实现线程的代码? * A:创建一个线程池对象,控制要创建几个线程对象 * public static ExecutorService newFixedThreadPool(int nThreads) * B:这种线程池的线程可以执行 * 可执行Runnable对象或者Callable对象代表的线程 * 做一个类实现Runnable接口 * C:调用如下方法 * Future<?> submit(Runnable task) * <T> Future<T> submit(Callable<T> task) * */ import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; public class ExecutorsDemo { public static void main(String[] args) { //创建一个线程池对象,控制要创建几个线程对象 ExecutorService pool = Executors.newFixedThreadPool(2); //可以执行Runnable对象或者CAllable对象表示的线程 pool.submit(new MyRunnable()); pool.submit(new MyRunnable()); //结束线程池 pool.shutdown(); } }
方法二:
import java.util.concurrent.Callable; public class MyCallable implements Callable<Integer> { private int number; public MyCallable(int number) { this.number = number; } public Integer call() throws Exception { int sum = 0; for (int x = 1; x <= number; x++) sum += x; return sum; } }
import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; public class CallableDemo { public static void main(String[] args) throws InterruptedException, ExecutionException { ExecutorService pool = Executors.newFixedThreadPool(2); Future<Integer> f1 = pool.submit(new MyCallable(100)); Future<Integer> f2 = pool.submit(new MyCallable(200)); System.out.println(f1.get()); System.out.println(f2.get()); pool.shutdown(); } }