进程与线程
1.进程
1.1独立性,动态性,并发性
进程就是正在运行的程序,它代表了程序所占用的内存区域,一个进程具有独立的内存空间,是一个正在系统中活动的指令集合,程序加入了时间的概念以后,称为进程,具有自己的生命周期和各种不同的状态,多个进程可以在单个处理器CPU上并发执行,之间不会互相影响。
1.2并行与并发
并行:多个CPU同时处理不同的进程
高并发:一个CPU处理多个进程,抢占资源
在高并发的情景中,尽可能的保证程序的可用性,减少系统不能提供服务的时间
2.线程
2.1线程与进程的关系
线程是操作系统OS能够进行运算调度的最小单位,它被包含在进程之中,是进程中的实际运作单位。一个操作系统中可以有多个进程,一个进程中可以包含一个线程(单线程程序),也可以包含多个线程(多线程程序)。每个线程在共享同一个进程中的内存的同时,又有自己独立的内存空间。
2.2多线程
随机性:线程的随机性指的是同一时刻,只有一个程序在执行。
CPU分时调度:时间片,即CPU分配给各个线程的一个时间段,称作它的时间片,即该线程被允许运行的时间,如果在时间片用完时线程还在执行,那CPU将被剥夺并分配给另一个线程,将当前线程挂起,如果线程在时间片用完之前阻塞或结束,则CPU当即进行切换,从而避免CPU资源浪费,当再次切换到之前挂起的线程,恢复现场,继续执行。
注意:我们无法控制OS选择执行哪些线程,OS底层有自己规则,如:
- FCFS(First Come First Service 先来先服务算法)
- SJS(Short Job Service短服务算法)
2.3线程的状态
- 就绪(可运行)状态:线程已经准备好运行,只要获得CPU,就可立即执行
- 执行(运行)状态:线程已经获得CPU,其程序正在运行的状态
- 阻塞状态:正在运行的线程由于某些事件(I/O请求等)暂时无法执行的状态,即线程执行阻塞
- 创建状态:线程的创建比较复杂,需要先申请PCB,然后为该线程运行分配必须的资源,并将该线程转为就绪状态插入到就绪队列中
- 终止状态:等待OS进行善后处理,最后将PCB清零,并将PCB返回给系统
3.多线程代码创建方式1:继承Thread
public class TestThread1 {
public static void main(String[] args) {
MyThread t = new MyThread();/*new对应线程的新建状态*/
/*如果只是通过两个线程对象调用run(),那么会先执行完一个线程的任务
* 再执行另外一个线程的任务*/
//t.run();
MyThread t2 = new MyThread();
//t2.run();
/*start()对应的状态是线程的就绪状态,什么时候执行,取决于什么时候被OS选中*/
/*当调用start()启动线程时,底层虚拟机会自动调用run()执行我们的业务*/
/*线程执行的效果具有随机性,执行结果不可控
* 具体取决于CPU的调度,时间片的分配*/
t.start();
t2.start();
/*自定义线程名*/
MyThread t3 = new MyThread("中国");
t3.start();
}
}
class MyThread extends Thread {
public MyThread(String name) {
super(name);
}
public MyThread() {
}
@Override
public void run() {
/*自定义线程类的业务需要写在重写的run()里
* super.run()表示调用的是父类的业务*/
//super.run();
for (int i = 0; i < 10; i++) {
/*getName()方法表示可以获取当前正在执行的线程名称*/
System.out.println(i+"="+getName());
}
}
}
4.多线程代码创建方式2:实现Runnable接口
public interface Runnable {
/**
* When an object implementing interface <code>Runnable</code> is used
* to create a thread, starting the thread causes the object's
* <code>run</code> method to be called in that separately executing
* thread.
* <p>
* The general contract of the method <code>run</code> is that it may
* take any action whatsoever.
*
* @see java.lang.Thread#run()
*/
public abstract void run();
}
Runnable接口中只有一个抽象方法run()
public Thread(Runnable target) {
init(null, target, "Thread-" + nextThreadNum(), 0);
}
Thread的构造方法传入Runnable的实现类对象,不直接实例化Thread类的原因是可以对一个对象实现多个线程同时调用。
public class TestThread2 {
public static void main(String[] args) {
/*目标业务对象,只需要创建一次,类似于系统给所有玩家发布了同一个任务target*/
MyRunnable target = new MyRunnable();
//模拟多线程,创建多个线程对象
/*Runnable与MyRunnable都没有start()
* 需要将接口实现类创建的目标业务对象target与多线程类Thread建立关系*/
Thread t1 = new Thread(target);
Thread t2 = new Thread(target);
Thread t3 = new Thread(target);
Thread t4 = new Thread(target);
t1.start();
t2.start();
t3.start();
t4.start();
}
}
/*多线程实现方法二:implements Runnable*/
class MyRunnable implements Runnable {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
/*Runnable接口中,没有提供多余的方法,唯独只有一个抽象的run()
* Thread.currentThread() 获取当前正在执行的线程对象
* Thread是java.lang包下的,可以直接使用
* Thread.currentThread() 是静态方法,所以可以通过类名直接调用
* getName() 获取当前线程对象的名字*/
System.out.println(i+"="+Thread.currentThread().getName());
}
}
}
售票实例(无同步锁)
public class TestThread {
public static void main(String[] args) {
TicketThread t1 = new TicketThread();
TicketThread t2 = new TicketThread();
TicketThread t3 = new TicketThread();
TicketThread t4 = new TicketThread();
t1.start();
t2.start();
t3.start();
t4.start();
}
}
class TicketThread extends Thread {
/*ticket成员变量被所有对象共享,每个对象都会调用100张票*/
//int tickets = 100;//票数
/*静态资源属于类资源,被全局所有对象共享*/
static int tickets = 100;
@Override
public void run() {
while (true) {
try {
/*本方法是让线程休眠的方法,参数为毫秒,此处休眠10ms*/
/*如果线程在休眠后仍然没有问题,才说明线程不会出现问题*/
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(getName()+"="+tickets--);
if (tickets <= 0) {
break;
}
}
}
}
public class TestRunnable {
public static void main(String[] args) {
TicketRunnable target = new TicketRunnable();
Thread t1 = new Thread(target,"霄");
Thread t2 = new Thread(target);
Thread t3 = new Thread(target);
Thread t4 = new Thread(target);
t1.start();
t2.start();
t3.start();
t4.start();
}
}
class TicketRunnable implements Runnable {
/*ticket不需要设置静态,此类只实例化一次*/
int tickets = 100;
@Override
public void run() {
while (true) {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"="+tickets--);
if (tickets <= 0) {
break;
}
}
}
}
这两种方法都存在同一张票重复售出,多售(票数为负)原因在于没有同步锁,多个线程被赋予时间片时,对同一张票进行操作。
比如:对线程一给予2.5个时间片(假设一张票一个时间片),线程一在销售第三张票时被阻塞,导致第三张票没有卖出,而后OS给予线程二2.5个时间片把第三张票卖出,等到了再次给予线程一时间片,第三张票又被卖出。(仅己愚见)
常见情况是由于线程的随机性+访问延迟。