文章目录
- 前言
- 多线程
- 并发与并行
- 多线程的实现方式
- Thread类
- Runnable接口
- Callable接口和Future接口
- Thread类的相关方法
- 线程对象
- 线程优先级
- 守护线程
- 出让线程/礼让线程
- 插入线程/插队线程
- 线程的相关问题
- 生命周期
- 安全问题
- Lock锁
- 死锁
- 等待唤醒机制(生产者和消费者)
- 通过阻塞队列实现
- 综合练习
- 线程池
前言
博客仅记录个人学习进度和一些查缺补漏。
学习内容:BV17F411T7Ao
多线程
首先需要知道什么是进程什么是线程,详见408《操作系统》
进程是程序执行的实体,是资源调度的基本单位,处理机分配给每一个程序的资源都以进程为整体。
线程是进程中的实际运行单位,也是资源调度的最小单位。
以前的代码都是单线程程序
为了提高CPU的运行效率,应该使用多线程程序
并发与并行
详见408,重点内容
并发 :指多个指令在单个CPU上交替执行
即在一定的时间周期内,多次进行作业调度,执行不同的指令
并行 :指多个指令在多个CPU上同时执行
即在同一时刻上,多个CPU同时执行不同的指令
并发和并行在当前的处理机环境下会同时发生
多线程的实现方式
Thread类
表示java中的一个线程,任何继承该类的子类都可以重写其run方法,提示为多线程程序。
例如:在run中书写要执行的代码,使用start来启动线程
例如:多线程同时启动,指同一个程序(打印helloworld的代码体只有一个)的两个不同线程(t1和t2)同时工作,其中getname是Thread类的内置方法
Runnable接口
实现Runnable接口再重写run方法,本质上是一种描述任务的方法,实际多线程还是通过Thread对象来执行。
例如:先定义一个类,再实现接口,再run中描述具体的任务,Runnable默认是多线程模式,可以直接获取当前线程的对象。
例如:创建Myrun对象,将任务发布,可以通过多个线程对象来同时进行任务并分开结算
Callable接口和Future接口
Callable接口提示该类为描述多线程任务类,再call中描述具体的任务,并且给出返回值,将任务发布给Thread类的对象来完成,并通过Future类的对象来取得call的结果。
例如:任务发布类
例如:找一个工会柜员来管理这个任务的结果,需要注意的是一个柜员可以对应多个完成者的结算,每次接收的都是最后一个完成者(线程)完成的结果
例如:找一个冒险者(线程)来完成这个任务,并通过柜员(Future)来管理结果
Thread类的相关方法
线程对象
例如:查看线程的默认名字
发布任务
对于继承了Thread的类来说,相当于任务实体本身就是可以完成任务的单位(只做固定任务的打工人),属于远征类任务,点一下自己就完成了,不需要额外分配thread类(冒险者)来完成。
此时线程是有自己的默认名字的
例如:如果要进行名字的设置,可以通过构造方法(需要自己重写)也可通过setname成员方法
如果没有手动执行线程,默认线程也是有名字的,即虚拟机默认的main线程
例如:sleep可以手动阻塞线程使其停留一段时间(改时间内会让出CPU,并设置一个计时器,时间到则会从挂起态恢复成就绪态。
线程优先级
抢占式调度:多线程抢夺执行权,执行顺序和执行时间都是不确定的(实际上就是优先级来看的,优先级越高能抢到的总时间占比就越大,当作业到来的时候能优先享用CPU)
非抢占式调度:新作业到达时只能排队,不能抢,大伙轮流依次使用CPU
JAVA中使用了抢占式调度,分为10个优先级
例如:查看默认优先级,包括main线程,java中默认线程优先级都是5
例如:设置优先级
优先级越大,抢占的CPU资源就多,就能更早的执行结束
守护线程
仅仅是为了执行同一个任务的主线程而存在的分线程,来源于同一个任务模块,当被设置成守护线程以后,会为真勇者铺路,真勇者任务结束了,即使自己的任务没有完成,也应该结束。
例如:会陆续结束而不是立即结束
例如此时,传输文件就是聊天的守护线程(分线程)
出让线程/礼让线程
由任务版本身提出限制,执行完一次任务后应当让出CPU,让各个执行任务的实体对象相互分配使用CPU
例如:
插入线程/插队线程
在申明了该方法后,将申明的线程调度到当前进程的前面,可以优先使用CPU
例如在原过程中,自定义线程和main线程并发执行,相互抢夺CPU使用权:
例如:申明了插队以后(在某一个线程内进行插队),可以比当前的进程更快执行
线程的相关问题
生命周期
详见408
在更新了阻塞锁的概念以后,添加了新的生命周期:
安全问题
多线程常常会出现临界资源共享顺序问题,如果进入临界区不上锁,会导致临界资源的出错
例如:将电影票设置为static,成为多线程共享的临界资源
但是因为临界区没有上锁,各线程之间随意进出临界区,导致临界资源一下被这个线程改,一下被那个线程改。
比如此时,还没来得及打印,刚使ticket自增,就被抢夺了执行权
这就引出了临界区和锁的概念,如果能在指定区域上锁,就不会出现类似的问题,但是如果锁的区域太多,就变成了顺序执行的单线程,如果锁的区域太小,无法彻底解决临界资源异常。
例如:锁对象可以是任意的类型的任意对象,但一定要是唯一的,所以使用static
注意,同步代码块的位置很关键,如下会导致先进入同步块的线程一个人就把任务做完了
注意,同一个区块需要使用同一个锁对象,一般使用当前类的字节码文件对象
如果要将一个方法里的所有代码都锁起来,可以直接在方法上加入Synchronized关键字
例如:使用Runnable来发布任务,提供ticket参数供线程对象来使用,构建同步代码块
例如:将买票这一行为抽象成一个方法,提取出来,加上同步修饰
此时锁对象就是Runnable对象的字节码文件,是唯一的。
例如:String Builder是多线程不安全的,多线程的时候应该使用String Buffer类,显然String Buffer类的方法中都是添加了同步锁的
Lock锁
例如:
就是在同步代码块进入区域上锁,出去的区域开锁,但要注意创建出来的锁要具有唯一性,以及一定要有开锁的余地(可以使用finally来执行开锁)
死锁
当不同的锁之间出现了嵌套,两边都在等着对方释放临界资源,就会产生死锁
例如:根据同步代码块的锁对象的不同,产生两把锁
等待唤醒机制(生产者和消费者)
经典PV操作,408必考知识点。
例如:先创建桌子并添加锁变量
例如:在消费者的任务描述页面,要提示具体是哪一把锁前面有人排队,唤醒哪个锁队列中的人
例如:在生产者任务页面,要使用相同的锁对象来进行判断
通过阻塞队列实现
进入临界区时,会判断是否有数据,然后是否挂载到队列中,出临界区时看队列中有没有人,有人就提出来
例如:阻塞队列显然只能有一个,厨师的任务发布页如下,其中构造方法要求一定要传入阻塞队列
注意其中并没有锁,锁写到了队列的put方法中
例如:put源码
例如:客人的构造方法要求一定要传入阻塞队列,并且和对应的厨师保持一致,这样就能从公共队列中获取信息,其中take方法本身带锁
例如:take源码
例如:程序界面先创建共同阻塞队列对象,通过这个队列来创建厨师,需要注意的是,打印语句定义到了锁的外面,并不是临界区,所以会导致数据错误。
综合练习
package com.itheima.demo6;
public class CinemaThread extends Thread{
public static int ticket = 1000;
public CinemaThread(String s) {
this.setName(s);
}
@Override
public void run() {
while (true) {
synchronized (CinemaThread.class) {
if (ticket > 0) {
try {
sleep(3000);
ticket--;
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}else {
break;
}
System.out.println(this.getName() + "卖出了一张票,还剩" + ticket + "张票");
}
}
}
}
package com.itheima.demo6;
public class ThreadTest {
public static void main(String[] args) {
CinemaThread cinemaThread1 = new CinemaThread("窗口1");
CinemaThread cinemaThread2 = new CinemaThread("窗口2");
cinemaThread1.start();
cinemaThread2.start();
}
}
package com.itheima.demo6;
public class GiftSent extends Thread{
public static int giftNum = 100;
public GiftSent(String name) {
this.setName(name);
}
@Override
public void run() {
while (true) {
synchronized (GiftSent.class) {
if (giftNum > 10) {
giftNum--;
}else {
break;
}
System.out.println(this.getName() + "发出了一份礼物,还剩" + giftNum + "个");
}
}
}
}
package com.itheima.demo6;
public class ThreadTest {
public static void main(String[] args) {
GiftSent giftSent1 = new GiftSent("A学生");
GiftSent giftSent2 = new GiftSent("B学生");
giftSent1.start();
giftSent2.start();
}
}
package com.itheima.demo6;
public class OddCount extends Thread{
public static int count = 100;
public OddCount (String name) {
this.setName(name);
}
@Override
public void run() {
while (true){
synchronized (OddCount.class) {
if(count % 2 != 0) {
System.out.println(this.getName() + "找到了一个奇数:" + count);
}
if (count < 1) {
break;
}else {
count--;
}
}
}
}
}
package com.itheima.demo6;
public class ThreadTest {
public static void main(String[] args) {
OddCount oddCount1 = new OddCount("A");
OddCount oddCount2 = new OddCount("B");
oddCount1.start();
oddCount2.start();
}
}
package com.itheima.demo6;
import java.util.Random;
public class RedBag extends Thread{
public static int num = 3;
public static int red = 100;
public RedBag(String name) {
this.setName(name);
}
@Override
public void run(){
synchronized (RedBag.class) {
if (num > 1) {
int t = red;
num--;
red -= new Random().nextInt(red);
System.out.println(this.getName() + "抢到了" + (t - red) + "元");
}else if (num == 1) {
num--;
System.out.println(this.getName() + "抢到了" + red + "元");
}else {
System.out.println(this.getName() + "没抢到");
}
}
}
}
package com.itheima.demo6;
public class ThreadTest {
public static void main(String[] args) {
RedBag redBag1 = new RedBag("A");
RedBag redBag2 = new RedBag("B");
RedBag redBag3 = new RedBag("C");
RedBag redBag4 = new RedBag("D");
RedBag redBag5 = new RedBag("E");
redBag1.start();
redBag2.start();
redBag3.start();
redBag4.start();
redBag5.start();
}
}
package com.itheima.demo6;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Random;
public class Bonus extends Thread{
public static ArrayList<Integer> integerArrayList = new ArrayList<>();
static {
Collections.addAll(integerArrayList, 10, 5, 20, 50, 100, 200, 500, 800, 2, 80, 300, 700);
}
public Bonus(String name) {
this.setName(name);
}
@Override
public void run() {
while (true) {
synchronized (Bonus.class) {
if(integerArrayList.isEmpty()) {
break;
}else {
int t = new Random().nextInt(integerArrayList.size());
System.out.println(this.getName() + "抽到了" + integerArrayList.get(t) + "元");
integerArrayList.remove(t);
}
}
}
}
}
package com.itheima.demo6;
public class ThreadTest {
public static void main(String[] args) {
Bonus bonus1 = new Bonus("抽奖箱1");
Bonus bonus2 = new Bonus("抽奖箱2");
bonus1.start();
bonus2.start();
}
}
package com.itheima.demo6;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Random;
public class Bonus extends Thread{
public static ArrayList<Integer> integerArrayList = new ArrayList<>();
private ArrayList<Integer> integerArrayList1 = new ArrayList<>();
static {
Collections.addAll(integerArrayList, 10, 5, 20, 50, 100, 200, 500, 800, 2, 80, 300, 700);
}
public Bonus(String name) {
this.setName(name);
}
@Override
public void run() {
while (true) {
synchronized (Bonus.class) {
if(integerArrayList.isEmpty()) {
System.out.print(this.getName() + "抽到了" + integerArrayList1.size() + "个奖,分别为:");
String str = "";
int num = 0;
int max = 0;
for (Integer i : integerArrayList1) {
str += i + ",";
num += i;
max = max<=i?i:max;
}
str = str.subSequence(0, str.length() - 1).toString();
System.out.println(str + " 最高" + max +"元" + ",共计" + num + "元");
break;
}else {
int t = new Random().nextInt(integerArrayList.size());
this.integerArrayList1.add(integerArrayList.get(t));
integerArrayList.remove(t);
}
}
}
}
}
package com.itheima.demo6;
public class ThreadTest {
public static void main(String[] args) {
Bonus bonus1 = new Bonus("抽奖箱1");
Bonus bonus2 = new Bonus("抽奖箱2");
bonus1.start();
bonus2.start();
}
}
package com.itheima.demo6;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Random;
public class Bonus extends Thread{
public static ArrayList<Integer> integerArrayList = new ArrayList<>();
private ArrayList<Integer> integerArrayList1 = new ArrayList<>();
private static int join_num = 2;
private static boolean flag = true;
private static int maxBonus = 0;
private static String name1 = "";
static {
Collections.addAll(integerArrayList, 10, 5, 20, 50, 100, 200, 500, 800, 2, 80, 300, 700);
}
public Bonus(String name) {
this.setName(name);
}
@Override
public void run() {
while (true) {
synchronized (Bonus.class) {
if(integerArrayList.isEmpty()) {
System.out.print(this.getName() + "抽到了" + integerArrayList1.size() + "个奖,分别为:");
String str = "";
int num = 0;
int max = 0;
for (Integer i : integerArrayList1) {
str += i + ",";
num += i;
max = max<=i?i:max;
}
str = str.subSequence(0, str.length() - 1).toString();
System.out.println(str + " 最高" + max +"元" + ",共计" + num + "元");
if(maxBonus <= max) {
maxBonus = max;
name1 = this.getName();
}
break;
}else {
int t = new Random().nextInt(integerArrayList.size());
this.integerArrayList1.add(integerArrayList.get(t));
integerArrayList.remove(t);
}
}
}
join_num--;
while (join_num > 0){
try {
sleep(1);
} catch (InterruptedException e) {
throw new RuntimeException(e)
}
}
synchronized (Bonus.class) {
if (flag) {
System.out.println("最高奖" + maxBonus + "元,由" + name1 + "取得");
flag = !flag;
}
}
}
}
package com.itheima.demo6;
public class ThreadTest {
public static void main(String[] args) throws InterruptedException {
Bonus bonus1 = new Bonus("抽奖箱1");
Bonus bonus2 = new Bonus("抽奖箱2");
bonus1.start();
bonus2.start();
}
}
使用第三种有返回值的方法
package com.itheima.demo6;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Random;
import java.util.concurrent.Callable;
public class Bonus implements Callable {
public static ArrayList<Integer> integerArrayList = new ArrayList<>();
private int max = 0;
static {
Collections.addAll(integerArrayList, 10, 5, 20, 50, 100, 200, 500, 800, 2, 80, 300, 700);
}
@Override
public Integer call() throws Exception {
ArrayList<Integer> integerArrayList1 = new ArrayList<>();
String name = Thread.currentThread().getName();
while (true) {
synchronized (Bonus.class) {
if(integerArrayList.isEmpty()) {
System.out.print(name + "抽到了" + integerArrayList1.size() + "个奖,分别为:");
String str = "";
int num = 0;
for (Integer i : integerArrayList1) {
str += i + ",";
num += i;
max = max<=i?i:max;
}
str = str.subSequence(0, str.length() - 1).toString();
System.out.println(str + " 最高" + max +"元" + ",共计" + num + "元");
break;
}else {
int t = new Random().nextInt(integerArrayList.size());
integerArrayList1.add(integerArrayList.get(t));
integerArrayList.remove(t);
}
}
}
return max;
}
}
package com.itheima.demo6;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
public class ThreadTest {
public static void main(String[] args) throws InterruptedException, ExecutionException {
Bonus bonus = new Bonus();
FutureTask<Integer> futureTask1 = new FutureTask<>(bonus);
FutureTask<Integer> futureTask2 = new FutureTask<>(bonus);
Thread bonus1 = new Thread(futureTask1);
Thread bonus2 = new Thread(futureTask2);
bonus1.setName("抽奖箱1");
bonus2.setName("抽奖箱2");
bonus1.start();
bonus2.start();
Integer num1 = futureTask1.get();
Integer num2 = futureTask2.get();
if(num1 > num2) {
System.out.println("抽奖箱1抽到了最大奖" + num1 + "元");
}else {
System.out.println("抽奖箱2抽到了最大奖" + num2 + "元");
}
}
}
线程池
以前通过多线程完成任务的时候,使用完线程就消亡了,每次创建进程都要重新分配资源,浪费很多时间
设置一个线程池,保存可以用来进行任务的线程(工人)
线程池可以理解成特殊的线程集合,任务可以直接发布给这个线程集合。(相当于一个工会)
每次给线程池提交一个任务都会发起一个新的线程来做事。
例如:无上限线程池
例如:有上限线程池,指只有三个线程做事
但是这个工具类不够灵活,每次都要手动排队
当阻塞队伍排满了,核心线程都在忙,才会创建临时线程
如果任务总数超过了核心数、临时数、队伍长度之和,就会触发任务拒绝策略
例如:创建参数详细的线程池
一般使用工具来测试