一、什么是多线程
1.1 什么是进程?
任务管理器里面运行的这些都是进程。
1.2 而线程:

1.3 单线程&多线程

单程序是一段时间之内只能执行一个线程,而不能几个线程来回切换执行。只能听歌或只能聊天
多线程可以几个线程来回切换进行。比如边听歌边聊天边上网。

1.4 并发和并行


一个CPU在多个线程之间来回切换执行,因为执行速度非常快,在人所能看到的宏观层面就是同时执行这些线程的,但其实是同一时间只能执行一个线程,是交替执行的。

也可以多个CPU同时实现并发和并行

二、实现多线程的三种方式

2.1 继承Thread类

代码示例

只要是Thread类的,都要用start方法启动

该方法,创建的类继承了Thread类,那就是线程,所以可以调用thread类的所有方法。在调用start方法之后,会调用类的run方法,此时可以标注线程的名字来区分执行的是哪个线程。
2.2 实现Runnable接口
类中的run方法的方法体是线程执行的内容,但thread类的对象才是线程对象,才能调用线程的方法。
代码示例:

多线程代码

ps:这里有处错误:只用创建一次mr对象即可,创建多个执行任务相同的线程的时候,thread的参数都是mr。
1)一个cpu在执行线程时,肯定知道改执行线程的名称name,所以在run方法里面直接调用Thread类的静态方法currentThread来获取线程。
2)再调用该线程对象的名字即可达到区别目的
2.3 实现Callable接口

代码示例

2.4 总结三个方法

三、多线程的常用成员方法

idea中执行的所有程序,所有方法,都是由一个个的线程来执行的,所以每个方法都有一个线程来执行它。
3.1 getName() 、setName()、CurrentThread()、sleep()
main代码
java
package com.Threadexe;
public class Threadexe02 {
public static void main(String[] args) throws InterruptedException {
//用构造方法设置线程名字
MyThread t1=new MyThread("飞机");
MyThread t2=new MyThread("坦克");
//哪条线程执行了这个方法,此时获取的就是哪条线城的对象---此时是main线程
Thread thread = Thread.currentThread();
System.out.println(thread.getName());
t1.start();
t2.start();
Thread.sleep(5000);
System.out.println("h------------------------");
}
}
定义的MyThread类
java
package com.Threadexe;
public class MyThread extends Thread{
public MyThread() {
}
public MyThread(String name) {
super(name);
}
@Override
public void run(){
//书写线程要执行的代码
for (int i = 0; i < 100; i++) {
//此时currentThread就是t1或t2执行的。
System.out.println(Thread.currentThread().getName()+"hello world");
}
}
}
输出结果


3.2 setPriority()、getPriority()
Java是抢占式调度,每个线程都可以抢占CPU得到执行权,上述的t1和t2线程就是如此,所以执行是随机的,不确定的,不知道是哪个线程就能突然抢到CPU得到执行权。
线程的优先级越大,那么该线程抢到CPU的概率就越大。

优先级有10级,从1级到10级,逐级递增,优先级越高。默认优先级是5

优先级不是绝对的。只是概率问题。
java
package com.Threadexe;
public class Threadexe03 {
public static void main(String[] args) {
//setPriority设置优先级
MyRun m1=new MyRun();
MyRun m2=new MyRun();
Thread t1=new Thread(m1,"飞机");
Thread t2=new Thread(m2,"坦克");
t1.setPriority(10);
t2.setPriority(1);
t1.start();
t2.start();
}
}
myRun类
java
package com.Threadexe;
public class MyRun implements Runnable{
@Override
public void run() {
for (int i = 0; i < 100; i++) {
//获取到当前线程的对象
Thread t=Thread.currentThread();
System.out.println(t.getName()+i);
}
}
}
输出结果会发现优先级大的会先执行完循环
3.3 setDaemon()设置为守护进程
当其他的非守护进程执行完毕之后,守护进程会陆续结束,但是不会立刻结束,会执行一段时间

输出结果展示:备胎线程只执行了5次就结束了。

守护进程应用场景:举例

当聊天框关闭之后,文件传输也没必要进行了,传输文件进程设置为守护进程,会陆续关闭,不再传输文件
3.4 yield() 出让线程/礼让线程(了解)

在run方法中插入Thread.yield方法,使当前正在执行的线程把CPU执行权礼让出去,这样的话,打印结果就会很均衡,但是并不是绝对的均衡,只是尽可能地均衡,因为礼让出去CPU还可能再抢回来。
3.5 join() 插入线程/插队线程(了解)

