Java多线程进阶应用解析

内容导读

线程死锁

线程的安全与同步

线程通信

线程池应用

一、线程死锁

当第一个线程拥有A对象的锁标记,并等待B对象的所标记。同时第二个线程拥有B对象锁标记,同时等待A对象的锁标记时,产生死锁。

比如:小美跟小帅说:请你把笔借给我,我就借给你铅笔;小帅对小美说:你把铅笔借给我,我就借给你书。在这里产生了"死锁"。

public class TestDeadLock {
    public static void main(String[] args) {
        Boy boy = new Boy();
        Gilr gilr = new Gilr();
        boy.start();
        gilr.start();
    }
}

class  MyLock{
    static Object obj1 = new Object(); //左筷子
    static Object obj2 = new Object(); //右筷子
}

class Boy extends Thread{
    @Override
    public void run() {
        synchronized (MyLock.obj1) { //拥有左筷子锁
            System.out.println("boy获取到了左筷子,等待右筷子");
            synchronized (MyLock.obj2) {
                System.out.println("boy可以吃饭");
            }
        }
    }
}
class Gilr extends Thread{
    @Override
    public void run() {
        synchronized (MyLock.obj2) {//拥有右筷子锁
            System.out.println("Gilr拥有右筷子,等待左筷子");
            synchronized (MyLock.obj1) {
                System.out.println("Gilr可以吃饭");
            }
        }
    }
}

发生死锁条件:(a)循环条件 (b)不可剥夺条件 (c)保持状态 (d)互斥条件

打破死锁的条件任意一个,则可解除死锁。

二、线程的安全与同步

线程的同步,保证多个线程在共享数据时,每个线程获取的数据是正确,解决线程安全问题。

同步锁synchronized解决线程中数据的安全问题。

线程同步有两种方式:同步代码块与同步方法。

1、同步代码块方式

当前类中同步代码块用this,如果是类的对象则要传入对象

