多线程

多线程详解

线程的实现

三种创建线程的方式

继承Thread类

  1. 继承Thread类

  2. 重写run()方法

  3. 调用start开启线程 :对象.start();

//继承Thread类
public class myThread extends Thread {
   @Override
   //重写run()方法
   public void run() {
       for (int i = 0; i <= 20; i++) {
           System.out.println("run程代码" + i);
      }
  }

   public static void main(String[] args) {
       //主线程
       //创建一个线程对象
       myThread myThread = new myThread();
       //调用start开启线程
       myThread.start();

       for (int i = 0; i <= 20; i++) {
           System.out.println("主线程代码" + i);
      }
  }
}
//结果:第一行是“主线程代码0”线程开启需要时间吧

 

网图的下载

下载的路径默认在项目目录下。

public class myThread2 extends Thread{
   private String url;
   private String name;

   public myThread2(String url,String file){
       //图片url
       this.url=url;
       //文件名
       this.name=file;
  }
   //网图下载demo
   @Override
   public void run() {
       WebDownLoad webDownLoad = new WebDownLoad();
       webDownLoad.downloader(url,name);
       System.out.println("下载了文件为"+name);
  }

   public static void main(String[] args) {
       myThread2 myThread1 = new myThread2("https://lmg.jj20.com/up/allimg/tp05/1Z9291R9191U7-0-lp.jpg","1.jpg");
       myThread2 myThread2 = new myThread2("https://lmg.jj20.com/up/allimg/tp05/1Z9291R9191U7-0-lp.jpg","2.jpg");
       myThread2 myThread3 = new myThread2("https://lmg.jj20.com/up/allimg/tp05/1Z9291R9191U7-0-lp.jpg","3.jpg");
       myThread1.start();
       myThread2.start();
       myThread3.start();
  }
}

public class WebDownLoad {
   public void downloader(String url,String name){
       try {
           FileUtils.copyURLToFile(new URL(url),new File(name));
      } catch (IOException e) {
           e.printStackTrace();
           System.out.println("IO异常,downloader方法出现问题");
      }
  }
}

实现Runnable类

推荐使用Runnable对象,因为java单继承具有局限性

  1. 实现Runnable接口

  2. 实现run()方法

  3. 创建线程对象,调用start()方法开启线程 :new Thread后用Thread的对象.start();

public class myRunnable implements Runnable{
   //实现Runnable接口
       @Override
       //实现run()方法
       public void run() {
           for (int i = 0; i <= 20; i++) {
               System.out.println("run程代码" + i);
          }
      }

       public static void main(String[] args) {
           //主线程
           //创建一个Runnable对象
           myRunnable myRunnable = new myRunnable();
           //创建一个线程对象
          Thread thread=new Thread(myRunnable);
           //调用start开启线程
           thread.start();
           for (int i = 0; i <= 20; i++) {
               System.out.println("主线程代码" + i);
          }
      }
  }

 

了解并发问题

多个线程操作同一个资源的情况下,线程不安全,数据紊乱

public class buyTicket implements Runnable{
   private int ticketNum=10;
   @Override
   public void run() {
       while (true){
           if (ticketNum<=0){
               break;
          }
           try {
               //延迟抢票
               Thread.sleep(200);
          } catch (InterruptedException e) {
               e.printStackTrace();
          }
      System.out.println(Thread.currentThread().getName()+"-->拿到了"+ticketNum--+"号票");
    }
}
   public static void main(String[] args) {
       buyTicket myRunnable = new buyTicket();
       Thread thread1 = new Thread(myRunnable);
       Thread thread2 = new Thread(myRunnable);
       Thread thread3 = new Thread(myRunnable);
       thread1.start();
       thread2.start();
       thread3.start();
       //会出现重复抢到同一张票的情况,这就是并发问题
//       Thread-2-->拿到了9号票
//       Thread-0-->拿到了8号票
//       Thread-1-->拿到了10号票
//       Thread-1-->拿到了6号票
//       Thread-2-->拿到了5号票
//       Thread-0-->拿到了7号票
//       Thread-0-->拿到了4号票
//       Thread-2-->拿到了2号票
//       Thread-1-->拿到了3号票
//       Thread-1-->拿到了1号票
//       Thread-2-->拿到了-1号票
//       Thread-0-->拿到了0号票
}
}