利用join方法就可以使土豆线程运行完之后再运行main线程,如果不使用join方法,土豆和main线程就会交叉执行。
四、线程的生命周期

答:不会立马执行下面的代码,会变成就绪状态,不停的抢CPU。
五、线程安全的问题
100张电影票,三个售票机,卖完这100张票。当前代码:

输出结果有问题:



5.1 分析原因
1)三个进程执行的时候都会先睡觉sleep

2)sleep结束后,三个窗口陆续执行ticket++操作,都还没有sout就被抢走CPU了,导致ticket=3

3)之后都执行了sout,都是ticket=3

4)当第99张票时:

5.2 解决方法------利用同步代码块

采用同步代码块来解决线程异步不安全的问题。
ticket就是一个共享资源,为三个窗口,即三个线程所共有,是共享资源。
锁也是一个共享资源,但是它只有一个,一个线程使用之后,其他线程就不能使用锁了,所以就无法执行 操作共享数据ticket的代码块 ,只能在外面等着,等那个线程执行完后,会把锁释放,拿到锁的线程就可以执行了,此时,其余的线程因为没有锁就无法执行。
5.3 代码
java
package com.Threadexe.safe;
public class MyThread extends Thread{
//用static,表示这个类的所有对象都共享ticket数据。
static int ticketID=0;
//锁对象,是什么类型都可以,但一定得是唯一的用static。
static Object obj=new Object();
@Override
public void run(){
while (true){
synchronized (obj){
if(ticketID<100){
try {
Thread.sleep(10);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
ticketID++;
System.out.println(getName()+"正在卖第"+ticketID+"张票");
}else{
break;
}
}
}
}
}
main代码
java
package com.Threadexe.safe;
public class safeexe01 {
public static void main(String[] args) {
//1.先创建三个线程
MyThread t1=new MyThread();
MyThread t2=new MyThread();
MyThread t3=new MyThread();
t1.setName("窗口1");
t2.setName("窗口2");
t3.setName("窗口3");
t1.start();
t2.start();
t3.start();
}
}
输出结果
java
窗口1正在卖第1张票
窗口1正在卖第2张票
窗口1正在卖第3张票
窗口1正在卖第4张票
窗口1正在卖第5张票
窗口1正在卖第6张票
窗口1正在卖第7张票
窗口1正在卖第8张票
窗口1正在卖第9张票
窗口1正在卖第10张票
窗口1正在卖第11张票
窗口1正在卖第12张票
窗口1正在卖第13张票
窗口1正在卖第14张票
窗口1正在卖第15张票
窗口1正在卖第16张票
窗口1正在卖第17张票
窗口1正在卖第18张票
窗口1正在卖第19张票
窗口1正在卖第20张票
窗口1正在卖第21张票
窗口1正在卖第22张票
窗口1正在卖第23张票
窗口1正在卖第24张票
窗口1正在卖第25张票
窗口1正在卖第26张票
窗口1正在卖第27张票
窗口1正在卖第28张票
窗口1正在卖第29张票
窗口1正在卖第30张票
窗口1正在卖第31张票
窗口1正在卖第32张票
窗口1正在卖第33张票
窗口1正在卖第34张票
窗口1正在卖第35张票
窗口1正在卖第36张票
窗口3正在卖第37张票
窗口3正在卖第38张票
窗口3正在卖第39张票
窗口3正在卖第40张票
窗口3正在卖第41张票
窗口3正在卖第42张票
窗口3正在卖第43张票
窗口3正在卖第44张票
窗口3正在卖第45张票
窗口3正在卖第46张票
窗口3正在卖第47张票
窗口3正在卖第48张票
窗口3正在卖第49张票
窗口3正在卖第50张票
窗口3正在卖第51张票
窗口3正在卖第52张票
窗口3正在卖第53张票
窗口3正在卖第54张票
窗口3正在卖第55张票
窗口3正在卖第56张票
窗口3正在卖第57张票
窗口3正在卖第58张票
窗口3正在卖第59张票
窗口3正在卖第60张票
窗口3正在卖第61张票
窗口3正在卖第62张票
窗口3正在卖第63张票
窗口3正在卖第64张票
窗口3正在卖第65张票
窗口3正在卖第66张票
窗口3正在卖第67张票
窗口3正在卖第68张票
窗口3正在卖第69张票
窗口3正在卖第70张票
窗口3正在卖第71张票
窗口3正在卖第72张票
窗口3正在卖第73张票
窗口3正在卖第74张票
窗口3正在卖第75张票
窗口3正在卖第76张票
窗口3正在卖第77张票
窗口3正在卖第78张票
窗口3正在卖第79张票
窗口3正在卖第80张票
窗口3正在卖第81张票
窗口3正在卖第82张票
窗口3正在卖第83张票
窗口3正在卖第84张票
窗口3正在卖第85张票
窗口3正在卖第86张票
窗口3正在卖第87张票
窗口3正在卖第88张票
窗口3正在卖第89张票
窗口3正在卖第90张票
窗口3正在卖第91张票
窗口3正在卖第92张票
窗口3正在卖第93张票
窗口3正在卖第94张票
窗口3正在卖第95张票
窗口3正在卖第96张票
窗口3正在卖第97张票
窗口3正在卖第98张票
窗口3正在卖第99张票
窗口3正在卖第100张票
锁是必须得是唯一的一个对象,即必须前面加个static修饰
ticketID是共享资源,共享也用static实现。
5.4 同步代码块的两个细节
1)synchronized 不能写在循环外面,不然结果就是一个窗口卖完所有票
2)synchronized的锁一定要用static修饰,锁必须得是唯一的。也可以用本身就是唯一的对象,比如该类的字节码文件.class。如图:

