多线程
1、线程简介
1.1、Process与Thread
进程与线程
- 说起进程,就不得不说一下程序。程序时指令和数据的有序集合,其本身没有任何运行的含义,是一个静态的概念。
- 而进程则是执行程序的一次执行的过程,它是一个动态的概念。是系统资源分配的单位
- 通常在一个线程中可以包含若干个线程,当然一个进程中至少有一个线程,不然没有存在的意义。线程是CPU调度和执行的单位。
- 总结:一个程序只有一个进程,一个进程必须至少包含一个线程,线程可以有多个,而程序执行的是线程。
注意:很多多线程是模拟出来的,真正的多线程是指有多个CPU,即多核,如服务器。如果是模拟出来的多线程,即在一个CPU的情况下,在同一个时间点,CPU只能执行一个代码,因为切换的很快,所以就有同时执行的错局。
1.2、核心概念
- 线程就是独立的执行路径
- 在程序运行时,即使没有自己创建线程,后台也会有多个线程,如主线程,gc线程;
- main()称之为主线程,为系统的入口,用于执行整个程序
- 在一个进程中,如果开辟了多个线程,线程的运行由调度器(CPU)安排调度,调度器是与操作系统紧密相关的,先后顺序是不能认为的干预的。
- 对同一份资源操作时,会存在资源抢夺的问题,需要加入并发控制
- 线程会带来额外的开销,如CPU调度时间,并发控制开销
- 每个线程在自己的工作内存交换,内存控制不当会造成和数据不一致。
2、线程创建
三种创建方式
Thread class 继承Thread类(重点)
Runnable接口 实现Runnable接口
callable接口 实现Callable接口(了解)
2.1、Thread继承实现
- 自定义线程继承Thread类
- 重写run()方法,编写线程执行体
- 创建线程对象,调用start()方法启动线程
代码参考:
/**
* @author 丶Anton
* @date 2021/4/14 16:58
* 总结:注意,线程开启不一定立即执行,由CPU调度执行。
*/
public class HelloThread extends Thread{
@Override
public void run(){
//线程体
for (int i = 0; i < 20; i++) {
System.out.println("我在主线程敲代码~~~~~~~~~~~~");
}
}
public static void main(String[] args) {
//main线程,主线程
//创建一个线程对象
HelloThread HelloThread = new HelloThread();
//调用线程对象中的start方法开启线程
HelloThread.start();
for (int i = 0; i < 200; i++) {
System.out.println("我是主线程,我在敲代码!");
}
}
}
总结:注意,线程开启不一定立即执行,由CPU调度执行。
2.1.1、练习 网络图片下载
需要导入包 **commons-io-1.3.2.jar **
工具类 FileUtils
代码参考:
package lib;
import org.apache.commons.io.FileUtils;
import java.io.File;
import java.io.IOException;
import java.net.URL;
/**
* @author 丶Anton
* @date 2021/4/14 17:19
* 联系Thread,实现多线程下载图片
*/
public class TestThread2 extends Thread {
private String url; //网络图片地址
private String name; //保存的文件名
public TestThread2(String url, String name) {
this.url = url;
this.name = name;
}
@Override
public void run(){
//线程体
//调用网络文件下载方法下载
WebDownloader.Downloader(url, name);
System.out.println("下载了文件:" + name);
}
public static void main(String[] args) {
TestThread2 t2 = new TestThread2("https://static.runoob.com/images/mix/plief.jpg", "程序人生.jpg");
TestThread2 t3 = new TestThread2("https://static.runoob.com/images/mix/maxresdefault.jpg", "编程是一门艺术.jpg");
TestThread2 t1 = new TestThread2("https://static.runoob.com/images/mix/plief.jpg", "程序人生1.jpg");
t1.start();
t2.start();
t3.start();
}
}
class WebDownloader{
/**
* 下载方法
* @param url 网页URL
* @param name 要保存的路径
*/
public static void Downloader(String url, String name){
try {
//参数: URL, 为文件取名
FileUtils.copyURLToFile(new URL(url), new File(name));
} catch (IOException e) {
System.out.println("Downloader方法出现问题,IO异常!");
}
}
}
总结:执行顺序是按照CPU调度执行的,一句话一切看CPU心情。
2.2、Runnable接口实现 【推荐】
步骤:
- 创建一个类,实现Runnable接口
- 重写run方法
- 在主线程中创建一个Runnable实现类对象
- 创建Thread线程对象,将Runnable实现类对象丢进Thread线程对象中
- 调用start方法,开启线程
/**
* @author 丶Anton
* @date 2021/4/14 17:51
*/
public class TestRunnable implements Runnable {
@Override
public void run() {
for (int i = 0; i < 50; i++) {
System.out.println("我是小小天才程序员,我在学习多线程");
}
}
public static void main(String[] args) {
//创建一个Runnable实现类对象
Runnable Runnable = new TestRunnable();
//将Runnable实现类对象丢进 Thread构造器创建一条新线程
Thread thread = new Thread(Runnable);
//开启线程
thread.start();
for (int i = 0; i < 200; i++) {
System.out.println("我是程序员,我在看小天才学习多线程~~~~~~");
}
}
}
总结:本质上还是跟继承Thread类一样。
2.3、小结
- 继承Thread类
- 子类继承Thread类具备多线程能力
- 启动线程:子类对象.start()
- 不建议使用:避免OOP单继承局限性
- 实现Runnable接口
- 实现Runnable接口具有多线程能力
- 启动线程:传入目标对象+Thread对象.start()
- 推荐使用:避免单继承局限性,灵活方便,方便同一个对象被多个线程使用
2.4、线程问题
代码:
package com.itanton;
/**
* @author 丶Anton
* @date 2021/4/14 19:46
* 火车票案列
*/
public class RunnableCase implements Runnable{
private int ticket;
public RunnableCase(int ticket) {
this.ticket = ticket;
}
public RunnableCase() {
}
public static void main(String[] args) {
RunnableCase Case = new RunnableCase(10);
//创建三个进程同时抢票
new Thread(Case).start();
new Thread(Case).start();
new Thread(Case).start();
}
@Override
public void run() {
while(ticket > 1){
System.out.println(Thread.currentThread().getName() + "拿到了第" + ticket + "张票");
ticket--;
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
运行结果:
Thread-1拿到了第10张票
Thread-0拿到了第10张票
Thread-2拿到了第10张票
Thread-0拿到了第7张票
Thread-2拿到了第6张票
Thread-1拿到了第5张票
Thread-0拿到了第4张票
Thread-2拿到了第3张票
Thread-1拿到了第3张票
总结:如果多个线程同时使用同一个对象的话,会引发线程安全问题,此处就会用到 synchronized锁来解决,后面再仔细介绍锁的使用。
3、静态代理模式【扩展】
总结:可以使用静态代理的前提是代理方与买家同时实现一个接口,买家将事情丢给代理方去做,代理方再将做事情,做事后交付即可。用现实生活中的例子可以叫做找中介
代码参考:
/**
* @author 丶Anton
* @date 2021/4/14 21:06
* 静态代理
*/
public class buyACar implements buyACarInterface {
@Override
public void buy() {
System.out.println("小罗买到车了,超级开心");
}
public static void main(String[] args) {
//创建买车人
buyACar buyACar = new buyACar();
//买车人将买车的事情交给 4儿子店去做
_4SShop _4SShop = new _4SShop(buyACar);
//4儿子点帮忙买车
_4SShop.buy();
}
}
//买车接口
interface buyACarInterface {
void buy();
}
//4儿子店
class _4SShop implements buyACarInterface{
//用于接收客户的
private buyACarInterface buyACarInterface;
public _4SShop(com.itanton.buyACarInterface buyACarInterface) {
this.buyACarInterface = buyACarInterface;
}
//买车前
private void before(){
System.out.println("4儿子进货");
}
//买车后
private void after(){
System.out.println("送保险");
}
//代理买车
@Override
public void buy() {
before();
this.buyACarInterface.buy();
after();
}
}
4、线程状态
线程有5大状态_看图:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-IwqnsxC0-1618477829332)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20210414212128620.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-NkhbEIUs-1618477829334)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20210414212349022.png)]
4.1、线程方法
方法 | 说明 |
---|---|
setPriority(int newPriority) | 更改线程优先级 |
static void sleep(long millis) | 在指定的毫秒数内让当前正在执行的线程休眠 |
void join() | 等待该线程终止 |
static void yield() | 暂停当前正在执行的线程对象,并执行其他线程 |
boolean isAlive() | 测试线程是否处于活动状态 |
4.2、停止线程
- 不推荐使用JDK提供的stop()、destroy()方法。【已废弃】
- 推荐线程自己停止下来
- 建议使用一个标志位进行终止变量,当flag=false,则终止线程运行
- 建议线程正常停止 --> 利用次数,不建议死循环
/**
* @author 丶Anton
* @date 2021/4/14 21:38
*/
public class ThreadStop {
//用于停止线程的标志位
private static boolean flag = true;
//设置停止标志位
public static void stop(){
flag = false;
}
public static void main(String[] args) {
int i = 0;
new Thread(() -> {
while(flag){
System.out.println("干活干活");
}
}).start();
for(; i < 100; i++){
System.out.println(Thread.currentThread().getName() + "当前:" + i);
if(i == 90){
stop();
System.out.println("该线程停止了");
}
}
}
}
4.3、线程休眠
- sleep(时间)指定当前线程阻塞的毫秒数
- sleep存在异常interruptedException
- sleep时间达到后线程进入就绪状态
- sleep可以模拟网络延时,倒计时,放大问题的发生性等
- 每个对象都有一个锁,sleep不会释放锁
//模拟倒计时
public TestSleep{
public static void main(){
int i = 10;
while(i > 0){
//停止1秒
try{
Thread.sellp(1000);
} catch(interruptedException e){
e.printStackTrace();
}
i--;
System.out.println(i);
}
}
}
4.4、线程礼让
- 礼让线程,让当前正在执行的线程停止,但不阻塞
- 将线程从运行状态转换位就绪状态
- 让CPU重新调度,礼让不一定成功,看CPU心情
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YxLcg2AX-1618477829336)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20210414221731740.png)]
public class ThreadYield {
public static void main(String[] args) {
new Thread(()->{
System.out.println("P-Go");
Thread.yield(); //线程礼让,但不阻塞,当线程回来时继续执行下面代码
System.out.println("P-END");
}).start();
new Thread(()->{
System.out.println("C-Go");
Thread.yield(); //线程礼让
System.out.println("C-END");
}).start();
}
}
4.4、线程强制执行【插队】
- Join合并线程,待此线程执行完毕后,再执行其他线程,其他线程阻塞
- 可以想象成插队
/**
* @author 丶Anton
* @date 2021/4/14 22:22
* 线程强制执行
*/
public class ThreadJoin implements Runnable {
public static void main(String[] args) {
Thread thread = new Thread(new ThreadJoin());
thread.start();
for (int i = 0; i < 10; i++) {
if (i == 5) {
try {
//等待该线程执行完毕才可以退出 插队
//所有线程都必须等待我执行完毕
thread.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(i);
}
}
@Override
public void run() {
for (int i = 0; i < 20; i++) {
System.out.println(Thread.currentThread().getName() + ",i=" + i);
}
}
}
4.5、观测线程状态
- NEW 初始状态
- RUNNABLE 运行时状态
- BLOCKED 阻塞于锁状态
- WAITING 等待状态
- TIMED_WAITING 等待超时状态
- TERMINATED 死亡状态(当线程死亡后就不可再次启动了,一个线程只可以启动 一次)
/**
* @author 丶Anton
* @date 2021/4/15 11:24
* 观察线程状态
*/
public class ThreadState {
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(() -> {
for (int i = 0; i < 2; i++) {
Thread.sleep(100); //线程睡眠
}
System.out.println("即将死亡");
});
Thread.State state = thread.getState(); //获取线程状态
System.out.println(state);
thread.start(); //启动线程
state = thread.getState(); //更新线程状态
System.out.println(state);
//如果thread线程没有死亡则一直运行
while( state != Thread.State.TERMINATED ){
state = thread.getState(); //更新线程状态
System.out.println(state);
Thread.sleep(100);
}
}
}
总结:
- NEW初始状态,在线程被创建时就会存在
- RUNNABLE状态,当线程启动时(Thread.start())
- TIMED_WAITING / WAITING状态,当CPU调度或则被线程插队时,则会切换至
- BLOCKED状态,当两个线程使用同一个锁,其中一个阻塞时,则阻塞那个线程则会转成次状态
- TERMINATED状态,当线程执行完所有代码
4.6、线程优先级
- Java提供一个线程调度器来监控程序中启动后进入就绪状态的所有线程,线程调度器按照优先级决定应该调度哪个线程来执行。
- 线程优先级用数字表示,范围从1~10.
- Thread.MIN_PRIORITY = 1
- Thread.MAX_PRIORITY = 10;
- Thread.NORM_PRIORITY = 5;
- 使用一下方式改变获取优先级
- getPriority()
- setPriority(int x)
/**
* @author 丶Anton
* @date 2021/4/15 11:42
* 测试线程优先级
* 总结:线程优先级高的不一定会先执行,但是大多数情况下会先执行
*/
public class ThreadPriority implements Runnable {
public static void main(String[] args) {
Thread t1 = new Thread(new ThreadPriority());
Thread t2 = new Thread(new ThreadPriority());
Thread t3 = new Thread(new ThreadPriority());
//设置线程优先级(先设置再启动)
t1.setPriority(1);t2.setPriority(5);t3.setPriority(10);
//启动线程
t1.start();t2.start();t3.start();
}
@Override
public void run() {
Thread thread = Thread.currentThread();
System.out.println(thread.getName() + "运行了,线程优先级:" + thread.getPriority());
}
}
总结:线程优先级高的不一定会先执行,但是大多数情况下会先执行
4.7、守护(daemon)线程
- 线程分为用户线程和守护线程
- 虚拟机必须确保用户线程执行完毕
- 虚拟机不用等待守护线程执行完毕
- 如:后天记录操作日志,监控内存,垃圾回收等待…
/**
* @author 丶Anton
* @date 2021/4/15 12:11
* 线程守护 --> 上帝守护着你
*/
public class ThreadDefend {
public static void main(String[] args) {
//上帝
Thread god = new Thread(() -> {
while(true){
System.out.println("上帝守护着你");
}
});
//你
Thread you = new Thread(() -> {
for (int i = 10; i > 0; i--) {
System.out.println("你还剩" + i + "天生命");
}
System.out.println("========Goodbye,World!========");
});
//设置守护线程(上帝)
god.setDaemon(true);
//启动线程
god.start();
you.start();
}
}
总结:当用户线程死亡时,守护线程也会跟着死亡,不过守护死亡的时候会根据虚拟机关闭的时间产生一定延时。
5、线程同步【重点】
- 多个线程操作同一个资源
- 并发:同一个对象被多个线程同时操作
- 处理多个线程问题时,多个线程访问同一个对象,并且某些线程还想修改这个对象,这个时候我们就需要线程同步,线程同步其实就是一种等待机制,多个需要同时访问此对象的线程进入这个对象的等待池形成队列,等待前面线程使用完毕,下一个线程再使用
- 由于同一进程的多个线程共享同一块存储空间,在带啦地方病的同时,也带来了访问冲突问题,为了保证数据在方法被访问时的正确性,在访问时加入锁机制synchronized,当一个线程获得对象的排它锁,独占资源,其他线程必须等待,使用后释放锁即可,存在以下问题:
- 一个线程持有锁会导致其他所有需要此锁的线程挂起;
- 在多线程竞争下,枷锁,释放锁会导致比较多的上下文切换 和 调度延迟,引发起性能问题;
- 如果一个优先级高的线程等待一个优先级低的线程释放锁 会导致优先级倒置,引起性能问题。
/**
* @author 丶Anton
* @date 2021/4/15 13:24
* 线程同步
*/
public class ThreadSyn {
public static void main(String[] args) {
Ticket ticket = new Ticket(10);
new Thread(ticket).start();
new Thread(ticket).start();
new Thread(ticket).start();
}
}
class Ticket implements Runnable {
private int ticketNum; //票
private boolean flag = true; //停止标志位
public Ticket(int ticketNum) {
this.ticketNum = ticketNum;
}
//买票
public void bug(){
if(ticketNum == 0){ //当无票了,则停止线程
flag = false;
return;
}
System.out.println(Thread.currentThread().getName() + "买到第:" + ticketNum-- + "张票");
}
@Override
public void run() {
Thread.currentThread();
while(flag){
//同步代码块
synchronized (this) {
bug();
}
}
}
//方式二
private synchronized void bug(){
if(ticketNum == 0){
flag = false;
return;
}
System.out.println(Thread.currentThread().getName() + "买到第:" + ticketNum-- + "张票");
}
@Override
public void run() {
Thread.currentThread();
while(flag){
bug();
}
}
}
/**
* @author 丶Anton
* @date 2021/4/15 13:57
* 银行取钱问题
*/
public class ThreadBankWithdrawal {
public static void main(String[] args) {
Account account = new Account(50);
User user1 = new User(0,account, 50);
User user2 = new User(0,account, 50);
new Thread(user1).start();
new Thread(user2).start();
}
}
//账户
class Account{
public int totalAmount;
public Account(int totalAmount) {
this.totalAmount = totalAmount;
}
}
//用户
class User implements Runnable {
//当前用户手里的钱
private int money;
//账户上的钱
private Account account;
//要取的钱
private int getMonet;
public User(int money, Account account, int getMonet){
this.money = money;
this.account = account;
this.getMonet = getMonet;
}
@Override
public void run() {
synchronized (account) {
if (account.totalAmount < getMonet) {
System.out.println("余额不足,无法取钱.剩余:" + account.totalAmount);
return;
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
account.totalAmount -= getMonet;
money += getMonet;
System.out.println(Thread.currentThread().getName() + "取钱成功,账户余额:" + account.totalAmount);
System.out.println(Thread.currentThread().getName() + "手里的前:" + money);
}
}
}
总结:synchronized默认锁的是this,同步代码块可以锁任何对象
5.1、死锁
多个线程自占有一些共享资源,并且互相等待其他线程占有的资源才能运行,而导致两个或者多个线程都在等待对方释放资源,都停止执行的情形,某一个同步块同时拥有“两个以上对象锁”时,就可能会发生“死锁”现象
一句话:多个线程互相抱着对方需要的资源,然后形成僵持。
产生死锁代码:
public class ThreadDeadlock {
public static void main(String[] args) {
String a = "挖掘机";
String b = "跑车";
new Thread(() -> {
String threadName = Thread.currentThread().getName();
synchronized (a) {
System.out.println(threadName + "拿到:" + a + "\n准备拿:" + b);
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
//A锁中抱着Therda-0想要拿到的锁a,又想同时拿到b锁
//线程执行到这里时就会产生僵持状态
synchronized (b) {
System.out.println(threadName + "拿到:" + b);
}
}
}).start();
new Thread(() -> {
String threadName = Thread.currentThread().getName();
synchronized (b) {
System.out.println(threadName + "拿到:" + b + "\n准备拿:" + a);
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
//B锁中抱着Therda-0想要拿到的锁b,又想同时拿到a锁
//线程执行到这里时就会产生僵持状态
synchronized (a) {
System.out.println(threadName + "拿到:" + a);
}
}
}).start();
}
}
解决方案
/**
* @author 丶Anton
* @date 2021/4/15 14:56
*/
public class ThreadDeadlock {
public static void main(String[] args) {
String a = "挖掘机";
String b = "跑车";
new Thread(() -> {
String threadName = Thread.currentThread().getName();
synchronized (a) {
System.out.println(threadName + "拿到:" + a + "\n准备拿:" + b);
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//让Thread-0释放a锁, 同时让Thread-1释放b锁,作为交换
synchronized (b) {
System.out.println(threadName + "拿到:" + b);
}
}).start();
new Thread(() -> {
String threadName = Thread.currentThread().getName();
synchronized (b) {
System.out.println(threadName + "拿到:" + b + "\n准备拿:" + a);
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//让Thread-0释放a锁, 同时让Thread-1释放b锁,作为交换
synchronized (a) {
System.out.println(threadName + "拿到:" + a);
}
}).start();
}
}
总结死锁避免方法:
- 产生死锁的四个必要条件
- 互斥条件:一个资源每次只能被一个进程使用
- 请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放。
- 不剥夺条件:进程已经获得的资源,在未使用完之前,不能强行剥夺
- 循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。
- 以上四个条件,只需要破其中的任意一种就可以避免死锁发生
5.2、Lock锁
- 从JDK5.0开始,Java提供了更强大的线程同步机制——通过显示定义同步锁对象来实现同步。同步锁使用Lock对象充当
- Java.util.concurrent.locks.Lock接口是控制多个线程对共享资源进行访问的工具。锁提供了对共享资源的独占访问,每次只能有一个线程对Lock对象加锁,线程开始访问共享资源之前应先获得Lock对象
- ReentrantLock类实现了Lock,它拥有与synchronized相同的并发性和内存语义,在实现线程安全的控制中,比较常用的是ReentrantLock,可以显示加锁、释放锁
/**
* @author 丶Anton
* @date 2021/4/15 15:21
*/
public class ThreadLock implements Runnable {
private final ReentrantLock ReentrantLock = new ReentrantLock();
private int ticket = 10;
public static void main(String[] args) {
ThreadLock ThreadLock = new ThreadLock();
new Thread(ThreadLock).start();
new Thread(ThreadLock).start();
new Thread(ThreadLock).start();
}
@Override
public void run() {
try {
//加锁
ReentrantLock.lock();
while (ticket > 0) {
System.out.println(Thread.currentThread().getName() + ":获取票:" + ticket--);
Thread.sleep(1000);
}
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
//解锁
ReentrantLock.unlock();
}
}
}
总结:lock锁只可以锁代码块,不可以锁方法。标准方法必须在try findlly里写。不管运行如果线程都必须解锁。
5.3、Lock锁与sunchronized锁的对比
Lock是显式锁(手动开启和关闭锁,别忘记关闭锁)synchronized是隐式锁,除了作用于自动释放
Lock只有代码块锁,synchronized有代码块和方法锁
使用Lock锁,JVM将花费更少的时间来调度线程,性能更好。并且具有更好的扩展性(提供更多的子类)
优先顺序:
Lock --> 同步代码块(已经进入方法体,分配了相应资源) --> 同步方法(在方法体之外)
6、线程协作通信
6.1、生产者消费者问题
- 假设仓库中只能存放一件产品,生产者将生产出来的产品放入仓库,消费者将仓库中产品取走消费
- 如果仓库在红没有产品,则生产者将产品放入仓库,否则停止生产并等待,直到仓库中的产品被消费者取走为止。
- 如果仓库中放有产品,则消费则可以将产品取走消费,否则停止消费并等待,直到仓库中再次放入产品为止
6.1、问题分析:
这是一个线程同步问题,生产者和消费者共享同一个资源,并且生产者和消费者之间互相依赖,互为条件。
- 对于生产者,没有生产产品之前,要通知消费者等待,而生产了产品之后,又需要马上通知消费者消费
- 对于消费者,在消费之后,要通知生产者已经结束消费,需要生产新的产品以供消费。
- 在生产者消费者问题中,仅有synchronized是不够的
- synchronized可组织并法更新同一个共享资源,实现了同步
- synchronized不能实现不同线程之间的消息传递(通信)
Java提供了几个方法解决线程之间的通信问题
方法名 | 作用 |
---|---|
wait() | 表示线程一直等待,直到其他线程通知,与sleep不同,会释放锁 |
wait(long timeout) | 指等待毫秒数 |
notify() | 唤醒一个处于等待状态的线程 |
notifyAll() | 唤醒同一个对象上所调用wait()方法的线程,优先级高的线程优先唤醒 |
以上方法全部是Object类的方法
/**
* @author 丶Anton
* @date 2021/4/15 15:48
* 生产者消费者问题解决
*/
public class ProductionAndConsumption {
public static void main(String[] args){
Depot Depot = new Depot();
new Thread(new Producer(Depot)).start();
new Thread(new Consumer(Depot)).start();
}
}
//生产者
class Producer implements Runnable {
private Depot depot;
public Producer(Depot depot){
this.depot = depot;
}
@Override
public void run() {
for (int i = 0; i < 10; i++) {
try {
depot.producer();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
//消费者
class Consumer implements Runnable {
private Depot depot;
public Consumer(Depot depot){
this.depot = depot;
}
@Override
public void run() {
for (int i = 0; i < 10; i++) {
try {
depot.consumer();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
//仓库
class Depot{
public boolean flag = false;
public List list = new ArrayList();
//生产
public synchronized void producer() throws InterruptedException {
//如果仓库中有产品
if(flag){
this.wait(); //睡眠
}
//生产
for (int i = 0; i < 10; i++) {
list.add(i);
System.out.println(Thread.currentThread().getName() + "生产了:" + i);
}
flag = !flag;
//生产完毕 唤醒用户
this.notifyAll();
}
//消费
public synchronized void consumer() throws InterruptedException {
//如果仓库没中有产品
if(!flag){
this.wait(); //睡眠
}
//消费
int i = 0;
while(list.size() > 0){
System.out.println(Thread.currentThread().getName() + "消费了:" + list.remove(i));
}
flag = !flag;
//生产完毕 唤醒生产
this.notifyAll();
}
}
7、线程池
- JDK5.0起提供了线程池相关API:ExecutorService和Executprs
- ExecutorService:真正的线程池接口。常见子类ThreadPooExecutor
- void execute(Runnable command): 执行任务/命令,没有返回值,一般用来执行Runnable
- Future submit(Callable task):执行任务,有返回值,一般用来执行Callable
- void shutdown():关闭线程池
- Executors:工具类、线程池的工厂类,用于创建并返回不同类型的线程池
/**
* @author 丶Anton
* @date 2021/4/15 16:57
*/
public class ThreadPool {
public static void main(String[] args) {
//在池子中放入10个线程
ExecutorService service = Executors.newFixedThreadPool(10);
//执行 任务1
service.execute( () -> {
System.out.println(1);
});
//执行 任务2
service.execute( () -> {
System.out.println(2);
});
//关闭连接池
service.shutdown();
}
}