这周的事情有点多了,所以学习进度有点慢。。
并行和并发
- 并行指应用能够同时执行不同的任务,并发指应用能够交替执行不同的任务。
- 并行是指同一时刻同时做多件事情,并发是指同一时间间隔内做多件事情。
- 并行是在不同实体上的多个事件,并发是在同一实体上的多个事件。
- 在一台处理器上“同时”处理多个任务是并行,在多台处理器上同时处理多个任务是并发。
进程
直译:正在进行中的程序;
进程是不直接执行的,它只是在分配该应用程序的内存空间。
一个进程中,不可能没有线程。
线程
就是进程中一个负责程序执行的控制单元(执行路径)。
负责进程中内容执行的一个控制单元,也称之为执行路径,也称之为执行情景。
多线程
一个进程中,可以多执行路径,称之为多线程。
开启多个线程,是为了同时执行多部分代码。
每一个线程都有自己运行的内容,这个内容可以称为线程要执行的任务。
那为什么要用多线程呢?
- 使用线程可以把占据长时间的程序中的任务放到后台去处理
- 用户界面可以更加吸引人,这样比如用户点击了一个按钮去触发某些事件的处理,可以弹出一个进度条来显示处理的进度
- 程序的运行速度可能加快
开发高并发系统的基础,利用好多线程机制可以大大提高系统整体的并发能力以及性能。
多线程的弊端
看上去是多线程同时执行,其实,在某一时刻,只有一个线程在执行,只是切换速度很快。
CPU的切换是随机的。
开的线程多了会造成卡顿,甚至死机,导致效率的降低。
进程与线程的关系
- 一个线程只能属于一个进程,而一个进程可以有多个线程,但至少有一个线程。线程是操作系统可识别的最小执行和调度单位。
- 资源分配给进程,同一进程的所有线程共享该进程的所有资源。 同一进程中的多个线程共享代码段(代码和常量),数据段(全局变量和静态变量),扩展段(堆存储)。但是每个线程拥有自己的栈段,栈段又叫运行时段,用来存放所有局部变量和临时变量。
- 处理机分给线程,即真正在处理机上运行的是线程。
- 线程在执行过程中,需要协作同步。不同进程的线程间要利用消息通信的办法实现同步。
JVM中的多线程解析
虚拟机的启动本身就依赖了多条线程。
至少有两个线程可以分析出来;
1.主线程
该线程的任务代码都定义在主函数中。
2.垃圾回收线程。
该线程的任务代码都在垃圾回收器定义。
例:
class Demo
{
}
class ThreadDemo
{
public static void main(String[] args)
{
new Demo();
new Demo();
new Demo();
System.out.println("Hello world");
}
}
java虚拟机程序在运行的时候,肯定有一条线程是在执行主函数的(主线程),还有回收垃圾的线程(垃圾回收线程)等。
class Demo extends Object
{
public void finalize()
{
System.out.println("Demo ok");
}
}
class ThreadDemo
{
public static void main(String[] args)
{
new Demo();
new Demo();
new Demo();
System.out.println("Hello world");
}
}
运行结果:
Hello world
但是,如果运行垃圾回收器(System类gc()方法)
则:
class Demo extends Object
{
public void finalize()
{
System.out.println("Demo ok");
}
}
class ThreadDemo
{
public static void main(String[] args)
{
new Demo();
new Demo();
System.gc();
new Demo();
System.out.println("Hello world");
}
}
注意:gc();不是立刻就让垃圾回收器启动,只是在告诉垃圾回收器需要启动。
运行结果就成为了:
Hello world
Demo ok
Demo ok
原因:
因为该程序是两个线程完成的,先运行主线程,JVM关闭前运行垃圾回收线程。
也有可能出现其他情况:
例如:
Hello world
Demo ok
或是:
Demo ok
Hello world
原因:虚拟机准备结束的时候,会强制清除它所在的内存区域。
主线程运行
主方法单线程运行
class Demo
{
private String name;
Demo (Srting name)
{
this.name = name;
}
public void show()
{
for(int x = 0; x < 10; x++)
{
for(int y =9999999;y<999999999;y++)
{
}
System.out.println(name+"...x="+x);
}
}
}
class ThreadDemo2
{
public static void main(String[] args)
{
Demo d1 = new Demo("一乘一");
Demo d2 = new Demo("liuxilin");
d1.show();
d2.show();
}
}
运行结果:
一乘一…x=0
一乘一…x=1
一乘一…x=2
一乘一…x=3
一乘一…x=4
一乘一…x=5
一乘一…x=6
一乘一…x=7
一乘一…x=8
一乘一…x=9
liuxilin…x=0
liuxilin…x=1
liuxilin…x=2
liuxilin…x=3
liuxilin…x=4
liuxilin…x=5
liuxilin…x=6
liuxilin…x=7
liuxilin…x=8
liuxilin…x=9
y的for循环让每次输出都有一定的延迟,但是必须d1都输出完才输出d2,这时就需要多线程来让他们同时输出
那么,该如何创建一个路径呢?
线程的实现
在Java中实现多线程有两种手段,一种是继承Thread类,另一种就是实现Runnable接口。
继承Thread类
- 定义一个类,继承Thread类
- 重写Thread类中的 run(); 方法
- 直接创建Thread类的子类对象(创建线程)
- 调用 start(); 方法开启线程,并调用线程的任务run(); 方法
class Demo extends Thread
{
private String name;
Demo (Srting name)
{
this.name = name;
}
public void run()
{
for(int x = 0; x < 10; x++)
{
//currentThread()获取当前运行中线程的引用
System.out.println(name+"...x="+x+"...Thread Name="+Thread.currentThread().getname());
}
}
}
class ThreadDemo
{
public static void main(String[] args)
{
Demo d1 = new Demo("一乘一");
Demo d2 = new Demo("liuxilin");
d1.start();//开启线程,调用run方法
d2.start();//开启线程,调用run方法
//CPU在主线程,d1,d2之间随机高速切换
}
}
实现Runnable接口
- 定义一个类,实现Runnable接口
- 重写接口中的run方法,将线程的任务代码封装到run方法中
- 通过Thread类创建线程对象,将Runnable接口的子类对象作为构造方法的参数进行传递
- 调用线程对象的start(); 方法开启线程
class Demo extends Fu implements Runable
{ //无法继承Thread但是需要多线程,通过接口形式完成
public void run()
{
show();
}
public void show()
{
for(int x = 0; x < 20; x++)
{
System.out.println(Thread.currentThread().getName()+"..."+x);
}
}
}
class ThreadDemo
{
public static void main(String[] args)
{
Demo d = new Demo();
Thread t1 = new Thread(d);
Thread t2 = new Thread(d);
t1.start;
t2.start;
}
}
从程序可以看出,现在的两个线程对象是交错运行的,哪个线程对象抢到了CPU资源,哪个线程就可以运行,所以程序每次的运行结果肯定是不一样的,在线程启动虽然调用的是start()方法,但实际上调用的却是run()方法定义的主体。
第二类方法的好处
- 从Thread类的定义可以清楚的发现,Thread类也是Runnable接口的子类,但在Thread类中并没有完全实现Runnable接口中的run()方法;
- 将线程的任务从线程的子类中分离出来,进行了单独的封装,按照面向对象的思想将任务封装成对象;
- 避免了java单继承的局限性。