并发:
- 多个不用的软件同时运行
windows/linux操作系统,同时管理多个软件并发执行
eclipse,notepad++,游戏等软件同时运行 - 一个软件可以被多个用户同时请求
多个浏览器用户同时请求淘宝,做结算操作,支付操作,等价于 结算操作和支付操作,在服务端被多次运行
总结
多个软件同时运行,一个软件被运行多次,cpu不停工作,提高cpu的利用率
计算机如果做到并发
前提:
一台计算机,一块cpu,单核
分析cpu:
cpu的某个时间点,只能有一个程序执行
为了能做到并发,把cpu的时间分成若干时间片
时间片划分原则由操作系统来定义
每个小的时间片只能执行一个程序
时间片到了就把程序强制退出cpu
再从内存中选出下一个程序继续运行,以此往复
看似在一段时间内,是多个程序在同时运行
分析软件:
一个软件动辄几百兆或上G,一个软件是不能整个存储到内存中的
进程:
把大的软件分割成多个小的程序段,此程序段可以称为进程
多个大的程序要运行,先把程序分割成多个程序段
然后把每个程序的前一部分的若干程序段加载到内存
最终内存中放置了多个程序的多个程序段
这些程序段要根据优先级排队,cpu从队头获取下一个程序段来执行
大量的程序段/进程,在频繁切换cpu的时候会占用cpu的时间做压栈和弹栈的工作
和需要部分内存存储栈中的数据
总结:
要想做到并发,cpu要划时间片段,程序要划进程,由操作系统来划时间片和程序段
线程:
因为并发导致占用cpu时间和内存,那么就把进程再次划分为多个更小的程序片段,叫做线程thread
其实在cpu上来回切换的是线程,因为所有的线程共享进程的资源
在进程中,包含的一个或多个线程是能够执行的独立的单元
结论:
cpu划片段,程序划分进程段,进程段划分线程段,真正在cpu运行的是线程
尽量提高cpu利用率,减少使用内存
进程还拥有一个私有的虚拟的内存地址,该空间仅能被他包含的线程访问
线程只能归属某一个进程,并且只能访问该进程的所有资源
当操作系统创建了一个进程后,该进程就会自动申请一个名为主线程或者首要线程的线程
同类的多个线程共享一块内存空间和一组资源
线程本身有一个供程序执行堆栈,线程在cpu切换时负荷小,因此线程被称为轻负荷进程,一个进程可以包含多个线程
进程和线程的区别:
一个进程至少有一个线程
线程的划分尺度小于进程,使得多个线程并发性高
进程在指定的过程中拥有独立的内存单元,而多个线程共享进程的内存空间
从而提高了程序的运行效率
线程在执行的过程中与进程区别在于每个独立的线程有一个程序的运行入口,顺序执行序列和程序出口
线程不能独立运行,必须在应用程序中,由应用程序提供对线程的控制
线程的应用场景
线程通常用于一个应用程序中需要同时完成多个任务的情况,我们可以将每个任务定义一个线程,使得它们得以一同工作
也可以用于单一线程中完成,但是使用多线程可以更快的完成所需要的功能
线程的使用
在java中,通过java的api做线程编程
方案一:继承Thread类
/**
* 创建线程方案一
* 继承自Thread类
*/
public class MyThread extends Thread {
public void run(){
System.out.println("线程的入口"+Thread.currentThread().getName());
System.out.println("MyThread1.run()-->线程的功能"+Thread.currentThread().getName());
System.out.println("线程的出口"+Thread.currentThread().getName());
}
public static void main(String[] args) {
//创建一个线程对象
MyThread th1 = new MyThread();
//th1.run();//可以调用run方法,但是不是线程并发,就是调用一个普通的方法
//把th1线程放在内存中,但不一定马上调用run方法
//th1线程获取到cpu则自动调用run方法
th1.run();//不是并发
th1.start();
//创建线程对象,th2和th1是两个线程对象
MyThread th2 = new MyThread();
th2.run();//不是并发
th2.start();
}
}
方案二:实现Runnable接口
/**
* 不是线程类,只是一个接口的子实现
* 就是普通的一个实现类
* 仅用于表达线程的逻辑
* 用于把线程和线程的逻辑分离
*/
public class MyThread2 implements Runnable{
@Override
public void run() {
System.out.println("线程的入口"+Thread.currentThread().getName());
System.out.println("MyThread1.run()-->线程的功能"+Thread.currentThread().getName());
System.out.println("线程的出口"+Thread.currentThread().getName());
}
public static void main(String[] args) {
//此对象不是线程对象
MyThread2 th = new MyThread2();
//创建了一个线程对象t1
Thread t1 = new Thread(th);
t1.start();
//创建了一个线程对象t2
Thread t2 = new Thread(th);
t2.start();
}
}
Thread类:
此类是线程类,其每一个实例对象表示一个可以并发运行的线程,我们可以通过继承此类并重写run方法来定义一个具体的接口,在run方法中,体现的是现成的功能,(有线程的入口,线程的出口),启动线程时,调用线程对象的start方法,而不是直接调用run方法
start方法将线程纳入线程调度,适当前线程可以并发运行
当线程获取到cpu时间片后开始自动运行run方法中的逻辑代码
Runnable接口
实现此接口并重写run方法,来定义线程类,然后再创建线程的时候,将Runnable接口的实例传入并启动线程
这样做的好处:可以将线程和线程要执行的业务逻辑分离,减少耦合,同时java是单继承,定义一个实现Runnable接口,这样做可以更好地去实现其他接口和继承父类
做线程的目的实际上为了做多线程
做多线程的目的为了并发
并发的目的为了提高cpu的利用率
做多线程的两种方式
- 多个线程共享一个run
- 多个线程可以每个线程一个run
java程序员可以写线程,不需要程序员关心线程在哪个进程里
继承Thread类
public class MyThread extends Thread{
public void run(){
//线程的入口
//线程的业务逻辑
//线程的出口
}
}
使用
MyThread mt = new MyThread();
mt.start();//调用重写的run方法,实际上是多态
实现Runnable接口
public class RunLJ implements Runnable{
public void run(){
//线程的入口
//线程的业务逻辑
//线程的出口
}
}
使用
Thread t = new Thread(new RunLJ());
t.start();//执行的是Thread中的run方法,由Thread的run方法调用接口的run
线程的状态
- 创建线程对象 创建态 Thread t = new Thread();
- 创建完的线程对象 就绪态 t.start();//排到线程的队列中,不一定马上获取cpu
- 获取到cpu资源 执行态 执行run方法
- 执行到某一个点时 阻塞态 从cpu退出;//io,sleep,wait等阻塞
- 正常执行完run方法 结束态/消亡态 等待GC回收
线程状态转化:
- 创建态–>就绪态
- 就绪态–>执行态
- 执行态–>阻塞态/挂起
- 执行态–>就绪态
- 阻塞态–>就绪态
- 执行态–>结束态/消亡态
结论
- 根据业务逻辑,要考虑有多少个业务逻辑,就写多少个run
- 每一个run要执行多少次
- 执行每一次run的时候,run是否使用数据的安全性
用内部类创建线程对象
通常我们可以通过匿名内部类的方式创建线程,使用该方式可以简化编写代码的复杂度,当一个线程仅需要一个实例时,我们通常使用如下方式
- 实现类是匿名的,对象有名
Thread t = new Thread(){
public void run(){
system.out.println("run方法");
}
};
t.start();
- 实现类是匿名的,对象也是匿名的
new Thread(){
public void run(){
system.out.println("run方法");
}
}.start();
- 实现类是匿名的,对象不是匿名的,有对象名
Runnable r = new Runnable(){
public void run(){
system.out.println("run方法");
}
};
Thread t = new Thread(r);
t.start();
- 对象是匿名,实现类也是匿名的
new Thread(new Runnable(){
public void run(){
system.out.println("run方法");
}
}).start();
- 实现类是匿名的,对象是有名的,对象名是t
Thread t = new Thread(new Runnable(){
public void run(){
system.out.println("run方法");
}
});
t.start();
创建线程对象:
- 继承自Thread类,重写run方法
优点:在当前的线程类中可以获取run方法并重写,同时在当前线程类中也可以访问Thread类中的方法
缺点:当前线程类不能继承其他的类,java是单继承 - 实现Runnable接口,并重写run方法
优点:当前类可以多实现,还可以继承一次,把线程对象和线程业务逻辑代码分离
缺点:Runnable接口的实现类只有一个run方法,要想使用其他线程的方法,需要Thread t = new Thread(r); - 特殊用法,匿名内部类
优点:写法简单,编码量少
缺点:run的实现只有一次
线程的api
- static Thread currentThread(); 获取当前的线程对象
- long getId(); 获取线程的标识符
- String getName(); 获取线程的名字
- int getPriority(); 获取线程的优先级,优先级有十级 (低)1-10(高)
- boolean isAlive(); 获取当前线程是否为活动状态
- boolean isDaemon(); 获取当前线程是否为守护线程
- boolean isInterrupted() 获取线程是否中断
- static void sleep(long 毫秒); 指定的毫秒数内让当前正在执行的线程休眠,此操作受操作系统计时器和调度程序精度和准确度的影响
守护线程
当进程中只剩下守护线程时,所有的守护线程强制终止
联合线程
void join() 此方法用于等待当前线程结束
t1.join();//t1线程没有结束,当前线程不会结束
join写在谁里面谁等待,join左边是被等待的对象.
public class TestThreadJoinClass {
/**
* 1. 创建线程t1,该线程模拟图片的下载过程
* 该线程输出一个字符串"t1":已下载图片+下载的百分比
* 例如:
* t1:已下载图片10%
* 2. 创建线程t2,该线程输出"t2":等待图片下载
* 然后将t1线程作为t2线程的主线程
* 最后输出"t2:显示图片"
* (此功能必须等待t1线程结束下载完毕后,才可以由t2线程显示图片)
* 3. 启动t1和t2线程
* @param args
*/
public static void main(String[] args) {
//1.创建线程t1,用来下载图片
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("t1:run begin");
for (int i = 1; i <= 20; i++) {
System.out.println("t1线程:"+i*5+"%");
try{
Thread.sleep(500);
}catch (InterruptedException e){
e.printStackTrace();
}
}
System.out.println("t1:run end 图片下载完毕");
}
});
t1.start();
//2.创建一个线程t2
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("t2:等待图片下载完毕 begin");
try{
//t1线程没有结束,t2线程(当前线程)处于等待
//t2线程阻塞block,或者挂起hangup
//等待t1线程结束,t2线程(当前线程)才能继续运行
t1.join();
}catch (InterruptedException e){
e.printStackTrace();
}
System.out.println("t2:显示图片 end");
}
});
t2.start();
}
}
线程的重要的关键点
- 线程何时启动 线程对象.start();是有先后顺序的.有可能某个线程的启动,要靠它的主线程是否启动
- run方法的执行顺序 run并发执行,靠的是何时start()后,何时能获取到cpu
- 可能run并发执行着,需要注意线程之间的依存关系 当前线程是否执行完毕,是依赖另一个线程是否执行完毕.run都执行,但结束是有顺序的(联合线程,join方法)