一、问题的提出
一台计算机为何能够执行多个程序?
它们又是如何去执行多个程序的?
二、CPU 核心数
通常情况下,现金 2000 年后我们购买的 CPU 都是拥有多个核心的(多核处理器),CPU 每一个核心可以分别独立地执行程式指令,利用平行计算的能力加快程式的执行速度。
三、进程与线程
假设计算机的大脑(中央处理器 CPU)只有一个的情况下,我们要让多个程序切换着使用 CPU,由于切换的速度足够快,给人的体验是两个进程在同时运行,通常情况下操作系统就是起到资源调度的作用。
操作系统(英语:Operating System,缩写:OS)就是一组主管并控制计算机操作、运用和运行硬件、软件资源和提供公共服务来组织用户交互的相互关联的系统软件程序,同时也是计算机系统的内核与基石。
每个运行中的程序被称作一个进程
每个进程中又包含了多个线程
进程是对运行时程序的封装,是系统进行资源调度和分配的的基本单位,实现了操作系统的并发;
线程是进程的子任务,是 CPU 调度和分派的基本单位,用于保证程序的实时性,实现进程内部的并发;线程是操作系统可识别的最小执行和调度单位。每个线程都独自占用一个虚拟处理器:独自的寄存器组,指令计数器和处理器状态。每个线程完成不同的任务,但是共享同一地址空间(也就是同样的动态内存,映射文件,目标代码等等),打开的文件队列和其他内核资源。
四、并发与并行
并发: 当有多个线程在操作时,如果系统只有一个 CPU,则它根本不可能真正同时进行一个以上的线程,它只能把 CPU 运行时间划分成若干个时间段,再将时间段分配给各个线程执行(时间片轮转),在一个时间段的线程代码运行时,其它线程处于挂起状。这种方式我们称之为并发(Concurrent)。
并行: 当系统
有一个以上 CPU 时,则线程的操作有可能非并发。当一个 CPU 执行一个线程时,另一个 CPU 可以执行另一个线程,两个线程互不抢占 CPU 资源,可以同时进行,这种方式我们称之为并行(Parallel)。
区别: 并发和并行是即相似又有区别的两个概念,并行是指两个或者多个事件在同一时刻发生;而并发是指两个或多个事件在同一时间间隔内发生。在多道程序环境下,并发性是指在一段时间内宏观上有多个程序在同时运行,但在单处理机系统中,每一时刻却仅能有一道程序执行,故微观上这些程序只能是分时地交替执行。倘若在计算机系统中有多个处理机,则这些可以并发执行的程序便可被分配到多个处理机上,实现并行执行,即利用每个处理机来处理一个可并发执行的程序,这样,多个程序便可以同时执行。
五、 多线程程序含义和作用的提出
创建一个 Thread 类的子类:
package com.google.study.thread;
public class FlemingThread extends Thread{
@Override
public void run() {
while (true) {
System.out.println("FlemingThread......");
}
}
}
创建一个 main 入口:
package com.google.study.thread;
public class ThreadTest {
public static void main(String[] args) {
// start()多开启一个线程,然后自动调用run()
FlemingThread flemingThread = new FlemingThread();
flemingThread.start();
while (true) {
System.out.println("MainTread......");
}
}
}
交替执行 MainTread 与 FlemingTread:
六、多线程的执行过程
七、Runnable
创建一个类继承 Runnable 接口,并重写 run()方法:
package com.google.study.thread;
public class FlemingThreadTwo implements Runnable{
@Override
public void run() {
while (true) {
System.out.println("FlemingThreadTwo......");
}
}
}
创建一个 main 入口:
package com.google.study.thread;
public class ThreadTestTwo {
public static void main (String[] args) {
FlemingThreadTwo flemingThreadTwo = new FlemingThreadTwo();
Thread thread = new Thread(flemingThreadTwo);
thread.start();
while (true) {
System.out.println("MainThread......");
}
}
}
八、简化操作及线程名
更新 Runnable 实现的多线程:
package com.google.study.thread;
public class FlemingThreadTwo implements Runnable{
@Override
public void run() {
while (true) {
System.out.println("FlemingThreadTwo......" + Thread.currentThread().getName());
}
}
}
更新 main 入口:
package com.google.study.thread;
public class ThreadTestTwo {
public static void main (String[] args) {
FlemingThreadTwo flemingThreadTwo = new FlemingThreadTwo();
new Thread(flemingThreadTwo, "fleming_thread_1").start();
while (true) {
System.out.println("MainThread......");
}
}
}
九、多线程编程
一个程序中所有线程对共享资源进行抢占,这种抢占的方式被称为多线程编程。
一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。多线程能满足程序员编写高效率的程序来达到充分利用 CPU 的目的。
十、后台守护线程的提出
新建一个抢鞋线程:
package com.google.study.thread;
public class ShoesThread implements Runnable {
private int shoes = 100;
@Override
public void run() {
while (true) {
if (shoes > 0) {
System.out.println(Thread.currentThread().getName() + "抢到了第" + (shoes--) + "双鞋");
}
}
}
}
新建一个后台守护线程:
package com.google.study.thread;
public class DaemonThread implements Runnable{
@Override
public void run() {
System.out.println("守护线程......");
}
}
新建入口主程序:
package com.google.study.thread;
public class Shoes {
public static void main (String[] args) {
ShoesThread shoesThread = new ShoesThread();
DaemonThread daemonThread = new DaemonThread();
// 后台线程 —— 守护线程
Thread dThread = new Thread(daemonThread);
dThread.setDaemon(true);
dThread.start();
System.out.println(dThread.isDaemon());
// 前台线程
new Thread(shoesThread, "Fleming").start();
new Thread(shoesThread, "Tom").start();
new Thread(shoesThread, "Jack").start();
}
}
运行结果:
十一、匿名内部类创建线程
package com.google.study.thread;
public class Anonymity {
public static void main(String[] args) {
new Thread(new Runnable() {
@Override
public void run() {
while (true) {
System.out.println(Thread.currentThread().getName());
}
}
}).start();
while (true) {
System.out.println("MainThread");
}
}
}
十二、synchronized 的概念和用途
修改共享资源时,为预防线程不同步不安全
导致两个线程进行重复操作而产生错误
每位用户购买商品后都需要及时更新数据库
用以保证线程同步与安全
程序入口:
package com.google.study.thread;
public class ShoesTwo {
public static void main (String[] args) {
ShoesThread shoesThread = new ShoesThread();
new Thread(shoesThread, "1号用户").start();
new Thread(shoesThread, "2号用户").start();
new Thread(shoesThread, "3号用户").start();
}
}
加锁后的抢鞋线程:
package com.google.study.thread;
public class ShoesThread implements Runnable {
private int shoes = 10;
// 锁对象
Object lock = new Object();
@Override
public void run() {
// 加同步锁
while (true) {
synchronized (lock) {
if (shoes > 0) {
// 线程不同步 线程不安全
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() +
"抢到了第" +
(shoes--) +
"双鞋");
}
}
}
}
}
十三、synchronized 同步方法
将同步代码块抽离为单一的同步方法:
package com.google.study.thread;
public class ShoesThread implements Runnable {
private int shoes = 10;
// 锁对象
Object lock = new Object();
@Override
public void run() {
// 加同步锁
while (true) {
shoesCatch();
}
}
// 同步方法
public synchronized void shoesCatch() {
// 同步代码块
if (shoes > 0) {
// 线程不同步 线程不安全
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() +
"抢到了第" +
(shoes--) +
"双鞋");
}
}
}
十四、Lock、ReentrantLock 同步锁
Lock 为接口,ReentrantLock 为其下的实现类:
package com.google.study.thread;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class ShoesThread implements Runnable {
private int shoes = 10;
// 同步锁
Lock renntrantLock = new ReentrantLock();
@Override
public void run() {
while (true) {
// 加同步锁
renntrantLock.lock();
// 同步代码块
if (shoes > 0) {
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() +
"抢到了第" +
(shoes--) +
"双鞋");
}
renntrantLock.unlock();
}
}
}
十五、CPU 线程调度与 Priority 线程优先级
每一个线程的优先使用权都是系统随机分配的,及人人平等,谁先分配到谁先用
同时,我们可以赋予某一个线程拥有至高使用权,及赋予其最高的优先级
操作系统是时间片轮转分配使用权,而在 Java 中通过抢占来获取 CPU 资源
Java 中优先级划分为 1-10,其中 main()主线程 value=5
创建一个高优先级线程:
package com.google.study.thread;
public class MaxPriorityThread implements Runnable{
@Override
public void run() {
for (int i = 0; i <300; i++) {
System.out.println(Thread.currentThread().getName());
}
}
}
创建一个低优先级线程:
package com.google.study.thread;
public class MinPriorityThread implements Runnable{
@Override
public void run() {
for (int i = 0; i <300; i++) {
System.out.println(Thread.currentThread().getName());
}
}
}
对不同线程赋予不同优先级:
package com.google.study.thread;
public class Main {
public static void main(String[] args) {
Thread maxThread = new Thread(new MaxPriorityThread(),"MaxThread");
Thread minThread = new Thread(new MinPriorityThread(),"MinThread");
maxThread.setPriority(Thread.MAX_PRIORITY);
minThread.setPriority(Thread.MIN_PRIORITY);
maxThread.start();
minThread.start();
}
}
十六、join 线程插队
package com.google.study.thread;
public class FlemingThread extends Thread{
@Override
public void run() {
for (int i = 0; i < 3; i++) {
System.out.println(Thread.currentThread().getName());
}
}
}
package com.google.study.thread;
public class Main {
public static void main(String[] args) throws InterruptedException{
FlemingThread flemingThread = new FlemingThread();
Thread thread_1 = new Thread(flemingThread,"1");
thread_1.start();
for (int i = 1; i < 10; i++) {
System.out.println(Thread.currentThread().getName());
if (i == 3) {
thread_1.join();
}
}
}
}
十七、sleep 线程休眠
使用 Thread.sleep()方法让线程休眠一定时间
package com.google.study.thread;
public class FlemingThread extends Thread{
@Override
public void run() {
for (int i = 0; i < 3000; i++) {
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName());
}
}
}
十八、yield 线程让步
package com.google.study.thread;
public class Main {
public static void main(String[] args) throws InterruptedException{
FlemingThread flemingThread = new FlemingThread();
Thread thread_1 = new Thread(flemingThread,"1");
Thread thread_2 = new Thread(flemingThread,"2");
thread_1.start();
thread_2.start();
}
}
package com.google.study.thread;
public class FlemingThread extends Thread {
@Override
public void run() {
for (int i = 1; i < 51; i++) {
if (i % 5 == 0) {
System.out.println(Thread.currentThread().getName() + "\n" + "===线程让步");
Thread.yield();
}
System.out.println(Thread.currentThread().getName());
}
}
}
实际上“===线程让步”这句话输出不符合预期
原因是 CPU 线程让步速度快于屏幕输出信息速度
十九、线程生命周期(线程状态)
共有 6 种线程状态
-
New 新建
new Thread(flemingThread,"1")
-
Runnable 可运行
new Thread(flemingThread,"1").start()
-
Blocked 阻塞
-
Waiting 等待
-
Timed waiting 计时等待
-
Terminated 终止
二十、线程通信
线程调度是赋予不同的优先级,主要依赖于系统
线程通信是一个线程执行完后通知另外的线程执行
wait 与 notify
package com.google;
public class Goods {
public boolean isStatus = false;
}
package com.google;
public class Customer extends Thread {
private Goods goods;
public Customer(Goods goods) {
this.goods = goods;
}
@Overwrite
public void run() {
while (true) {
synchronized (goods) {
// 是否有库存?如果没有,告知生产者。
if (goods.isStatus == false) {
try {
// 没有库存,开始等。
goods.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 假设卖光了.状态为false
goods.isStatus = false;
System.out.println(Thread.currentThread().getName()+"买完了,缺货,唤醒生产者。")
// 通知唤醒其它线程
goods.notify();
}
}
}
}
package com.google;
public class Producer extends Thread {
private Goods goods;
public Producer(Goods goods) {
this.goods = goods;
}
@Overwrite
public void run() {
while (true) {
synchronized (goods) {
// 是否有库存?如果有,告知消费者。
if (goods.isStatus == true) {
try {
// 有库存,开始等。
goods.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 假设卖光了.状态为true
goods.isStatus = true;
System.out.println(Thread.currentThread().getName()+"上货了,唤醒消费者。")
goods.notify();
}
}
}
}
package com.google.study.thread;
public class Main {
public static void main(String[] args) {
Goods goods = new Goods();
new Customer(goods).start();
new Producer(goods).start();
}
}
notifyAll
若有多个其他线程,可以使用 notifyAll 唤醒其他所有线程