多线程
并发与并行
- 并行:指两个或多个事件在同一时刻发生(同时执行)。
- 并发:指两个或多个事件在同一个时间段内发生(交替执行)。
在操作系统中,安装了多个程序,并发指的是在一段时间内宏观上有多个程序同时运行,这在单 CPU 系统中,每一时刻只能有一道程序执行,即微观上这些程序是分时的交替运行,只不过是给人的感觉是同时运行,那是因为分时交替运行的时间是非常短的。
而在多个 CPU 系统中,则这些可以并发执行的程序便可以分配到多个处理器上(CPU),实现多任务并行执行,即利用每个处理器来处理一个可以并发执行的程序,这样多个程序便可以同时执行。目前电脑市场上说的多核 CPU,便是多核处理器,核越多,并行处理的程序越多,能大大的提高电脑运行的效率。
注意:单核处理器的计算机肯定是不能并行的处理多个任务的,只能是多个任务在单个CPU上并发运行。同理,线程也是一样的,从宏观角度上理解线程是并行运行的,但是从微观角度上分析却是串行运行的,即一个线程一个线程的去运行,当系统只有一个CPU时,线程会以某种顺序执行多个线程,我们把这种情况称之为线程调度。
线程与进程
- 进程:是指一个内存中运行的应用程序,每个进程都有一个独立的内存空间,一个应用程序可以同时运行多个进程;进程也是程序的一次执行过程,是系统运行程序的基本单位;系统运行一个程序即是一个进程从创建、运行到消亡的过程。
- 线程:是进程中的一个执行单元,负责当前进程中程序的执行,一个进程中至少有一个线程。一个进程中是可以有多个线程的,这个应用程序也可以称之为多线程程序。
进程
线程
进程与线程的区别
- 进程:有独立的内存空间,进程中的数据存放空间(堆空间和栈空间)是独立的,至少有一个线程。
- 线程:堆空间是共享的,栈空间是独立的,线程消耗的资源比进程小的多。
**注意:**下面内容为了解知识点
1:因为一个进程中的多个线程是并发运行的,那么从微观角度看也是有先后顺序的,哪个线程执行完全取决于 CPU 的调度,程序员是干涉不了的。而这也就造成的多线程的随机性。
2:Java 程序的进程里面至少包含两个线程,主进程也就是 main()方法线程,另外一个是垃圾回收机制线程。每当使用 java 命令执行一个类时,实际上都会启动一个 JVM,每一个 JVM 实际上就是在操作系统中启动了一个线程,java 本身具备了垃圾的收集机制,所以在 Java 运行时至少会启动两个线程。
3:由于创建一个线程的开销比创建一个进程的开销小的多,那么我们在开发多任务运行的时候,通常考虑创建多线程,而不是创建多进程。
线程调度:
-
分时调度
所有线程轮流使用 CPU 的使用权,平均分配每个线程占用 CPU 的时间。
-
抢占式调度
优先让优先级高的线程使用 CPU,如果线程的优先级相同,那么会随机选择一个(线程随机性),Java使用的为抢占式调度。
创建线程类
自定义线程
多线程的开发步骤:
- 自己创建一个类继承Thread,然后重写run方法
- 创建线程的对象
- 调用start方法开启线程
public class Demo01 {
public static void main(String[] args) {
//创建线程对象
MyThread mt = new MyThread();
//开启线程
mt.start();
for (int i = 0; i < 1000; i++) {
System.out.println("小强"+i);
}
}
}
public class MyThread extends Thread {
@Override
public void run() {
//线程的执行体,用来放线程的逻辑
for (int i = 0; i < 1000; i++) {
System.out.println("旺财"+i);
}
}
}
线程的执行流程
主线程执行后期间创建了线程并启动了线程,存在主线程和子线程同时执行。等两个线程都执行完毕,进程执行完毕。
线程的内存原理分析
进程:执行一个进程后,该进程有自己独立的内存空间【包含了栈,堆,方法区】
线程:线程是进程的一部分,是进程中栈内存的一部分,每个线程运行都会有自己独立的栈空间。堆,方法区两个区域是被所有该进程的线程所共享。
线程类的使用
线程的对象的两种创建方式:
线程的创建方式1
public Thread()
:分配一个新的线程对象。public Thread(String name)
:分配一个指定名字的新的线程对象。
以上两种构造方法直接创建线程对象,没有意义。给其子类使用
所以以上两个构造方法要有存在的意义,需要子类去使用。
【代码实践】
public class MyThread extends Thread{
//子类种使用Thread的两个构造方法如下:
public MyThread() {
super(); //public Thread()
}
public MyThread(String name) {
super(name); //public Thread(String name)
}
//自己重写run方法才有存在的意义
@Override
public void run() {
}
}
线程的创建方式2
public Thread(Runnable target)
:分配一个带有指定目标新的线程对象。public Thread(Runnable target,String name)
:分配一个带有指定目标新的线程对象并指定名字。
Runnable 是一个接口,里面有一个抽象方法
void run()
直接定义一个类实现Runnable
/*
当前类不是线程类,而是线程将要执行的任务
*/
public class MyRunnable implements Runnable {
@Override
public void run() {
//线程要执行的逻辑
for (int i = 0; i < 1000; i++) {
System.out.println("小强"+i);
}
}
}
/*
- public Thread(Runnable target):分配一个带有指定目标新的线程对象。
- public Thread(Runnable target,String name):分配一个带有指定目标新的线程对象并指定名字。
*/
public class Demo01 {
public static void main(String[] args) {
//创建线程执行的任务
Runnable target = new MyRunnable();
//创建线程对象
Thread t1 = new Thread(target);
t1.start();
//创建线程对象
Thread t2 = new Thread(target, "小强");
t2.start();
}
}
借助Runnable创建匿名子类
public class Demo02 {
public static void main(String[] args) {
//Runnable匿名子类的方式创建
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
//些线程的逻辑
System.out.println("匿名子类方式1");
}
});
t1.start();
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
//些线程的逻辑
System.out.println("匿名子类方式2");
}
},"线程的名字");
t2.start();
}
}
Thread中常用的方法
public String getName()
:获取当前线程名称。public void start()
:导致此线程开始执行; Java虚拟机调用此线程的run方法。public void run()
:此线程要执行的任务在此处定义代码。public static void sleep(long millis)
:使当前正在执行的线程以指定的毫秒数暂停(暂时停止执行)。public static Thread currentThread()
:返回对当前正在执行的线程对象的引用。
【代码实践】
/*
1. public String getName():获取当前线程名称。
2. public void start():导致此线程开始执行; Java虚拟机调用此线程的run方法。
3. public void run():此线程要执行的任务在此处定义代码。
4. public static void sleep(long millis):使当前正在执行的线程以指定的毫秒数暂停(暂时停止执行)。
5. public static Thread currentThread():返回对当前正在执行的线程对象的引用。
*/
public class Demo01 {
public static void main(String[] args) {
//获取主线程的名字:
//1.先获取主线程的对象
Thread mt = Thread.currentThread();
//2.调用getName方法
String name = mt.getName();
System.out.println("name = " + name);//main
//执行10此for循环,每1s执行一次
for (int i = 0; i < 10; i++) {
System.out.println(i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
线程安全
线程的安全性问题
【代码演示】
public class Demo01 {
public static void main(String[] args) {
Ticket task = new Ticket();//卖票任务
//线程执行的任务是同一个
new Thread(task, "【窗口1】").start();
new Thread(task, "【窗口2】").start();
new Thread(task, "【窗口3】").start();
}
}
/*
模拟卖票任务
*/
public class Ticket implements Runnable {
private int ticketNum = 100;
@Override
public void run() {
Thread t = Thread.currentThread();
String name = t.getName();
//卖票
while (true) {
if (ticketNum > 0) {
System.out.println(name+"::"+ticketNum);
//票卖一张,减一张
ticketNum--;
}
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
同步
线程的同步
线程安全性问题导致的核心原因: 一个线程在操作一个资源时,被其他线程插足。
要解决线程安全问题,就要保证一个线程在操作一个资源的时候,要独享资源。
线程同步方案:
1)同步代码块
2)同步方法
3)锁机制
同步代码块
【格式】
synchronized(同步锁){
需要同步操作的代码
}
同步锁:
- 就是一个任意类型的对象
- 多个线程需要使用相同的锁
【卖票问题的解决】
将操作资源的代码用同步代码块包裹起来了
/*
模拟卖票任务
*/
public class Ticket implements Runnable {
private int ticketNum = 100;
private Object lock = new Object();
@Override
public void run() {
//锁不能在本方法中创建,否则线程会各自有自己的锁
Thread t = Thread.currentThread();
String name = t.getName();
//卖票
while (true) {
synchronized (lock) {//上锁
//被同步的代码,此时只会有一个线程在执行
if (ticketNum > 0) {
System.out.println(name+"::"+ticketNum);
//票卖一张,减一张
ticketNum--;
}
}//释放锁
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
同步方法
【格式】
修饰符 synchronized 返回值类型 方法名(参数列表){
同一时刻只能被一个线程使用
}
【锁对象】
-
如果是静态方法:锁对象就是类的
.Class
对象【类型一旦加载到方法区,就会自己创建一个Class对象】,字节码对象 -
如果非静态方法:锁对象就是
this
对象
【代码实践】
/*
模拟卖票任务
*/
public class Ticket implements Runnable {
private int ticketNum = 100;
@Override
public void run() {
Thread t = Thread.currentThread();
String name = t.getName();
//卖票
while (true) {
sellTicket(name);
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
//被synchronized关键字修饰符的方法,就是同步方法
private synchronized void sellTicket(String name) {
if (ticketNum > 0) {
System.out.println(name+"::"+ticketNum);
//票卖一张,减一张
ticketNum--;
}
}
}
同步锁 Lock
Lock
锁机制:
Lock
是一个接口,用来模拟synchronized
锁定操作
void lock();//获取锁
void unLock();//释放锁
创建对象时可以借助其子类:ReentrantLock
Lock lock = new ReentrantLock();
【使用格式】
lock.lock(); //获取锁 synchronized(锁对象){
需要同步的代码
lock.unLock(); //释放锁 }
【代码实践】
/*
模拟卖票任务
*/
public class Ticket implements Runnable {
private int ticketNum = 100;
Lock LOCK = new ReentrantLock();
@Override
public void run() {
Thread t = Thread.currentThread();
String name = t.getName();
//卖票
while (true) {
LOCK.lock();//获取锁
if (ticketNum > 0) {
System.out.println(name+"::"+ticketNum);
//票卖一张,减一张
ticketNum--;
}
LOCK.unlock();//释放锁
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
线程状态
线程状态分析
线程六种状态
等待和唤醒
等待和唤醒指代的是两种方法,一种是让线程进入等待状态的方法,一种是让线程唤醒其他线程的方法
这些方法都来自于同步锁:
1)等待方法:
public void wait() : 让线程进入无限等待状态,会释放当前线程的锁资源,需要其他线程唤醒
public void wait(long time) : 让线程进入计时等待状态,也会释放锁资源,如果计时结束前有其他线程可以唤醒,过了指定时间后会自动唤醒
2)唤醒方法:
public void notify() : 唤醒其他单个正在等待线程
public void notifyAll(): 唤醒所有在等待的线程
注意:
1)等待和唤醒都要使用同一个锁对象执行
2)这些方法都得放到同步代码中
【无限等待】
/*
无限等待:
1.锁对象调用wait方法
唤醒无限等待的线程:
1.锁对象调用notify/notifyAll
*/
public class Demo01 {
public static void main(String[] args) {
//锁对象
Object LOCK = new Object();
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("进入线程【AAA】");
synchronized (LOCK) {
System.out.println("线程AAA释放锁进入无限等待");
try {
LOCK.wait();//进入无限等待,会释放锁,阻塞
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程AAA获取了锁继续执行");
}
System.out.println("结束线程【AAA】");
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("进入线程【BBB】");
synchronized (LOCK) {
System.out.println("BBB线程唤醒其他线程");
LOCK.notify(); // 不会释放锁
System.out.println("BBB没有释放锁,继续执行");
try {
Thread.sleep(2000); //不会释放锁
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("结束线程【BBB】");
}
}).start();
}
}
【计时等待】
public class Demo01 {
public static void main(String[] args) {
Object LOCK = new Object();
new Thread(new Runnable() {
@Override
public void run() {
synchronized (LOCK) {
System.out.println("进入计时等待2s");
try {
LOCK.wait(2000);//释放锁,2秒后被唤醒重新获取锁
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("计时等待结束,重新获取锁执行");
}
}
}).start();
}
}
等待唤醒案例
吃包子案例
【代码实践】
public class Demo01 {
public static void main(String[] args) {
//定义锁
Object LOCK = new Object();
//定义资源
ArrayList<String> baozi = new ArrayList<>();
new Thread(new Consumer(LOCK,baozi)).start();//消费者线程
new Thread(new Producer(LOCK,baozi)).start();//生产者线程
}
}
生产者
/*
生产者:
定义锁
定义资源
*/
public class Producer implements Runnable{
private Object LOCK ; //锁
private ArrayList<String> baozi;//资源
private int count = 0;
public Producer(Object LOCK, ArrayList<String> baozi) {
this.LOCK = LOCK;
this.baozi = baozi;
}
@Override
public void run() {
//模拟生产包子
while (true) {
//开始同步
synchronized (LOCK) {
if (baozi.size() > 0) {
try {
LOCK.wait(); //包子容器有包子,休息一会儿
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//做包子
count++;
System.out.println("【师傅生产包子】::"+count);
baozi.add(count+"");
//唤醒吃货去吃包子
LOCK.notify();
}
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
消费者
/*
消费者:
定义锁
定义资源
*/
public class Consumer implements Runnable{
private Object LOCK ; //锁
private ArrayList<String> baozi;//资源
public Consumer(Object LOCK, ArrayList<String> baozi) {
this.LOCK = LOCK;
this.baozi = baozi;
}
@Override
public void run() {
//模拟吃包子
while (true) {
//开始同步
synchronized (LOCK) {
if (baozi.size() == 0) {
try {
LOCK.wait(); //包子容器没有包子,休息一会儿
} catch (InterruptedException e) {
e.printStackTrace();
}
}
String baozi = this.baozi.remove(0);
System.out.println("【吃货吃包子】::"+baozi);
//唤醒师傅做包子
LOCK.notify();
}
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}