实现Callable类

  1. 实现Callable接口

  2. 重写call方法,需抛出异常

  3. 创建目标对象

  4. 创建执行服务:ExecutorService ser = Executors.newFixedThreadPool(1);

  5. 提交执行:Future<Boolean> result1 = ser.submit(t1);

  6. 获取结果:boolean r1 =result1.get();

  7. 关闭服务:ser.shutdownNow();

//使用Callable类实现图片下载
public class myCallable implements Callable<Boolean> {
       private String url;
       private String name;

       public myCallable(String url,String file){
           //图片url
           this.url=url;
           //文件名
           this.name=file;
      }
       //网图下载,重写call()
       @Override
       public Boolean call() {
           WebDownLoad webDownLoad = new WebDownLoad();
           webDownLoad.downloader(url,name);
           System.out.println("下载了文件为"+name);
           return true;
      }

       public static void main(String[] args) throws ExecutionException, InterruptedException {
           myCallable myThread1 = new myCallable("https://lmg.jj20.com/up/allimg/tp05/1Z9291R9191U7-0-lp.jpg","1.jpg");
           myCallable myThread2 = new myCallable("https://lmg.jj20.com/up/allimg/tp05/1Z9291R9191U7-0-lp.jpg","2.jpg");
           myCallable myThread3 = new myCallable("https://lmg.jj20.com/up/allimg/tp05/1Z9291R9191U7-0-lp.jpg","3.jpg");
           //创建执行服务,这里开启了三个线程
           ExecutorService ser = Executors.newFixedThreadPool(3);
           //提交执行
           Future<Boolean> result1 = ser.submit(myThread1);
           Future<Boolean> result2 = ser.submit(myThread2);
           Future<Boolean> result3 = ser.submit(myThread3);
           //获取结果
           boolean r1 =result1.get();
           boolean r2 =result2.get();
           boolean r3 =result3.get();
           System.out.println(r1);
           System.out.println(r2);
           System.out.println(r3);
           //关闭服务:
           ser.shutdownNow();
      }
}

 

静态代理

  1. 真实对象和代理对象都要实现同一个接口

  2. 代理对象要代理真实角色

好处:

  1. 代理对象可以做很多真实对象做不到的事情

  2. 真实对象可以专注做自己的事情

 

 

 

线程状态

创建状态:一般是new Thread创建

就绪状态:线程创建后调用start方法进入就绪状态,等待被调度执行

运行状态:线程被调度执行

阻塞状态:调用sleep、wait等方法使线程进入阻塞状态,状态解除后重新进入就绪状态等待CPU调度执行

死亡状态:线程中断或者结束,此线程再也不能重新启动

线程状态

观察线程状态

Thread thread = new Thread(()->System.out.println("线程执行中"));
//观察状态
Thread.State state = thread.getState();

 

 

线程方法

  • setPriority(int newPriority):更改线程优先级

  • static void sleep(long millis):在指定的毫秒数内让当前正在执行的线程休眠

  • void join():等待该线程终止

  • static void yield():暂停当前正在执行的线程对象,并执行其他线程

  • void interrupt():中断线程,最好别用这个方式

  • boolean isAlive:测试线程是否处于活动状态

 

停止线程

不推荐jdk提供的stop()、destroy()方法【已废弃】

推荐线程自己停下来

建议使用一个标志位进行终止变量,当flag=false,则终止线程运行。

public class TestStop implements Runnable{
   //1.线程中定义线程体使用的标识
   private boolean flag=true;
   
   @Override
   public void run(){
   //2.线程体使用该标识
       while(flag){
           System.out.println("线程运行中");
      }
  }
   //3.对外提供方法改变标识
   public void stop(){
       flag=false;
  }
   
}

线程休眠

  • sleep(时间)指定当前线程阻塞的毫秒数;

  • sleep存在异常InterruptedException;

  • sleep时间达到后线程进入就绪状态

  • sleep可以模拟网络延迟和倒计时等

  • 没个对象都有一个锁,sleep不会释放锁

 