格式:synchronized(this/或另一对象){ //需要同步的语句; }

(1)案例解析:再次实现三个窗口同时卖票

public class TestTicket implements Runnable {	
    // 定义线程的准准备类	
    private int ticket = 10;	
    @Override	
    public void run() {		
        for (int i = 0; i < 10; i++) {			
            synchronized (this) {				
                if (ticket >= 0)	 
                    System.out.println(Thread.currentThread().getName() + "还剩:"+ticket--);			
            }		
        }	 
    } 
}

(2)测试一下

public class TestMain {	
    public static void main(String[] args) {		
    // 步骤1,创建一个实现了Runnable接口类的对象		
    TestTicket tt = new TestTicket();		
     // 步骤2:创建一个Thread线程类的对象,同时将实现了	 
     // Runnable接口的类的对象tt传入Thread的构造器中		
    Thread th1 = new Thread(tt, "窗口1");		
    Thread th2 = new Thread(tt, "窗口2");		
    Thread th3 = new Thread(tt, "窗口3");		
      th1.start();		
      th2.start();		
      th3.start();
     }
}

2、同步方法方式

public synchronized void run(){ 
   //需要同步的语句,其它代码省略 
}

三、线程通信

1、线程通信概述

  • 线程的通信:不同的线程的之间交互运行与数据传递
  • 如何保证线程交互运行时,相互通信的数据是安全的技术要点,运用线程的两个方法

(1)wait:一个线程等待另一个线程某个操作执行完毕。

(2)notify(notifyall):一个线程完成某个操作后,主动提示另一线程继续运行应用在同步代码块或同步方法中。

格式:

synchronized(this){ 语句; wait();}

synchronized(this){ 语句; notify();}

2、案例解析:实现多线程间通信与数据同步生产者/消费者模式,模拟银行存款与取款的情形

(1)创建一个帐户实体类

public class Account {
    //   帐户余额初始为0
    private int balance = 0;
    //  声明一个标志,用于表明是否已存(也可以不用此标志)
    private boolean flag = false;
    //    创建存款的方法,生产者
    public synchronized void saveMoney(int savem) {
        if (flag) {
            try {
                //已经有存款,存款线程等取款线程去取
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }//if
        //执行存款操作
        balance += savem;
        //输出提示信息
        System.out.println("已存:" + savem + " 余额:" + balance);
        flag = true;
        //通知取款线程继续取款
        notify();
    }
    //    创建取款的方法,消费者
    public synchronized void getMoney(int getm) {
        if (!flag) {//相当于flag==false
            try {
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }//if
        //执行取款操作
        if (getm <= balance) {
            balance -= getm;
        } else {
            getm = 0;
        }
        //输出取款信息
        System.out.println("已取:" + getm + " 余额:" + balance);
        flag = false;
        //通知存款线程继续存入
        notify();
    }
}

(2)创建取款子线程类,也就消费者角色

public class ConsumerThread implements Runnable {
    //声明共享资源对象
    private  Account acc;
    //创建有参构造器
    public ConsumerThread(Account acc){
        this.acc = acc;
    }
    @Override
    public void run() {
        //完成具体取款操作
        play();
    }//run
    public  void play(){
        for(int i=0;i<10;i++){
            int temp = (int)(Math.random()*60)+40;
            acc.getMoney(temp);
            try {
                Thread.sleep((int)(Math.random()*50)+100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }//for
    }//play
}

(3)创建存款子线程类,也就是生产者角色

public class ProduceThread implements Runnable {
    //声明共享的帐户对象
    private Account acc;
    public ProduceThread(Account acc) {
        this.acc = acc;
    }
    @Override
    public void run() {
        go();
    }//run
    public void go() {
        //具体实现存款操作
        for (int i = 0; i < 10; i++) {
            int temp = (int)(Math.random()*60)+40;
            acc.saveMoney(temp);
            try {
                Thread.sleep((int)(Math.random()*50)+100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }//go
}

(4)测试一下多线程间的通信

public class TestAccount {
    public static void main(String[] args) {
        //  创建共享资源对象
        Account acc = new Account();
        //   创建子线程对象
        ConsumerThread ct = new ConsumerThread(acc);
        ProduceThread pt = new ProduceThread(acc);
        Thread cons = new Thread(ct);
        Thread prod = new Thread(pt);
        cons.start();
        prod.start();
    }//main
}

说明:

(1)多线运行时,达到数据的同步,三个线程都只是获取资源,用synchronized就可以了。

(2)生产者与消费者共享数据源

生产者:生产一定的产品,并达到了消费者的产品需求就通知消费者(notify)。

消费者:根据生产者提供的产品及自身消费的数量来决定是否立即消费或继续等待,如果产品数量不足,则执行wait()方法,等待生产者通知产品已达到数量为止。

四、线程池应用

4.1 线程池概念

  • 如果有非常的多的任务需要多线程来完成,且每个线程执行时间不会太长,这样频繁的创建和销毁线程。
  • 频繁创建和销毁线程会比较耗性能。有了线程池就不要创建更多的线程来完成任务,因为线程可以重用。
  • 线程池用维护者一个队列,队列中保存着处于等待(空闲)状态的线程。不用每次都创建新的线程。

4.2 线程池的作用

  • 降低资源消耗:通过重复利用已经创建好的线程来降低线程创建和销毁造成的消耗。
  • 提高响应速度:当有任务时,任务可以不用等待线程创建直接执行。
  • 提高线程的可管理性:线程池可以进行统一的分配,调优和监控。

4.3 线程池中常见的类

1、常用的线程池接口和类,所在包java.util.concurrent

Executors(工具类)

ExecutorService(子接口)

ThreadPoolExecutor(子类):用于自定义

Executor(父接口):一般不用

2、通过Executors工具类,创建线程池服务对象,实现线程池功能

(1)newCachedThreadPool()

它是一种用来处理大量短时间工作任务的线程池,当无缓存线程可用时就会创建新的工作线程;

如果线程闲置的时间超过 60 秒,则被终止并移出缓存;长时间闲置时,这种线程池,不会消耗什么资源。

ExecutorService es1 = Executors.newCachedThreadPool();

(2)newFixedThreadPool(int nThreads)

生成指定数目的线程,并放入线程池中。

ExecutorService es2 = Executors.newFixedThreadPool(10);

(3)newScheduledThreadPool(int nThreads)

功能强大的定时任务,可以进行定时或周期性的工作调度,并使用多个工作线程。

ExecutorService es3 = Executors.newScheduledThreadPool(20);

(4)newSingleThreadScheduledExecutor()

可以进行定时或周期性的工作调度,它是单一工作线程。

ExecutorService es4 = Executors.newSingleThreadScheduledExecutor();

案例:计算1-1000结果,使用四个线程分别计算?即:第一个线程计算1-250 ,第二个 251~500.....

4.4 自定义线程池应用(阿里建议)

//阿里建议自定义线程池功能
public class MyTest {
    public static void main(String[] args) {
        //自定义线程池对象(核心线程池大小,线程池最大数,发呆时间,时间单位,阻塞队列)
        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(11, 20, 0L, TimeUnit.SECONDS, new ArrayBlockingQueue<Runnable>(10));
        for (int i = 0; i < 20; i++) {
            //执行线程
            threadPoolExecutor.execute(new Runnable() {
                @Override
                public void run() {
                    //模拟业务场景
                    System.out.println("你好:"+Thread.currentThread().getName());
                    try {
                        Thread.sleep(80);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                } });
        }//for
        关闭线程池
        threadPoolExecutor.shutdown();
    }//main
}

实战练习

1、创建一个Account类,一个余额balance属性(余额为0元),再创建两个子线程类。

要求:

(1)一个模拟存款,一个模拟取款,两次存款或取款操作间隔时间都为80ms内的随机值,分20次进行存款与取款操作。

(2)每次存入80-150元不等,取出90-200元不等,并输出每次的存款数、取款数、帐户余额数,程序要保证存取款的正确性(即存款数与取款数要匹配)。

2、选做题,编程实现模拟接力赛跑(多线程应用)

需求说明:

(1)多人参加1000米接力跑(比如:有五个人)

(2)每人跑100米,换下个选手

(3)每跑10米显示信息

更多精彩内容请关注本站!

  • 11
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值