JAVA多线程
文章目录
一、线程创建
1.继承Thread类
步骤: 继承Thread类–>重写run()方法–>调用start开启线程。
例一:
public class TestThread_01 extends Thread{
public void run(){
//run方法体
for (int i = 0; i < 10; i++) {
System.out.println("我是子线程");
try {
//线程休眠函数,休眠200ms
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
//main主线程
public static void main(String[] args) {
//创建线程类对象
TestThread_01 test = new TestThread_01();
//调用start方法,开辟一个新的线程,新的线程自动调用run方法
test.start();
for (int i = 0; i < 10; i++) {
System.out.println("我是主线程");
try {
//线程休眠函数,休眠200ms
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
对于上述代码,按照步骤执行我们分析可推导:应该先输出十遍 ”我是子进程“ ,再输出十遍 ”我是父进程“,但真实的执行结果却是
并没有按照程序的先后顺序一步一步地执行,这是因为我们创建了一个子线程,这时该进程拥有两个子进程,CPU调度到哪个,哪个就会被执行。
例二:
//多线程下载网络图片
import org.apache.commons.io.FileUtils;
import java.io.File;
import java.net.URL;
//通过继承 Thread 实现多线程
public class TestThread_02 extends Thread{
private String url;//网络图片地址
private String name;//保存文件路径
public TestThread_02(String url, String name) {
this.url = url;
this.name = name;
}
public void run(){
WebDownloader webdownloader = new WebDownloader();
webdownloader.downloader(url,name);
System.out.println("下载了文件名为:"+name);
}
public static void main(String[] args) {
TestThread_02 test1 = new TestThread_02("https://www.baidu.com/img/" +
"PCtm_d9c8750bed0b3c7d089fa7d55720d6cf.png","1.png");
TestThread_02 test2 = new TestThread_02("https://www.baidu.com/img/" +
"PCtm_d9c8750bed0b3c7d089fa7d55720d6cf.png","2.png");
TestThread_02 test3 = new TestThread_02("https://www.baidu.com/img/" +
"PCtm_d9c8750bed0b3c7d089fa7d55720d6cf.png","3.png");
test1.start();
test2.start();
test3.start();
}
}
//下载器
class WebDownloader{
//下载方法
public void downloader(String url,String name){
try{
FileUtils.copyURLToFile(new URL(url),new File(name));
}catch (Exception e){
e.printStackTrace();
}
}
}
2、实现 Runnable 接口
步骤: 实现Runnable接口–>重写run方法–>创建Runnable接口的类实例–>将线程作为参数传入
例一:
package 多线程;
//通过实现Runnable接口实现多线程,重写run方法,执行线程需要丢入Runnable接口的实现类
public class TestThread_03 implements Runnable{
//重写run方法
@Override
public void run() {
for (int i = 0; i < 300; i++) {
System.out.println("子线程");
}
}
public static void main(String[] args) {
//创建Runnable接口的实现类对象
TestThread_03 testThread_03 = new TestThread_03();
//创建线程对象,通过线程对象开启我们的多线程
Thread thread = new Thread(testThread_03);
thread.start();
for (int i = 0; i < 300; i++) {
System.out.println("父线程");
}
}
}
在方法二与方法一中,我们都实现了多线程,但粗壮乃一些问题,例如当多个线程操作同一资源时,会出现重复操作。
例二:
//多个线程操作同一个资源
//买火车票的例子
//发现问题:多个线程操作同一资源时,线程不安全,数据紊乱,并发问题
public class TestThread_04 implements Runnable{
//火车票数
private int ticketNums = 10;
@Override
public void run() {
while(true){
if(ticketNums <=0){
break;
}
//模拟延时
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
//Thread.currentThread().getName()用于获取当前线程ID
System.out.println(Thread.currentThread().getName()+"-->拿到了第"+ ticketNums-- +"票");
}
}
public static void main(String[] args) {
TestThread_04 test = new TestThread_04();
new Thread(test,"线程1").start();
new Thread(test,"线程2").start();
new Thread(test,"线程3").start();
}
}
执行结果:
可以看到,总共10张票的情况下,线程一、二、三同时拿到了第十张票,这就属于数据紊乱(脏数据)
解决办法:通过增加flag标志位对公共资源进行上锁与解锁
例三:
//多个线程操作同一个资源
//买火车票案例改进
//通过增加flag标志位,对公共资源进行上锁与开锁
public class TestThread_04 implements Runnable{
//火车票数
private int ticketNums = 10;
//设置标记位
static boolean flag = true;
@Override
public void run() {
while(true){
//模拟延时
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 判断当前公共资源(火车票)是否被占用
if(flag) {
// 将公共资源上锁,其它线程使用时发现上锁,循环等待
flag = false;
//判断是否有票
if(ticketNums <=0) {
// 将公共资源解锁并退出
flag = true;
break;
}
//Thread.currentThread().getName()用于获取当前线程ID
System.out.println(Thread.currentThread().getName() + "-->拿到了第" + ticketNums-- + "票");
//解锁公共资源
flag = true;
}
}
}
public static void main(String[] args) {
TestThread_04 test = new TestThread_04();
new Thread(test,"线程1").start();
new Thread(test,"线程2").start();
new Thread(test,"线程3").start();
}
}
执行结果:
注意: 线程调度由CPU管理,任意两行代码之间,都可能出现线程切换,保证程序原子性在之后的线程锁中记载。
3、重写Callable方法(了解)
步骤:
1.实现Callable接口,需要返回值类型
2.重写call方法,需要抛出异常
3.创建目标对象
4.创建执行服务:ExecutorService ser = Executors.newFixedThreadPool(3);
5.提交执行: Future<Boolean> result1 = ser.submit(test1);
6.获取结果: boolean r1 = result1.get();
7.关闭服务:ser.shutdownNow();
例:
//多线程图片下载器
import java.util.concurrent.*;
import org.apache.commons.io.FileUtils;
import java.io.File;
import java.net.URL;
//通过继承 Thread 实现多线程
public class TestCallable implements Callable {
private String url;//网络图片地址
private String name;//保存文件路径
public TestCallable(String url, String name) {
this.url = url;
this.name = name;
}
@Override
public Boolean call() throws Exception {
//下载图片线程函数体
Downloader downloader = new Downloader();
downloader.downloader(url,name);
System.out.println("下载了图片:"+name);
return true;
}
public static void main(String[] args) {
TestCallable test1 = new TestCallable("https://www.baidu.com/img/" +
"PCtm_d9c8750bed0b3c7d089fa7d55720d6cf.png","1.png");
TestCallable test2 = new TestCallable("https://www.baidu.com/img/" +
"PCtm_d9c8750bed0b3c7d089fa7d55720d6cf.png","2.png");
TestCallable test3 = new TestCallable("https://www.baidu.com/img/" +
"PCtm_d9c8750bed0b3c7d089fa7d55720d6cf.png","3.png");
//创建执行服务 参数为创建线程池数量
ExecutorService ser = Executors.newFixedThreadPool(3);
//提交执行 执行线程方法返回值 //参数为线程
Future<Boolean> result1 = ser.submit(test1);
Future<Boolean> result2 = ser.submit(test2);
Future<Boolean> result3 = ser.submit(test3);
//获取返回结果 需要异常处理
try {
boolean r1 = result1.get();
boolean r2 = result2.get();
boolean r3 = result3.get();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}finally {
//关闭服务 销毁创建的线程池
ser.shutdownNow();
}
}
}
//下载器
class Downloader{
//下载方法
public void downloader(String url,String name){
try{
FileUtils.copyURLToFile(new URL(url),new File(name));
}catch (Exception e){
e.printStackTrace();
}
}
}
该方法的优点在于可以从线程处获取返回值
二、静态代理模式
1、总结
1.真实对象与代理对象都要实现同一个接口
2.代理对象要代理真实角色
2、优点
1.代理对象可以做很多真实对象做不了的东西
2.真实对象可以专注做一件事
3、代码示例
public class StaticProxy {
public static void main(String[] args) {
//创建个人示例
You you = new You(); //你要结婚
//创建婚庆公司实例
WeddingCompany weddingCompany = new WeddingCompany(you); //婚庆公司帮你结婚
//婚庆公司代理个人完成婚礼整体步骤
//个人作为参数加入到婚庆公司的计划中
weddingCompany.happyMarry();
}
}
// 创建结婚接口,用于规范
interface Marry{
void happyMarry();
}
//创建结婚对象实现结婚接口
class You implements Marry{
//ALT+INS自动实现接口函数,接口里的函数规范都是public的
@Override
public void happyMarry(){
System.out.println("小明今天结婚");
}
}
//创建婚庆公司实现结婚接口
//代理角色,帮助你结婚
class WeddingCompany implements Marry{
private Marry target;
//Marry接口引用可以指向实现了他的所有类
public WeddingCompany(Marry target) {
this.target = target;
}
@Override
public void happyMarry() {
System.out.println("婚庆公司布置会场");
this.target.happyMarry(); //这是真实对象
System.out.println("清理场地,并结尾款");
}
}
三、Lambda表达式
1、使用方法
为了减少匿名内部类的使用,让代码更加简洁对函数接口(只含有一个方法的接口)可以使用Lambda表达式创建实现该接口的临时类。
2、代码示例
public class TestLambda {
public static void main(String[] args) {
//普通方法
Like like = new Like();
like.lambda(2,1);
//lambda表达式方法
ILike like2 = (int a,float b)->{
System.out.println("I like lambda2");
};
// 简化1.当函数体内只有一行代码时可省略花括号,否则只能用代码块
like2 = (int a,float b)-> System.out.println("I like lambda2 "+ a);
like2.lambda(2,1);
// 简化2. 参数的类型可以省略
like2 = (a,b)-> System.out.println("I like lambda3 "+ a);
like2.lambda(2,1);
/*
简化3. 当只含有一个参数时,可以去除参数外面的小括号
like2 = a,b-> System.out.println("I like lambda4 "+ a);
like2.lambda(2,1);
*/
}
}
// 1.定义一个函数接口(只包含一个方法的接口)
interface ILike{
void lambda(int a,float b);
}
// 2.创建接口的实现类
class Like implements ILike{
@Override
public void lambda(int a,float b) {
System.out.println("I like lambda "+ a +" " + b);
}
}
四、Thread常用方法
1、线程停止
注意:
1.建议线程正常停止--->设置执行次数,避免出现死循环
2.建议使用标志位--->设置一个标志位
3.不要使用stop或者destroy等过时或JDK不建议使用的方法
例:
public class TestStop implements Runnable{
//1.设置一个标志位
private boolean flag = true;
@Override
public void run() {
int i =0;
while (flag){
System.out.println("执行"+Thread.currentThread().getName() +"第 " + ++i + " 次");
}
}
//2.设置一个公开的方法停止线程,转换标志位
public void stop(){
this.flag = false;
}
public static void main(String[] args) {
TestStop testStop = new TestStop();
Thread thread = new Thread(testStop,"线程例子");
thread.start();
for(int i=0;i<1000;i++){
System.out.println("main " + i);
if(i==900){
//调用stop方法停止线程
testStop.stop();
System.out.println("线程停止了");
}
}
}
}
2、观测线程状态 (State)
public class TestState {
public static void main(String[] args) {
Thread thread = new Thread(() -> {
for (int i = 0; i < 1; i++) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("/");
});
//观测线程状态
Thread.State state = thread.getState();
System.out.println(state);
//观察启动后状态
thread.start();
state = thread.getState();
System.out.println(state);
//只要线程状态不是终止,就一直输出
while (state != Thread.State.TERMINATED) {
//更新状态
state = thread.getState();
System.out.println(state);
}
}
}
3、线程礼让
注意:
注意线程礼让,是让线程从运行状态转换为就绪状态,再由CPU进行线程调度,CPU可能还会调度该线程运行,所以线程礼让不一定会成功
例:
public class TestYield {
public static void main(String[] args) {
MyYield myYield = new MyYield();
Thread thread1 = new Thread(myYield,"线程1");
Thread thread2 = new Thread(myYield,"线程2");
thread1.start();
thread2.start();
}
}
class MyYield implements Runnable{
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"线程开始执行");
//线程礼让
Thread.yield();
System.out.println(Thread.currentThread().getName()+"线程执行结束");
}
}
4、线程优先级
注意:
设置优先级会增加其再CPU调度中的权重,但不一定先执行!!!
所以优先级高的线程不一定先执行,但有很大概率先执行。
例:
public class TestPriority {
public static void main(String[] args) {
MyPriority myPriority = new MyPriority();
Thread thread = new Thread(myPriority,"自定义线程");
//设置现线程的优先级,注意:优先级只能为1-10,超出将报错
thread.setPriority(10); //类内静态常量 最大值:Thread.MAX_PRIORITY 最小值:Thread.MIN_PRIORITY;
//先设置优先级再启动线程
thread.start();
//输出主线程的优先级
System.out.println(Thread.currentThread().getName()+"的优先级为--->"+Thread.currentThread().getPriority());
}
}
class MyPriority implements Runnable{
@Override
public void run() {
// Thread.currentThread() 获取正在运行的线程对象 getPriority()获取线程优先级
System.out.println(Thread.currentThread().getName()+"的优先级为--->"+Thread.currentThread().getPriority());
}
}
5、线程合并(线程插队)
插队就是强制CPU中断当前线程,执行某个线程。
例:
public class TestJoin implements Runnable{
@Override
public void run() {
//模拟延时
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//等待3秒后,依然执行插队的子线程,说明插队的线程具有原子性
for (int i = 0; i < 5; i++) {
System.out.println("VIP线程来了");
}
}
public static void main(String[] args) throws InterruptedException {
TestJoin testJoin = new TestJoin();
Thread thread = new Thread(testJoin);
for(int i=0;i<10;i++){
if(i==5) {
thread.start();
//抛出异常
thread.join();
}
System.out.println("main线程"+Thread.currentThread().getName());
}
}
}
6、设置守护线程
虚拟机不用等待守护线程结束,当其他线程都结束时,虚拟机即关闭。
例:
public class TestDaemon {
public static void main(String[] args) {
God god = new God();
You you = new You();
//创建守护线程
Thread thread = new Thread(god);
//将上帝设为守护线程,当其余非守护线程结束时,上帝线程不管是否执行完成都一起结束
thread.setDaemon(true);
//开启守护线程
thread.start();
//创建用户线程并启动
new Thread(you).start();
}
}
//上帝
class God implements Runnable {
@Override
public void run() {
//上帝线程一旦开启,便会一直执行下去。
while (true) {
System.out.println("上帝保护着你");
}
}
}
//你
class You implements Runnable {
@Override
public void run() {
for (int i = 0; i < 36500; i++) {
System.out.println("你开心的活着");
}
System.out.println("========goodbye! world===========");
}
}
五、线程同步
定义及实现:
①线程同步:队列+锁
②优缺点:可以增加安全性,但会降低性能
③实现:修饰符 synchronized,用于控制对“对象”的访问,每个对象对应一把锁,每个被synchronized修饰的方法都必须获得调用该方法的对象的锁才能执行, 方法一旦执行,就独占该锁。
1、synchronized方法或块
例一:买票问题
public class SafeBuyTicket {
public static void main(String[] args) {
BuyTicket buyTicket = new BuyTicket();
Thread thread1 = new Thread(buyTicket,"小明");
Thread thread2 = new Thread(buyTicket,"小强");
Thread thread3 = new Thread(buyTicket,"小亮");
thread1.start();
thread2.start();
thread3.start();
}
}
class BuyTicket implements Runnable{
//票数
private int nums = 10;
//设置标志位
private boolean flag = true;
@Override
//买票
public void run() {
while(flag){
try {
buy();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
//加修饰符 synchronized ,变为同步方法,锁的是 this
private synchronized void buy() throws InterruptedException {
//判断是否有票
if(nums<=0){
flag = false;
return;
}
//模拟延时,sleep不释放锁
Thread.sleep(100);
System.out.println(Thread.currentThread().getName()+"买到了第"+ nums-- +"张票");
}
}
}
例二:列表问题
import java.util.ArrayList;
import java.util.List;
public class SafeList {
public static void main(String[] args) {
List<String> list = new ArrayList<String>();
for (int i = 0; i < 10000; i++) {
new Thread(()->{
//利用synchronized(obj){}块对公共资源list上锁
synchronized (list){
list.add(Thread.currentThread().getName());
//System.out.println(list.size());
}
}).start();
}
//若不加延时,可能出现主线程已经执行完毕,子线程还在执行,
// 但子线程中没有输出语句,所以最终小于10000
try{
Thread.sleep(3000);
}catch (InterruptedException e){
e.printStackTrace();
}
System.out.println(list.size());
}
}
例三:银行取款问题
// 两个人去银行取钱,账户
// 锁应该给要访问公共资源的代码块,在这个例子中,执行多线程的是银行,但公共资源为账户中的钱,
// 所以要用synchronized(obj){}块进行锁定
public class SafeBank {
public static void main(String[] args) {
//创建账户实例
Account account = new Account(100,"结婚基金");
//创建个人实例
Drawing you = new Drawing(account,50,"你");
Drawing yourGirlFriend = new Drawing(account,100,"女朋友");
yourGirlFriend.start();
you.start();
}
}
// 账户
class Account{
int money;
String name;
public Account(int money, String name) {
this.money = money;
this.name = name;
}
}
//银行:模拟取款
class Drawing extends Thread{
Account account; //账户
int drawingMoney; //取款金额
int nowMoney; //现在手里的现金
//构造函数
public Drawing(Account account, int drawingMoney, String name){
super(name);
this.account = account;
this.drawingMoney = drawingMoney;
}
@Override
public void run() {
// 对访问公共资源的代码块进行上锁
synchronized (this.account){
// 判断钱够不够
if(this.account.money - this.drawingMoney < 0){
System.out.println(Thread.currentThread().getName()+"取钱余额不足");
return;
}
//模拟延时
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
//卡内余额
this.account.money = this.account.money - this.drawingMoney;
//你手里的钱
this.nowMoney = nowMoney + drawingMoney;
}
System.out.println(this.account.name+"中余额为"+this.account.money);
//Thread.currentThread().getName() = this.getName()
System.out.println(this.getName()+"手里的现金"+this.nowMoney);
}
}
2、使用安全类型
例:JUC里面的安全类型(安全列表演示)
import java.util.concurrent.CopyOnWriteArrayList;
//测试JUC安全类型的集合
public class TestJUC {
public static void main(String[] args) throws InterruptedException {
//java.util.concurrent包下定义的安全列表类型
CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<String >();
for (int i=0;i<10000;i++){
new Thread(()->{
list.add(Thread.currentThread().getName());
}
).start();
}
Thread.sleep(3000);
System.out.println(list.size());
}
}
3、Lock锁
例:
import java.util.concurrent.locks.ReentrantLock;
public class TestLock {
public static void main(String[] args) {
BuyTicket buyTicket = new BuyTicket();
new Thread(buyTicket).start();
new Thread(buyTicket).start();
new Thread(buyTicket).start();
}
}
class BuyTicket implements Runnable{
private int ticketNums = 10;
//定义Lock锁 可重入锁
private final ReentrantLock lock = new ReentrantLock();
@Override
public void run() {
while(true){
try{
this.lock.lock();
//任意线程对ticketNums上锁后,其他线程想要访问其对应地址都不能实现,会在判断处等待
if(this.ticketNums>0){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(this.ticketNums--);
}else{
break;
}
}finally {
this.lock.unlock();
}
}
}
}
相比于synchronized的优势在于,Lock可以显式的加锁与解锁,使用起来更为灵活。
六、死锁
程序中,多个线程互相抱着对方需要的资源,而形成的一种僵持状态,成为死锁
1、产生死锁的四个必要条件及如何避免死锁
①互斥条件:一个资源每次只能被一个进程使用。
②请求与保持条件:一个进程因为请求资源而阻塞时,对已经获得的资源保持不放。
③不剥夺条件:进程已获得的资源,在未使用完之前,不能强行剥夺。
④循环等待条件:若干进程之间形成一种头尾相接的循环等待关系。
只要破坏其中的一个或多个条件,即可解除死锁。
2、死锁实例
//多个线程互相抱着对方需要的资源,形成僵持
//小红和小芳是好朋友(两个线程),她们同时要化妆(需要镜子和口红),同时伸手去拿镜子和口红。
//小红拿到了口红,小芳拿到了镜子
//小红停下来等待小芳镜子用完好获得镜子,继续化妆
//小芳停下来等待小红口红用完好获得口红,继续化妆
//谁都不礼让谁,陷入僵持状态
public class DeadLock {
public static void main(String[] args) {
Makeup makeup1 = new Makeup(0,"小红");
Makeup makeup2 = new Makeup(1,"小芳");
makeup1.start();
makeup2.start();
}
}
//口红
class Lipstick{
}
//镜子
class Mirror{
}
//化妆
class Makeup extends Thread{
//需要的资源只有一份,用static来保证0
static Lipstick lipstick = new Lipstick();
static Mirror mirror = new Mirror();
int choice; //选择
String name; //使用化妆品的人
public Makeup(int choice, String name) {
this.choice = choice;
this.name = name;
}
@Override
//重写run()方法
public void run() {
try {
makeup();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//化妆
private void makeup() throws InterruptedException {
if(this.choice==0){
synchronized (lipstick){
System.out.println(this.name + "拿到了口红,希望拿到镜子");
Thread.sleep(2000);
//等待两秒后,此时另外一个线程拿到了镜子
//另外一个线程在等待口红
//两个线程同时占用对方需要的资源并开始等待,陷入僵持
synchronized (mirror){
System.out.println(this.name + "拿到了镜子,可以开始化妆了");
Thread.sleep(2000);
}
}
}else{
synchronized (mirror){
System.out.println(this.name + "拿到了镜子,希望拿到口红");
Thread.sleep(2000);
synchronized (lipstick){
System.out.println(this.name + "拿到了口红,可以开始化妆了");
Thread.sleep(2000);
}
}
}
}
}
七、线程通信
1、管程法
//测试生产者消费者模型 --> 利用缓冲区解决
//需要 生产者、消费者、产品、缓冲区
public class TestPC {
public static void main(String[] args) {
Warehouse warehouse = new Warehouse();
new Thread(new Producer(warehouse)).start();
new Thread(new Consumer(warehouse)).start();
}
}
//生产者
class Producer implements Runnable{
Warehouse warehouse;
public Producer(Warehouse warehouse) {
this.warehouse = warehouse;
}
@Override
public void run() {
while(true){
this.warehouse.WarehousingProducts(new Product());
//模拟生产延时
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
//消费者
class Consumer implements Runnable{
Warehouse warehouse;
public Consumer(Warehouse warehouse) {
this.warehouse = warehouse;
}
@Override
public void run() {
while(true){
this.warehouse.OutboundProducts();
//模拟消费延时
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
//产品
class Product{
}
//仓库
class Warehouse{
//设置仓库大小,最多只能容纳10件产品
private Product []products = new Product[10];
//设置计数器
int count = 0;
//入库产品,因为要使用公共资源-仓库,所以使用方法锁
public synchronized void WarehousingProducts(Product product){
if(this.count == this.products.length){
//仓库满了,通知生产者停止生产并等待,通知消费者尽快消费
try {
//释放锁等待
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//如果仓库没满
this.products[count] = product;
count++;
System.out.println("生产者生产了产品,此时仓库有"+this.count+"件产品");
//生产完成,通知消费者消费
this.notify();
}
//出库产品
public synchronized void OutboundProducts(){
if(this.count==0){
//仓库是空的,没有产品可消费,通知消费者停止消费并等待,通知生产者抓紧时间生产
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//如果仓库里有产品
this.count--;
System.out.println("消费者消费了产品,此时仓库有"+this.count+"件产品");
//消费完成,通知生产者生产
this.notify();
}
}
2、信号灯法
//测试生产者消费者模型 --> 利用缓冲区解决
//需要 生产者、消费者、产品、缓冲区
public class TestPC {
public static void main(String[] args) {
Warehouse warehouse = new Warehouse();
new Thread(new Producer(warehouse)).start();
new Thread(new Consumer(warehouse)).start();
}
}
//生产者
class Producer implements Runnable{
Warehouse warehouse;
public Producer(Warehouse warehouse) {
this.warehouse = warehouse;
}
@Override
public void run() {
while(true){
this.warehouse.WarehousingProducts(new Product());
//模拟生产延时
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
//消费者
class Consumer implements Runnable{
Warehouse warehouse;
public Consumer(Warehouse warehouse) {
this.warehouse = warehouse;
}
@Override
public void run() {
while(true){
this.warehouse.OutboundProducts();
//模拟消费延时
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
//产品
class Product{
}
//仓库
class Warehouse{
//设置标记位
boolean flag = true;
//入库产品,因为要使用公共资源-仓库,所以使用方法锁
public synchronized void WarehousingProducts(Product product){
if(!flag){
//仓库满了,通知生产者停止生产并等待,通知消费者尽快消费
try {
//释放锁等待
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("生产者生产了产品");
//生产完成,通知消费者消费
this.flag = !this.flag;
this.notifyAll();
}
//出库产品
public synchronized void OutboundProducts(){
if(this.flag){
//仓库是空的,没有产品可消费,通知消费者停止消费并等待,通知生产者抓紧时间生产
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("消费者消费了产品");
//消费完成,通知生产者生产
this.flag = !this.flag;
this.notifyAll();
}
}
八、线程池
1、为什么要使用线程池
不使用线程池的问题: 经常创建和销毁线程,会使用特别大的资源,对程序的性能影响很大。
解决办法: 提前创建好多个线程,放入线程池中,使用时直接获取,使用完放回池中,避免频繁的创建销毁线程。
2、使用线程池的好处
①提高程序响应速度。
②降低资源消耗。
③便于线程管理(核心池大小、最大线程数、没有任务时县城多久被销毁)。
3、代码实例
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class TestPool {
public static void main(String[] args) {
//1.创建服务,创建线程池 工具类 创建线程池 参数为池容量
ExecutorService service = Executors.newFixedThreadPool(10);
//执行
service.execute(new MyThread());
service.execute(new MyThread());
service.execute(new MyThread());
service.execute(new MyThread());
//2.关闭连接
service.shutdown();
}
}
class MyThread implements Runnable{
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
}