线程礼让

  • 礼让线程,让当前正在执行的线程暂停,但不阻塞

  • 将线程从运行状态转化成就绪状态

  • 让CPU重新调度,礼让不一定成功,看CPU心情

 

JOIN

  • join合并线程,待此线程执行完成后,再执行其他线程,其他线程在此期间阻塞

  • 可以想象成插队

public class TestJoin implements Runnable{

       @Override
       public void run() {
           for (int i = 0; i < 100; i++) {
               System.out.println("Vip来了"+i);
          }
      }

       public static void main(String[] args) throws InterruptedException {
           TestJoin joinThread = new TestJoin();
           Thread Thread =new Thread(joinThread);
           //主程序
           for (int i = 0; i < 1000; i++) {
               if (i==200){
                   Thread.start();
                   Thread.join();
              }
               System.out.println("主线程running"+i);
          }
      }

  }

线程优先级

线程优先级不是固定就是谁先谁后的,这代表的是权重,权重越高,被CPU调度可能性越高

线程优先级用数字表示,范围是1-10

  • Thread.MIN_PRIORITY=1;

  • Thread.MAX_PRIORITY=10;

  • Thread.NORM_PRIORITY=5;

使用下列方法获取或者改变优先级

getPriority().setPriority(int Num)

 

守护(daemon)线程

  • 线程分为用户线程和守护线程

  • 虚拟机必须确保用户线程执行完毕

  • 虚拟机不用等待守护线程执行完毕

  • 如后台日志、监控内存、垃圾回收等等都是守护线程

//守护线程,上帝守护你一生
public class TestDaemon {

public static void main(String[] args) {

GodThread godThread = new GodThread();
MyThread myThread = new MyThread();
Thread thread = new Thread(godThread);
thread.setDaemon(true);//默认是false,表示不是守护线程,是用户线程,正常线程都是用户线程
thread.start();
new Thread(myThread).start();//结果是上帝在主线程结束后依然执行了一段时间,因为虚拟机停止需要时间
}

}
class GodThread implements Runnable{
@Override
public void run() {
while (true){
System.out.println("上帝保佑你");
}
}
}
class MyThread implements Runnable{
@Override
public void run() {
for (int i = 1; i < 36500; i++) {
System.out.println("活着");
}
System.out.println("===============GOODBYE!WORLD!=============");
}
}

 

 

 

线程同步(重点)

多个线程访问同一个对象这属于并发问题,如果某个线程要修改这个对象,这时就需要等待线程同步,多个需要同时访问此对象的线程进入这个对象的等待池形成队列,等待前面的线程使用完毕,下一个线程再使用。

锁(synchronized)

多个线程共享一块储存空间,为了保证数据在方法中被访问的正确性,加入了锁机制,当一个线程获得对象的排他锁,独占资源,其他线程必须等待,使用后释放锁即可,但也会存在以下问题:

  • 一个线程持有锁会导致其他所有需要此锁的线程挂起

  • 在多线程竞争下,加锁,释放锁会导致比较多的上下文切换和调度延时,引起性能问题

  • 如果一个优先级高的线程等待一个优先级低的线程释放锁会导致优先级倒置,引起性能问题

 

 

 

线程不安全案例