六、同步方法------来实现同步代码块、线程安全

锁是系统自己设置的,不用管。
6.1 代码示例:
java
package com.Threadexe.safe;
public class MyRun implements Runnable{
int ticket=0;
@Override
public void run() {
//1.循环
while (true){
//2.同步代码块(同步方法)
synchronized (MyRun.class){
//3.判断共享数据是否到了末尾,如果到了末尾
if(ticket==100){
break;
}else{
//4.判断共享数据是否到了末尾,如果没有到末尾
try {
Thread.sleep(10);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
ticket++;
System.out.println(Thread.currentThread().getName()+"正在卖第"+ticket+"张票!!");
}
}
}
}
}
这是在使用同步方法之前的代码。锁是用本类的字节码文件.class。
6.2 使用同步方法之后的代码:
java
package com.Threadexe.safe;
public class MyRun implements Runnable{
int ticket=0;
@Override
public void run() {
//1.循环
while (true){
//2.同步代码块(同步方法)
if (method()) break;
}
}
//非静态的方法,锁是this
private synchronized boolean method() {
//3.判断共享数据是否到了末尾,如果到了末尾
if(ticket==100){
return true;
}else{
//4.判断共享数据是否到了末尾,如果没有到末尾
try {
Thread.sleep(10);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
ticket++;
System.out.println(Thread.currentThread().getName()+"正在卖第"+ticket+"张票!!");
}
return false;
}
}
直接抽取synchronized下面的代码块成方法,并在方法前面加上synchronizied关键字。把原代码中的synchronized的那行代码删去即可。锁不用管,系统会自动设置。
注意:这个runnable的线程实现方法不用再共享资源ticket前面加static,因为这个MyRun这个类再main方法中只创建一个对象,不是和MyThread类的main方法创建多个对象。
6.3 main方法
java
package com.Threadexe.safe;
public class safeexe02 {
public static void main(String[] args) {
MyRun mr=new MyRun();
Thread t1=new Thread(mr,"窗口一");
Thread t2=new Thread(mr,"窗口二");
Thread t3=new Thread(mr,"窗口三");
t1.start();
t2.start();
t3.start();
}
}
输出结果和使用同步代码块的一样,线程很安全。
6.4 使用场景举例
StringBuilder和StringBuffer类的作用和使用方法一样,但是StringBuffer的每个成员方法都是同步方法,这样就可以避免多线程的时候出现安全问题。
所以,单线程的时候用StringBuilder,而多线程的时候使用StringBuffer来保证安全。
七、Lock锁
7.1 Lock锁的简要介绍

7.2 代码演示一:(错误的)

这样最后一个人的ticket==100并且拿着锁,会直接第一个if条件break之后循环结束,不会执行lock.unlock()代码,就一直不释放锁,另外两个线程就没办法拿到锁把循环执行完,程序就一直不会结束。
7.2.1 解决办法一:在if语句中添加lock.unlock()
java
package com.Threadexe.lock;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class Mythread extends Thread{
static int ticket=0;
static Lock lock=new ReentrantLock();
@Override
public void run(){
while (true){
lock.lock();
if (ticket==100){
lock.unlock();
break;
}else {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
ticket++;
System.out.println(getName()+"正在卖第"+ticket+"张票!!");
}
lock.unlock();
}
}
}
这样在if语句中也有unlock解锁了,就不会一直拿着锁不放了
7.2.2 解决办法二:使用try-catch-finally架构,把lock.unlock()写到finally中
finally不管怎样都要执行,即使在循环中前面语句执行了break,也要执行finally中的。
java
package com.Threadexe.lock;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class Mythread extends Thread{
static int ticket=0;
static Lock lock=new ReentrantLock();
@Override
public void run(){
while (true){
lock.lock();
try {
if (ticket==100){
break;
}else {
Thread.sleep(10);
ticket++;
System.out.println(getName()+"正在卖第"+ticket+"张票!!");
}
} catch (InterruptedException e) {
throw new RuntimeException(e);
} finally {
lock.unlock();
}
}
}
}
7.3 总结------实现安全线程共有三个方法:
1)5.2 利用同步代码块
2)6.2 使用同步方法
3)7.2 使用lock锁的方法。
八、死锁
外面一个锁嵌套着里面一个锁

