在学习线程之前,就要知道什么是线程。
任务调度:
大部分操作系统都是采用时间片轮转的抢断式调度,简单来说就是让任务执行一小段时间后强制暂停去执行另一个任务,任务执行的一小段时间就是时间片。由于cpu的执行效率非常高,时间片非常短,各个任务之间的切换非常快。让人感觉好像这些任务在同时执行。
举个例子:你去和国手去下围棋,而国手同时和包括你在内的100个人同时下棋,你下一步棋需要经过一定的思索,而国手和你们下棋非常的轻松几乎不需要思考。你特别认真的和国手下棋,以为国手只和你一个人下棋,但其实国手同时在和你们100个人下棋。国手相当于cpu,而你们相当于一个个任务,国手和你下棋的一瞬间就是时间片。
进程:
进程是一个具有一定独立功能的程序在一个数据集上的一次动态的执行过程。有自己的生命周期,产生和消亡。每个进程都有自己的内存空间。
线程:
进程中的一个控制单元,负责当前进程中的程序执行,一个进程至少有一个线程,一个进程可以运行多个线程,多个线程可以共享数据。
进程和线程的区别:
根本区别:进程是操作系统资源分配的基本单元,而线程是处理器任务调度的和执行的基本单位。
资源开销:每个进程都有自己独立的代码和空间(程序上下文),程序之间的切换会有较大的开销;线程可以看作轻量级的进程,同一类线程共享代码和数据空间,每个线程都有自己独立的运行栈和程序计数器(PC),线程之间切换的开销小。
包含关系:如果一个进程内有多个线程,则执行的过程不是一条线的,而是多条线(多个线程),共同完成;线程是进程的一部分,可以把线程看作是轻量级的进程。
内存分配:同一进程的线程共享本进程的地址空间和资源,而进程之间的地址空间和资源是相互独立的。
什么是死锁?
所谓死锁,是指多个进程在运行过程中因争夺资源而造成的一种僵局,当进程处于这种僵持状态时,若无外力作用,它们都将无法再向前推进。
死锁产生的4个必要条件
1、互斥: 某种资源一次只允许一个进程访问,即该资源一旦分配给某个进程,其他进程就不能再访问,直到该进程访问结束。
2、占有且等待: 一个进程本身占有资源(一种或多种),同时还有资源未得到满足,正在等待其他进程释放该资源。
3、不可抢占: 别人已经占有了某项资源,你不能因为自己也需要该资源,就去把别人的资源抢过来。
4、循环等待: 存在一个进程链,使得每个进程都占有下一个进程所需的至少一种资源。
当以上四个条件均满足,必然会造成死锁,发生死锁的进程无法进行下去,它们所持有的资源也无法释放。这样会导致CPU的吞吐量下降。所以死锁情况是会浪费系统资源和影响计算机的使用性能的。那么,解决死锁问题就是相当有必要的了。
避免死锁的方法
1、死锁预防 ----- 确保系统永远不会进入死锁状态
产生死锁需要四个条件,那么,只要这四个条件中至少有一个条件得不到满足,就不可能发生死锁了。由于互斥条件是非共享资源所必须的,不仅不能改变,还应加以保证,所以,主要是破坏产生死锁的其他三个条件。
a、破坏“占有且等待”条件
方法1:所有的进程在开始运行之前,必须一次性地申请其在整个运行过程中所需要的全部资源。
优点:简单易实施且安全。
缺点:因为某项资源不满足,进程无法启动,而其他已经满足了的资源也不会得到利用,严重降低了资源的利用率,造成资源浪费。
使进程经常发生饥饿现象。
方法2:该方法是对第一种方法的改进,允许进程只获得运行初期需要的资源,便开始运行,在运行过程中逐步释放掉分配到的已经使用完毕的资源,然后再去请求新的资源。这样的话,资源的利用率会得到提高,也会减少进程的饥饿问题。
b、破坏“不可抢占”条件
当一个已经持有了一些资源的进程在提出新的资源请求没有得到满足时,它必须释放已经保持的所有资源,待以后需要使用的时候再重新申请。这就意味着进程已占有的资源会被短暂地释放或者说是被抢占了。
该种方法实现起来比较复杂,且代价也比较大。释放已经保持的资源很有可能会导致进程之前的工作实效等,反复的申请和释放资源会导致进程的执行被无限的推迟,这不仅会延长进程的周转周期,还会影响系统的吞吐量。
c、破坏“循环等待”条件
可以通过定义资源类型的线性顺序来预防,可将每个资源编号,当一个进程占有编号为i的资源时,那么它下一次申请资源只能申请编号大于i的资源。
以上是有关线程中的一些概念和解释,下面让我们一起来看看创建线程的五种方法
1.继承Thread来创建线程(重写的是父类的Thread的run())
public class Demo01 {
public static void main(String[] args) {
//创建对象
A j=new A();
B w=new B();
//调用线程
j.start();
w.start();
}
}
class A extends Thread{//继承Thread类
public void run(){
//循环输出
for (int i=0;i<10;i++){
try {
//睡眠2秒
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("你是谁");
}
}
}
class B extends Thread{//继承Thread类
public void run(){
for (int i=0;i<10;i++){
try {
//睡眠2秒
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("你猜猜");
}
}
}
运行结果:
运行结果为交替进行输出,你是谁,你猜猜,两秒后继续输出你是谁,你猜猜。由于设定了睡眠两秒后执行所以执行输出你是谁后,两秒后才能继续执行,而这时输出你猜猜执行。
设定这个睡眠时间的作用是为了防止某一个线程一直执行。设定睡眠时间来让这俩个线程可以交替进行。
2.实现Runnable接口,重写run()
public class Demo02 {
public static void main(String[] args) {
T t=new T();
Thread tt=new Thread(t);//将任务放到线程的构造器
tt.start();//启动线程
}
}
class T implements Runnable{
public void run() {
for (int i = 0; i < 10; i++) {
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(".......");
}
}
}
输出结果:
每隔三秒输出一个。。。。。。循环10次
3.匿名内部类
public class Demo03 {
public static void main(String[] args) {
//相当于继承了Thread类,作为子类重写run()实现
new Thread(){
public void run(){
System.out.println("匿名内部类创建方式一");
}
}.start();
//相当于实现了Runnable作为匿名内部类
new Thread(new Runnable(){
public void run(){
System.out.println("匿名内部类创建方法二");
}
}).start();
}
}
运行结果:
4.实现Callable(带返回值的线程)
public class Demo01 {
public static void main(String[] args) {
MyCallable callable=new MyCallable();
FutureTask<String> task=new FutureTask(callable);
task.run();
try {
String o=task.get();
System.out.println(o);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
class MyCallable implements Callable<String>{
public String call()throws Exception{
Thread.sleep(5000);
return "abc";
}
}
运行结果:五秒后输出abc
5.线程池
FixedThreadPool固定大小的线程池
public class Demo04 {
public static void main(String[] args) {
//FixedThreadPool固定大小的线程池
//创建带有5个线程的线程池
ExecutorService ex= Executors.newFixedThreadPool(5);
for (int i=0;i<5;i++){
ex.submit(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
});
}
//销毁线程池
ex.shutdown();
}
}
运行结果
newSingleThreadExecutor:单线程池
public static void main(String[] args) {
//单线程池
ExecutorService executorService= Executors.newSingleThreadExecutor();
for (int i=0;i<5;i++){
executorService.submit(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
});
}
executorService.shutdown();
}
运行结果:
CachedThreadPool:缓存线程池
public class Demo06 {
public static void main(String[] args) {
ExecutorService executorService= Executors.newCachedThreadPool();
for (int i=0;i<5;i++){
executorService.submit(new Runnable(){
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
});
}
executorService.shutdown();
}
}
运行结果:
下一篇讲解线程的一些方法以及线程的工具类。