不安全买票
//常见线程安全问题,不安全买票
public class UnsafeBuyTicket {
public static void main(String[] args) {
BuyTicket buyTicket = new BuyTicket();
new Thread(buyTicket,"黄牛1").start();
new Thread(buyTicket,"黄牛2").start();
new Thread(buyTicket,"我").start();

}
}
class BuyTicket implements Runnable{

private int Ticket= 10;
boolean flag=true;
@Override
public void run() {
//买票
while (flag){
buy();
}
}
private void buy(){
if (Ticket<=0){
flag=false;
System.out.println("已经没票了");
return;
}
else {
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"已经购买到票"+Ticket--);
}
}
}
不安全的取钱
//不安全的取钱
public class UnSafeBank {
public static void main(String[] args) {
Account account = new Account("结婚基金", 600000);

Drawing drawing1 = new Drawing(account, 200000,"我取");
Drawing drawing2 = new Drawing(account, 600000,"我家人取");

drawing1.start();
drawing2.start();

}
}
//账户(模拟)
class Account{
int money; //余额,单位分
String name;
public Account(String name,int money){
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() {
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (account.money-drawingMoney<0){
System.out.println("余额不足");
return;
}else
//卡里的余额
account.money= account.money-drawingMoney;
//手里的钱
nowMoney=nowMoney+drawingMoney;
System.out.println(account.name+"账户余额"+account.money);
//this.getName()等于Thread.currentThread().getName()
System.out.println(this.getName()+"手里的现金"+nowMoney);
}
}
不安全集合
//不安全集合
public class UnsafeList {
public static void main(String[] args) {
ArrayList<String> list = new ArrayList<>();
for (int i = 0; i < 10000; i++) {
new Thread(()->list.add(Thread.currentThread().getName())).start();
}
Thread.sleep(4000);
System.out.println(list.size());
}
}

结果是小于10000的。

原因可能是new Thread(()->list.add(Thread.currentThread().getName())).start();时,

两个或多个线程同时执行,将多个元素添加到同一个内存空间

 

 

同步方法

由于private只能保证对象被方法访问,所以只需要针对方法提出一套机制:synchronized

synchronized就是关键字,它包括两种用法:synchronized方法和synchronized块

同步方法

synchronized方法控制对对象的访问,每个synchronized方法都必须获得调用该方法的对象的锁才能执行,否则线程会阻塞,方法一旦执行,就独占该锁,直到该方法返回才释放锁,后面被阻塞的线程才能获得这个锁,继续执行。

缺点是:如果将一个大的方法申明为synchronized将会影响效率,方法里面需要修改的内容才需要上锁,锁的太多容易浪费资源

 

同步块

synchronized块:synchronized(Obj){}

Obj称之为同步监视器

  • Obj可以是任何对象,但是推荐使用共享资源作为同步监视器

  • 同步方法中无需指定同步监视器,因为同步方法的同步监视器就是this,就是这个对象本身或者是class

同步监视器的执行过程:

  1. 第一个线程访问时,锁定同步监视器,执行其中代码

  2. 第二个线程访问时,发现同步监视器被锁定,无法访问

  3. 第一个线程访问完毕,解锁同步监视器

  4. 第二个线程访问时,发现同步监视器没有被锁定,然后锁定并访问

 

线程不安全案例解决

解决不安全买票

//常见线程安全问题,不安全买票
public class UnsafeBuyTicket {
public static void main(String[] args) {
BuyTicket buyTicket = new BuyTicket();

new Thread(buyTicket,"黄牛1").start();
new Thread(buyTicket,"黄牛2").start();
new Thread(buyTicket,"我").start();

}
}
class BuyTicket implements Runnable{

private int Ticket= 10;
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 (Ticket<=0){
flag=false;
System.out.println("已经没票了");
return;
}
Thread.sleep(100);
System.out.println(Thread.currentThread().getName()+"已经购买到票"+Ticket--);

}
}

解决不安全的取钱

//这里需要synchronized同步块,因为对象不是单一的,不能单单锁方法
public class UnSafeBank {
public static void main(String[] args) {
Account account = new Account("结婚基金", 600000);

Drawing drawing1 = new Drawing(account, 200000,"我取");
Drawing drawing2 = new Drawing(account, 500000,"我家人取");

drawing1.start();
drawing2.start();

}
}
//账户(模拟)
class Account{
int money; //余额,单位分
String name;
public Account(String name,int money){
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块的使用,使账户作为一个同步块,执行完了才能被下一个对象上锁并调用
synchronized (account){
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (account.money-drawingMoney<0){
System.out.println("余额不足");
return;
}else
//卡里的余额
account.money= account.money-drawingMoney;
//手里的钱
nowMoney=nowMoney+drawingMoney;
System.out.println(account.name+"账户余额"+account.money);
//this.getName()等于Thread.currentThread().getName()
System.out.println(this.getName()+"手里的现金"+nowMoney);
}
}
}

解决不安全集合