比如:A和B都等着对方释放对方所持有的锁

代码示例

什么时候释放锁:只有当synchronized里面的代码块都执行完之后才会释放锁
明白一点:写代码的时候,不要写嵌套锁
九、生产者和消费者------等待唤醒机制一(wait和notify方式)
9.1 代码执行初期

如果这时候消费者去空桌子上,就会wait,CPU就会给生产者,生产者做完饭后叫醒消费者。

始终要完成这样一个逻辑:(1)桌子上没有饭------生产者来了做饭------做完后叫醒消费者
(2)桌子上有饭------生产者来了wait------消费者抢到CPU则吃饭------吃完叫醒生产者。
(3)就是有食物就是消费者执行,没有食物就是生产者进行。
9.2 常见方法

9.3 消费者 wait 空桌子 代码实现
foodFlag代表桌子上是否有食物:1有食物;0没有食物
**有三个类:**生产者Cook,消费者Foodie,桌子Desk:用来控制生产者和消费者的执行,里面包含(1)foodFlag是否有面条,(2)生产者和消费者这样的活动次数,(3)锁 对象 用static
java
package com.Threadexe.waitandnotify;
import com.sun.source.tree.WhileLoopTree;
public class Foodie extends Thread{
//消费者线程
//1.循环
//2.同步代码块
//3.判断共享数据是否到了末尾(到了末尾)
//4.判断共享数据是否到了末尾(没有到末尾,执行核心逻辑)
@Override
public void run(){
while (true){
synchronized (Desk.lock){
if(Desk.count==0){
break;
}else{
//核心逻辑
if(Desk.foodFlag==0){//1.判断桌子上是否有饭------无
//那就等待
try {
Desk.lock.wait();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
else{//1.判断桌子上是否有饭------有
//先吃饭
Desk.count--;
Desk.foodFlag=0;
System.out.println("消费者正在吃饭,还剩下"+Desk.count+"碗饭");
//吃完饭叫醒生产者去做饭
Desk.lock.notify();
}
}
}
}
}
}
9.4 生产者 wait 满桌子 代码实现
java
package com.Threadexe.waitandnotify;
public class Cook extends Thread{
@Override
public void run(){
//消费者线程
//1.循环
//2.同步代码块
//3.判断共享数据是否到了末尾(到了末尾)
//4.判断共享数据是否到了末尾(没有到末尾,执行核心逻辑)
while (true){
synchronized (Desk.lock){
if(Desk.count==0){
break;
}else{
if(Desk.foodFlag==1){
//有饭--等待
try {
Desk.lock.wait();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
else{
//无饭--做饭
Desk.foodFlag=1;
System.out.println("生产者做了一碗饭");
Desk.lock.notify();//叫醒消费者
}
}
}
}
}
}
Desk类
java
package com.Threadexe.waitandnotify;
public class Desk {
public static int foodFlag=0;
public static int count=10;//这样的逻辑要执行10次
//锁,唯一,要用static
public static Object lock=new Object();
}
Test测试类
java
package com.Threadexe.waitandnotify;
public class Test {
public static void main(String[] args) {
Cook c=new Cook();
Foodie f=new Foodie();
c.setName("生产者");
f.setName("消费者");
c.start();
f.start();
}
}
输出结果
java
生产者做了一碗饭
消费者正在吃饭,还剩下9碗饭
生产者做了一碗饭
消费者正在吃饭,还剩下8碗饭
生产者做了一碗饭
消费者正在吃饭,还剩下7碗饭
生产者做了一碗饭
消费者正在吃饭,还剩下6碗饭
生产者做了一碗饭
消费者正在吃饭,还剩下5碗饭
生产者做了一碗饭
消费者正在吃饭,还剩下4碗饭
生产者做了一碗饭
消费者正在吃饭,还剩下3碗饭
生产者做了一碗饭
消费者正在吃饭,还剩下2碗饭
生产者做了一碗饭
消费者正在吃饭,还剩下1碗饭
生产者做了一碗饭
消费者正在吃饭,还剩下0碗饭
无论怎么执行,都是这个结果,因为有wait和notify就能控制线程的执行顺序。
十、等待唤醒机制二(阻塞队列方式)

阻塞队列是生产者和消费者之间的一个通道,是有长度的,如果长度设为1,实质就是上述wait和notify代码foodflag=1.

利用两个实现类来实现代码:
生产者:
java
package com.Threadexe.BlockQueue;
import java.util.concurrent.ArrayBlockingQueue;
public class Cook extends Thread{
ArrayBlockingQueue<String> queue;
public Cook(ArrayBlockingQueue<String> queue) {
this.queue = queue;
}
@Override
public void run(){
while (true){
//不断的把面条放到阻塞队列当中
try {
queue.put("面条");
System.out.println("厨师放了一碗面条");
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}
put方法里面有锁lock,所以外面不能再有同步代码块synchronized,take方法同理。
消费者:
java
package com.Threadexe.BlockQueue;
import java.util.concurrent.ArrayBlockingQueue;
public class Foodie extends Thread{
ArrayBlockingQueue<String> queue;
public Foodie(ArrayBlockingQueue<String> queue) {
this.queue = queue;
}
@Override
public void run() {
while (true) {
//不断的从阻塞队列中获取面条
try {
String food = queue.take();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("面条");
}
}
}
测试类:在这里设置阻塞队列
java
package com.Threadexe.BlockQueue;
import java.util.concurrent.ArrayBlockingQueue;
public class BlockQueu01 {
public static void main(String[] args) {
//在测试类中定义阻塞队列
ArrayBlockingQueue<String> queue=new ArrayBlockingQueue<>(1);
//只一碗饭,和上述代码实质一样
//创建线程
Cook c=new Cook(queue);
Foodie f=new Foodie(queue);
//设置名字
c.setName("厨师");
f.setName("消费者");
c.start();
f.start();
}
}
sout打印语句没有锁包围。
十一、线程的六种状态

其实没有运行状态,只有这6种:

**阻塞状态参考 :**3个窗口随机出售100张票的代码,虽然有同步代码块,但CPU的执行权落到哪个线程手中还是随机的。------抢不到CPU无奈阻塞自己
**等待状态参考:**生产者消费者代码。CPU执行权已经有逻辑地落在某个线程手中。------主动wait
十二、多线程练习题
12.1

12.2

12.3

12.4

线程执行内容代码
java
package com.Threadexe.练习题7个.exe04;
import java.util.Random;
public class MyThread4 extends Thread {
//1.先写共享资源---红包钱数--红包个数
static int money = 100;
static int count = 3;
//设置一个常量final,因为每次抢到的红包最小是0.01
static final double MIN = 0.01;
@Override
public void run() {
//循环
//同步代码块
//判断共享数据是否到了末尾------------已经到末尾
//判断共享数据是否到了末尾------------没有到末尾
//这次只执行一次,所以没有循环直接同步代码块
synchronized (MyThread4.class) {
if (count == 0) {
//红包抢完了
System.out.println(getName() + "没抢到红包");
} else {
//红包没抢完
//定义一个变量price,用来存储表示抢到的红包钱数
double price = 0;
if (count == 1) {
//抢到的是最后一个红包,所以不用随机,直接拿剩余钱money为抢到的红包钱数
price = money;
} else {
//抢到的是第一个、第二个红包,可以随机
Random r = new Random();
double bounds = money - (count - 1) * MIN;
price = r.nextDouble(bounds);
if (price < MIN) {
price = MIN;
}
;//但是随机可能得到0,进行处理
}
money -= price;
count--;
System.out.println(getName() + "抢到了" + price + "元红包");
}
}
}
}
测试类代码
java
package com.Threadexe.练习题7个.exe04;
public class exe04 {
public static void main(String[] args) {
//创建五个线程
MyThread4 t1=new MyThread4();
MyThread4 t2=new MyThread4();
MyThread4 t3=new MyThread4();
MyThread4 t4=new MyThread4();
MyThread4 t5=new MyThread4();
//设置名字
t1.setName("丹丹");
t2.setName("倩倩");
t3.setName("轩轩");
t4.setName("琪琪");
t5.setName("陈陈");
t1.start();
t2.start();
t3.start();
t4.start();
t5.start();
}
}
输出结果
java
丹丹抢到了28.562627761687093元红包
陈陈抢到了66.11714857190539元红包
琪琪抢到了4.0元红包
轩轩没抢到红包
倩倩没抢到红包
12.5

线程执行代码
1、先清楚哪个是共享数据,2、同步代码块四步走
共享数据如果是数组or集合这种对象,可以把它创建在测试类,并在线程类中设置构造函数,把集合当参数传递进来。
测试类代码:
java
package com.Threadexe.练习题7个.exe05;
import java.util.ArrayList;
import java.util.Collections;
public class Test {
public static void main(String[] args) {
ArrayList<Integer> list=new ArrayList<>();
Collections.addAll(list,10,5,20,90,100,5,40,60,30);
MyThread5 t1=new MyThread5(list);
MyThread5 t2=new MyThread5(list);
t1.setName("抽奖箱1");
t2.setName("抽奖箱2");
t1.start();
t2.start();
}
}
线程类代码:
java
package com.Threadexe.练习题7个.exe05;
import java.util.ArrayList;
import java.util.Collections;
public class MyThread5 extends Thread{
ArrayList<Integer> list;
public MyThread5(ArrayList<Integer> list) {
this.list = list;
}
@Override
public void run(){
//循环
while (true){
//同步代码块
synchronized (MyThread5.class){
//判断共享数据是否到了末尾------------已经到末尾
if(list.size()==0){
break;
}else{//判断共享数据是否到了末尾------------没有到末尾
//随机拿走一个奖项
Collections.shuffle(list);
Integer price = list.remove(0);
System.out.println(getName()+"又产生了一个"+price+"大奖");
}
}
//如果想让线程交替均衡执行,可以在同步代码块外面设置sleep
try {
Thread.sleep(10);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}
输出结果:
java
抽奖箱1又产生了一个90大奖
抽奖箱2又产生了一个10大奖
抽奖箱1又产生了一个5大奖
抽奖箱1又产生了一个60大奖
抽奖箱2又产生了一个30大奖
抽奖箱1又产生了一个5大奖
抽奖箱2又产生了一个40大奖
抽奖箱2又产生了一个100大奖
抽奖箱1又产生了一个20大奖
注意:如果想让线程交替均衡执行,可以在同步代码块外面设置sleep
12.6(1)

在线程类中设置两个static修饰的集合,代表这两个集合是属于线程类的,和任何线程对象都没有关系,任何线程对象来执行了,都是这两个集合。
测试类
java
package com.Threadexe.练习题7个.exe06;
import java.util.ArrayList;
import java.util.Collections;
public class Test {
public static void main(String[] args) {
ArrayList<Integer> list=new ArrayList<>();
Collections.addAll(list,10,5,20,90,100,5,40,60,30);
Mythread6 t1=new Mythread6(list);
Mythread6 t2=new Mythread6(list);
t1.setName("抽奖箱1");
t2.setName("抽奖箱2");
t1.start();
t2.start();
}
}
线程类
java
package com.Threadexe.练习题7个.exe06;
import com.Threadexe.练习题7个.exe05.MyThread5;
import java.util.ArrayList;
import java.util.Collections;
public class Mythread6 extends Thread {
ArrayList<Integer> list;
public Mythread6(ArrayList<Integer> list) {
this.list = list;
}
static ArrayList<Integer> l1=new ArrayList<>();
static ArrayList<Integer> l2=new ArrayList<>();
@Override
public void run(){
//循环
while (true){
//同步代码块
synchronized (MyThread5.class){
//判断共享数据是否到了末尾------------已经到末尾
if(list.size()==0){
//到了抽奖完时进行打印:
if ("抽奖箱1".equals(getName())){
System.out.println("抽奖箱1:"+l1+";最大奖项为:"+Collections.max(l1)+",总金额为"+getSum(l1));
}else{
System.out.println("抽奖箱2:"+l2+";最大奖项为:"+Collections.max(l2)+",总金额为"+getSum(l2));
}
break;
}else{//判断共享数据是否到了末尾------------没有到末尾
//随机拿走一个奖项
Collections.shuffle(list);
Integer price = list.remove(0);
if("抽奖箱1".equals(getName())){
//如果是抽奖箱1线程执行,就把它所获得的奖项放到l1集合中
l1.add(price);
}else{
l2.add(price);
}
//System.out.println(getName()+"又产生了一个"+price+"大奖");
}
}
//如果想让线程交替均衡执行,可以在同步代码块外面设置sleep
try {
Thread.sleep(10);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
public int getSum(ArrayList<Integer> list){
int sum=0;
for (Integer i : list) {
sum+=i;
}
return sum;
}
}
输出结果
java
抽奖箱1:[60, 40, 20, 5];最大奖项为:60,总金额为125
抽奖箱2:[100, 30, 90, 5, 10];最大奖项为:100,总金额为235
关键点------同步资源写在哪?
(1)如果集合一开始是有数据的,就在测试类中创建集合,并用构造函数传递。在线程类中把集合当作成员变量。
(2)如果集合没有数据,就用static方法,把集合当作静态变量,让它属于线程类。
12.6(2)
如果抽奖箱有个呢?上面的方法一就不适用了,要创建个静态的L1集合,还要写100个if语句。
这时可以把创建集合的语句放到线程类的run方法中,这样的话,每start一个线程对象,就会创建一个栈内存,里面有run方法,进而有自己的集合。各个线程的集合互不干扰。
线程类代码
java
package com.Threadexe.练习题7个.exe06;
import com.Threadexe.练习题7个.exe05.MyThread5;
import java.util.ArrayList;
import java.util.Collections;
public class Mythread6 extends Thread {
ArrayList<Integer> list;
public Mythread6(ArrayList<Integer> list) {
this.list = list;
}
@Override
public void run(){
ArrayList<Integer> l=new ArrayList<>();
//循环
while (true){
//同步代码块
synchronized (MyThread5.class){
//判断共享数据是否到了末尾------------已经到末尾
if(list.size()==0){
//到了抽奖完时进行打印:
System.out.println(getName()+l);
break;
}else{//判断共享数据是否到了末尾------------没有到末尾
//随机拿走一个奖项
Collections.shuffle(list);
Integer price = list.remove(0);
l.add(price);
//System.out.println(getName()+"又产生了一个"+price+"大奖");
}
}
//如果想让线程交替均衡执行,可以在同步代码块外面设置sleep
try {
Thread.sleep(10);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
public int getSum(ArrayList<Integer> list){
int sum=0;
for (Integer i : list) {
sum+=i;
}
return sum;
}
}
测试类代码
java
package com.Threadexe.练习题7个.exe06;
import java.util.ArrayList;
import java.util.Collections;
public class Test {
public static void main(String[] args) {
ArrayList<Integer> list=new ArrayList<>();
Collections.addAll(list,10,5,20,90,100,5,40,60,30);
Mythread6 t1=new Mythread6(list);
Mythread6 t2=new Mythread6(list);
Mythread6 t3=new Mythread6(list);
Mythread6 t4=new Mythread6(list);
Mythread6 t5=new Mythread6(list);
t1.setName("抽奖箱1");
t2.setName("抽奖箱2");
t3.setName("抽奖箱3");
t4.setName("抽奖箱4");
t5.setName("抽奖箱5");
t1.start();
t2.start();
t3.start();
t4.start();
t5.start();
}
}
输出结果
java
抽奖箱4[10]
抽奖箱3[100, 5]
抽奖箱1[90, 20]
抽奖箱5[5, 40]
抽奖箱2[30, 60]
多线程的内存图:

一个线程有一个自己的栈内存,之前都是画一个栈内存,因为都只有一个main线程。
t1.start()方法一旦执行,就会给t1线程创建一个栈内存;t2也是如此
在线程类创建的run方法,就会放到每个线程对象的栈内存中,一个线程对象有一个run方法,里面就分别有自己的变量,包括整数、数组集合等变量。 所以各线程的变量互不干扰。

12.7

此题的关键点就在于要获取两个线程运行结束后的各自集合中的最大值,并进行比较得到更大值。
所以就想到了用Callable接口方法来创建线程。
线程类
java
package com.Threadexe.练习题7个.exe07;
import com.Threadexe.练习题7个.exe05.MyThread5;
import java.util.ArrayList;
import java.util.Collections;
import java.util.concurrent.Callable;
public class Mycallable implements Callable<Integer> {
ArrayList<Integer> list;
public Mycallable(ArrayList<Integer> list) {
this.list = list;
}
@Override
public Integer call() throws Exception {
ArrayList<Integer> l = new ArrayList<>();
//循环
while (true) {
//同步代码块
synchronized (Mycallable.class) {
//判断共享数据是否到了末尾------------已经到末尾
if (list.size() == 0) {
//到了抽奖完时进行打印:
System.out.println(Thread.currentThread().getName() + l);
break;
} else {//判断共享数据是否到了末尾------------没有到末尾
//随机拿走一个奖项
Collections.shuffle(list);
Integer price = list.remove(0);
l.add(price);
//System.out.println(getName()+"又产生了一个"+price+"大奖");
}
}
//如果想让线程交替均衡执行,可以在同步代码块外面设置sleep
Thread.sleep(10);
}
return Collections.max(l);
}
}
要在循环结束之后返回集合的最大值
测试类
java
package com.Threadexe.练习题7个.exe07;
import com.Threadexe.练习题7个.exe06.Mythread6;
import java.util.ArrayList;
import java.util.Collections;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
public class Test {
public static void main(String[] args) throws ExecutionException, InterruptedException {
ArrayList<Integer> list=new ArrayList<>();
Collections.addAll(list,10,5,20,90,100,5,40,60,30,1000,800,600);
//创建多线程要运行的参数对象,创建一个就可以了,不论有几个线程,只要执行的内容一样,就只创建一个
Mycallable mc=new Mycallable(list);
//创建多线程运行结果的管理者,可以创建多个,为多个线程接住返回结果
//线程一
FutureTask<Integer> ft1=new FutureTask<>(mc);
//线程二
FutureTask<Integer> ft2=new FutureTask<>(mc);
//创建线程
Thread t1=new Thread(ft1);
Thread t2=new Thread(ft2);
t1.setName("抽奖箱1");
t2.setName("抽奖箱2");
t1.start();
t2.start();
Integer max1 = ft1.get();
Integer max2 = ft2.get();
Integer MAX = max1 > max2 ? max1 : max2;
if(max1==MAX){
System.out.println("抽奖箱1拿到了最大奖项,金额为"+max1);
}else{
System.out.println("抽奖箱2拿到了最大奖项,金额为"+max2);
}
}
}
输出结果
java
抽奖箱1[100, 90, 40, 1000, 60, 5]
抽奖箱2[20, 10, 800, 30, 5, 600]
抽奖箱1拿到了最大奖项,金额为1000
十三、线程池



线程池不会关闭,一直有。

代码示例:
任务:
java
package com.Threadexe.threadPool;
public class Myrun implements Runnable{
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName()+"------------"+i);
}
}
}
测试类
java
package com.Threadexe.threadPool;
import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class exe {
public static void main(String[] args) {
//1.获取线程池对象
ExecutorService pool1= Executors.newCachedThreadPool();
//2.提交任务
pool1.submit(new Myrun());
pool1.submit(new Myrun());
pool1.submit(new Myrun());
pool1.submit(new Myrun());
pool1.submit(new Myrun());
pool1.submit(new Myrun());
pool1.submit(new Myrun());
pool1.submit(new Myrun());
//3.销毁线程池
//pool1.shutdown();
}
}
输出结果
任务需要时间长,无法很快执行完就回线程池复用,所以就会创建8个线程来完成这些任务。

而如果任务很块就执行完了,那就可以复用一开始创建的线程来完成任务。
任务改为:没有循环,只有一条打印语句。

这种的依然体现不出来,因为提交任务也很快,前面的线程无法执行完立马回来完成后面提交的任务。所以我们要让任务提交得慢些,让线程1有充足的返回线程池时间,所以在后面的提交任务前面加一个sleep。
线程复用体现

这样的话,线程1就有充足的时间返回线程池被复用了。
有上限的线程池:

13.2 自定义线程



完全由核心线程执行任务

等待的4和5,直到线程123执行完,才会执行4和5.

任务7交给临时线程4;任务8交给临时线程5;线程456等待。

超负荷了已经,多余的任务就直接触发拒绝策略。


代码实现

总结

最大并行数

实际上是4个CPU,但是通过超线程技术可以实现8个线程来处理计算机的任务。
所以,最大并行数是 8

线程池多大合适呢?

经常用到数据库的是I/O密集型运算项目,现在的项目一般都是这种的。
总时间和CPU计算时间可以用Thread类的dump方法来获取。
十四、线程总结
写线程练习题时:
-
关键点------同步资源写在哪?
(1)如果集合一开始是有数据的,就在测试类中创建集合,并用构造函数传递。在线程类中把集合当作成员变量。
(2)如果集合没有数据,就用static方法,把集合当作静态变量,让它属于线程类的静态变量。
-
各个线程是通过共享数据资源才联系在一起的,才会并发的。
-
如果任务只执行一次就不需要循环。
-
同步代码块四部曲:循环、同步代码块、判断共享数据是否到了末尾------到了末尾、判断共享数据是否到了末尾------没有到末尾