public class UnsafeList {
public static void main(String[] args) throws InterruptedException {
ArrayList<String> list = new ArrayList<>();
for (int i = 0; i < 60000; i++) {
new Thread(()->{
synchronized (list){
list.add(Thread.currentThread().getName());
}
}).start();
}
Thread.sleep(4000);
System.out.println(list.size());
}
}

 

 

 

 

 

死锁

某一个同步代码块同时拥有"两个以上对象的锁"时,就有可能发生死锁的问题。

死锁案例

//死锁:多个线程互相抱着对方需要的资源,然后形成僵持
public class DeadLock {
public static void main(String[] args) {
Makeup girl1 = new Makeup(0,"灰姑娘");
Makeup girl2 = new Makeup(1,"白雪公主");
girl1.start();
girl2.start();
}
}
//口红类
class Lipstick{

}
//镜子类
class Mirror{

}
//化妆
class Makeup extends Thread{
//需要的资源
static Lipstick lipstick=new Lipstick();
static Mirror mirror=new Mirror();

int choice;//选择
String girlName;//使用化妆品的人
Makeup(int choice,String girlName){
this.choice=choice;
this.girlName=girlName;
}
@Override
public void run() {
try {
makeup();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//化妆模拟死锁,互相持有对方的锁,且需要拿到对方的资源
private void makeup() throws InterruptedException {
if (choice==0){
synchronized (lipstick){
System.out.println(this.girlName+"获得口红的锁");
Thread.sleep(1000);
synchronized (mirror){
//一秒后想获得镜子
System.out.println(this.girlName+"获得镜子的锁");
}
}
}else {
synchronized (mirror){
System.out.println(this.girlName+"获得镜子的锁");
Thread.sleep(2000);
synchronized (lipstick){
//一秒后想获得口红
System.out.println(this.girlName+"获得口红的锁");
}
}
}
}
}

解决死锁案例

将同步块里共享的资源对象拿出来即可

//死锁:多个线程互相抱着对方需要的资源,然后形成僵持
public class DeadLock {
public static void main(String[] args) {
Makeup girl1 = new Makeup(0,"灰姑娘");
Makeup girl2 = new Makeup(1,"白雪公主");
girl1.start();
girl2.start();
}
}
//口红类
class Lipstick{

}

//镜子类
class Mirror{

}

//化妆
class Makeup extends Thread{
//需要的资源
static Lipstick lipstick=new Lipstick();
static Mirror mirror=new Mirror();

int choice;//选择
String girlName;//使用化妆品的人
Makeup(int choice,String girlName){
this.choice=choice;
this.girlName=girlName;
}
@Override
public void run() {
try {
makeup();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//化妆模拟死锁,互相持有对方的锁,且需要拿到对方的资源
private void makeup() throws InterruptedException {
if (choice==0){
synchronized (lipstick){
System.out.println(this.girlName+"获得口红的锁");
Thread.sleep(1000);
}
synchronized (mirror){
//一秒后想获得镜子
System.out.println(this.girlName+"获得镜子的锁");
}
}else {
synchronized (mirror){
System.out.println(this.girlName+"获得镜子的锁");
Thread.sleep(2000);
}
synchronized (lipstick){
//一秒后想获得口红
System.out.println(this.girlName+"获得口红的锁");
}
}
}
}

死锁的避免方法

产生死锁的四个条件:

  1. 互斥条件:一个资源每次只能被一个进程使用

  2. 请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放

  3. 不剥夺条件:进程已获得的资源,在未使用完成前,不能强行剥夺

  4. 循环等待条件:若干个进程之间形成一种头尾相接的循环等待资源关系

上面列出的死锁的四个条件,我们只需要破其中一个或多个条件就可以避免死锁发生。

 

Lock

  • 从jdk1.5开始,java提供了更强大的线程同步机制——通过显式定义同步锁对象来实现同步,同步锁使用Lock对象充当

  • java.util.concurrent.locks.Lock接口是控制多个线程对共享资源进行访问的工具。锁提供了对共享资源的独占访问,每次只能有一个线程对Lock对象加锁,线程开始访问共享资源前应先获得Lock对象

  • ReentrantLock实现了Lock,它拥有与synchronized相同的并发性和内存语义,在实现线程安全的控制中,比较常用的是ReentrantLock,可以显式加锁,释放锁

 

Lock对象使用的案例

使用 //定义Lock锁(保证私有并定义为不可修改) private final ReentrantLock lock =new ReentrantLock();

try{lock.lock();//加锁 +不安全代码}

finally{lock.unlock();//解锁}完成Lock对象的使用

public class TestLock {
public static void main(String[] args) {
TestLock2 testLock2 = new TestLock2();
new Thread(testLock2).start();
new Thread(testLock2).start();
new Thread(testLock2).start();
}
}
class TestLock2 implements Runnable{
int ticketNum =10;
//定义Lock锁(保证私有并定义为不可修改)
private final ReentrantLock lock =new ReentrantLock();

@Override
public void run() {
while (true){
try {
lock.lock();//加锁
if (ticketNum>0){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(ticketNum--);
}else {
break;
}
}finally {
lock.unlock();//解锁
}

}
}
}

 

synchronized与Lock区别

  • Lock是显式锁,需要手动上锁解锁,synchronized是隐式锁,出了作用域自动释放

  • Lock只锁代码块,synchronized有代码块锁(同步块)和方法锁(关键字)

  • Lock性能更好,并且有很多功能丰富的子类供调度

  • 优先使用顺序:Lock>同步代码块>同步方法

 

 

线程协作

概念:生产者消费者,也就是消息队列里的知识

java提供了几个方法来解决线程之间的通信问题

wait():表示线程一直等待,直到其他线程通知,与sleep不同,会释放锁

wait(Long time):等待指定毫秒数

notify():唤醒一个处于等待状态线程

notifyAll():唤醒同一对象上所有调用wait()方法的线程,优先级别高的线程优先调度

注意:这些只能在同步代码块和同步方法中使用,否则会抛异常

 

管程法

利用生产者消费者以及缓冲器(消息队列)解决线程通信问题

//生产者,消费者,产品,缓冲器
public class GuanChengFa {
public static void main(String[] args) {
SynContainer container = new SynContainer();
new Producer(container).start();
new Consumer(container).start();
}
}
//生产者
class Producer extends Thread{
SynContainer container;
public Producer(SynContainer container){
this.container=container;
}

@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println("生产了"+i+"号鸡");
container.push(new Chicken(i));
}
}
}
//消费者
class Consumer extends Thread{
SynContainer container;
public Consumer(SynContainer container){
this.container=container;
}

@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println("消费了-->"+container.pop().id+"号鸡");

}
}
}
//产品
class Chicken{
int id;
public Chicken(){}
public Chicken(int id){
this.id=id;
}
}
//缓冲器
class SynContainer{
//需要一个容器大小
Chicken[] chickens=new Chicken[10];
//容器计数器
int count=0;
//生产者放入产品
public synchronized void push(Chicken chicken) {
//容器满了等待消费者消费
if (count==chickens.length){
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//容器没满,丢入产品
chickens[count]=chicken;
count++;
//可以通知消费者消费了
this.notifyAll();
}
//消费者消费产品
public synchronized Chicken pop(){
//判断能否消费
if (count==0){
//提示生产者生产,消费者开始等待
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//如果可以消费
count--;
Chicken chicken=chickens[count];
//通知生产者生产
this.notifyAll();
return chicken;
}
}

多个消费者生产者写法

//生产者,消费者,产品,缓冲器
public class GuanChengFa {
public static void main(String[] args) {
SynContainer container = new SynContainer();
Producer producer = new Producer(container);
Consumer consumer = new Consumer(container);
new Thread(producer).start();
new Thread(producer).start();
new Thread(consumer).start();
new Thread(consumer).start();
}
}
//生产者
class Producer implements Runnable{
//定义Lock锁(保证私有并定义为不可修改)
private final ReentrantLock lock =new ReentrantLock();
SynContainer container;
public Producer(SynContainer container){
this.container=container;
}
@Override
public void run() {
try {lock.lock();
for (int i = 0; i < 100; i++) {
System.out.println("生产了"+i+"号鸡");
container.push(new Chicken(i));
}
}finally {
lock.unlock();
}
}
}
//消费者
class Consumer implements Runnable{
//定义Lock锁(保证私有并定义为不可修改)
private final ReentrantLock lock =new ReentrantLock();
SynContainer container;
public Consumer(SynContainer container){
this.container=container;
}
@Override
public void run() {
try {lock.lock();
for (int i = 0; i < 100; i++) {
System.out.println("消费了-->"+container.pop().id+"号鸡");
}
}finally {
lock.unlock();
}
}
}
//产品
class Chicken{
int id;
public Chicken(){}
public Chicken(int id){
this.id=id;
}
}
//缓冲器
class SynContainer{
//需要一个容器大小
Chicken[] chickens=new Chicken[10];
//容器计数器
int count=0;
//生产者放入产品
public synchronized void push(Chicken chicken) {
//容器满了等待消费者消费
if (count==chickens.length){
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//容器没满,丢入产品
chickens[count]=chicken;
count++;
//可以通知消费者消费了
this.notifyAll();
}
//消费者消费产品
public synchronized Chicken pop(){
//判断能否消费
if (count==0){
//提示生产者生产,消费者开始等待
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//如果可以消费
count--;
Chicken chicken=chickens[count];
//通知生产者生产
this.notifyAll();
return chicken;
}
}

信号灯法

public class XinHaoDengFa {
/**
* 测试生产者消费者问题2:信号灯法,标志位解决
*/
public static void main(String[] args) {
TV tv=new TV();
new Player(tv).start();
new Watcher(tv).start();

}
}
//生产者:演员
class Player extends Thread{
TV tv;
public Player(TV tv){
this.tv=tv;
}
@Override
public void run() {
for (int i = 0; i < 20; i++) {
if(i%2==0){
this.tv.play("播放快手");
}else{
this.tv.play("播放抖音");
}
}
}
}
//消费者:观众
class Watcher extends Thread{
TV tv;
public Watcher(TV tv){
this.tv=tv;
}
@Override
public void run() {
for (int i = 0; i < 20; i++) {
tv.watch();
}
}
}
//产品:节目
class TV {

//演员表演,观众等待 T
//观众观看,演员等待 F
String voice;//表演的节目
boolean flag=true;
//表演
public synchronized void play(String voice){
if(!flag){
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("演员表演了:"+voice);
//通知观众观看
this.notifyAll();
this.voice=voice;
this.flag =!this.flag;
}
//观看
public synchronized void watch(){
if(flag){
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("观众观看了:"+voice);
//通知演员表演
this.notifyAll();
this.flag =!this.flag;
}
}

线程池

经常创建销毁线程会使用特别大的资源,可以提前创建好多个线程,使用时直接获取,用完就放回池中,避免频繁地创建销毁,重复利用。

好处

  • 提高响应速度

  • 降低资源消耗

  • 便于线程的管理: 1. corePoolSize:核心池大小 2. maximumPoolSize:最大线程数 3. keepAliveTime:线程没有任务时最多保持多久时间后会终止

使用线程池

  • JDK5提供了相关API:ExecutorService和Executors

  • ExecutorService:真正的线程池接口,常见子类ThreadPoolExecutor:

  1. void execute(Runnable command):执行任务/命令,没有返回值,一般用来执行Runnable。

  2. <T>Future<T> submit(Callable<T> task):执行任务,有返回值,一般用来执行Callable。

  3. void shutdown():关闭连接池

  • Executors:工具类,线程池工厂类,用于创建并返回不同类型的线程池

使用Runnable连接线程池

public class TestPool {
public static void main(String[] args) {
//创建一个线程数为10的线程池
ExecutorService executorService= Executors.newFixedThreadPool(10);
executorService.execute(new MyThread());
executorService.execute(new MyThread());
executorService.execute(new MyThread());
executorService.execute(new MyThread());
//关闭连接
executorService.shutdown();
}
}
class MyThread implements Runnable{
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
}

使用callable连接线程池前面讲过了

posted @ 2023-08-10 14:21  haohao2036  阅读(31)  评论(0编辑  收藏  举报