JAVA学习线路:day10多线程[2]

心得:
我是一名正在自学的java的即将毕业的大学生
总结笔记是为了让自己更好的理解和加深印象。可能不是那么美观,自己可以看懂就好
所有的文档和源代码都开源在GitHub: https://github.com/kun213/DailyCode上了。希望我们可以一起加油,一起学习,一起交流。

day10【多线程】

一、Lock接口

1、能够说出Lock接口的特点

java.util.concurrent.locks.Lock机制提供了比synchronized代码块和synchronized方法更广泛的锁定操作,同步代码块/同步方法具有的功能Lock都有,除此之外更强大,更体现面向对象。

2、能够使用Lock接口的方法

Lock锁也称同步锁,加锁与释放锁方法化了,如下:

  • 接口实现类:java.util.locks.lock.ReentrantLock

  • public void lock():加同步锁。

  • public void unlock():释放同步锁。

使用如下:

public class Ticket implements Runnable{
	private int ticket = 100;
	//Lock接口实现类
	private Lock lock = new ReentrantLock();
	/*
	 * 执行卖票操作
	 */
	@Override
	public void run() {
		//每个窗口卖票的操作 
		//窗口 永远开启 
		while(true){
			lock.lock();
			if(ticket>0){//有票 可以卖
				//出票操作 
				//使用sleep模拟一下出票时间 
				try {
					Thread.sleep(50);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
				//获取当前线程对象的名字 
				String name = Thread.currentThread().getName();
				System.out.println(name+"正在卖:"+ticket--);
			}
			lock.unlock();
		}
	}
}

二、线程通信,等待与唤醒

1、能够理解等待唤醒案例

包子案例:生产者和消费者

定义一个变量,包子铺线程完成生产包子,包子进行++操作;吃货线程完成购买包子,包子变量打印出来。
1. 当包子没有时(包子状态为false),吃货线程等待。
2. 包子铺线程生产包子(即包子状态为true),并通知吃货线程(解除吃货的等待状态)。
3. 保证线程安全,必须生产一个消费一个,不能同时生产或者消费多个。

案例代码:

包子铺类:变量flag只在类中使用,因此可以去掉get/set方法。

/**
 * 资源对象,包子
 * 被线程调用的
 *
 *
 *  flag =false 指示灯,标记
 *  指示线程,该做什么
 *  默认值false , 没有包子,需要生产线程进行生产,对变量count++
 *  生产线程,完成任务, 修改标志位= true (有包子),唤醒对方线程。
 *
 *  flag=true 指示线程,有包子,需要消费者线程进来消费,对变量输出打印
 *  消费已经完成,修改标志位 = false(无包子),唤醒对方线程。
 */

public class BaoZiPu {
    private int count;

    //定义布尔变量,标志位
    private boolean flag =false ;

    //get方法,被消费者线程调用,输出
    public synchronized void get() {
        //判断标志位,是否允许消费
        //flag是false,没有包子,不能消费
        //等,等生产完毕
       while (flag == false){
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println("消费第 "+count+" 个包子...");
        //修改标志位
       flag = false;
        this.notifyAll();//唤醒全部线程
    }
    //set方法,被生存者线程调用,++变量
    public synchronized void set() {
       while (flag == true){
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        count++;
        System.out.println("生产第 "+count+" 个包子");
         flag = true;
        this.notifyAll();//唤醒全部线程
    }

生产者类:

/**
 * 生产者线程
 * 调用包子铺资源对象的方法set
 *
 * java.lang.IllegalMonitorStateException 无效的监视器状态异常
 * 同步(对象) 锁对象,专业名词是监视器
 * 线程通信的方法 wait(), notify() 必须依赖锁对象调用!!
 * 不是锁对象调用,抛出异常
 */
public class Product implements Runnable {
    //创建资源对象
    private BaoZiPu baoZiPu;
    public Product(BaoZiPu baoZiPu){
        this.baoZiPu = baoZiPu;
    }

    public void run() {
        while (true) {
           baoZiPu.set();
        }
    }

消费者类:

/**
 * 消费者线程
 * 调用资源对象,包子铺方法get
 */
public class Customer implements Runnable {
    private BaoZiPu baoZiPu;
    public Customer(BaoZiPu baoZiPu){
        this.baoZiPu = baoZiPu;
    }
    @Override
    public void run() {
        while (true) {
            baoZiPu.get();
        }
    }
2、能够说出sleep和wait方法区别
  • sleep()是Thread类静态方法,不需要对象锁。
  • wait()方法是Object类的方法,被锁对象调用,而且只能出现在同步中。
  • 执行sleep()方法的线程不会释放同步锁。
  • 执行wait()方法的线程要释放同步锁,被唤醒后还需获取锁才能执行。

三、Condition接口

1、能够说出Condition接口的方法的特点

Condition接口方法和Object类方法比较

  • Condition可以和任意的Lock组合,实现管理线程的阻塞队列。
    • 一个线程的案例中,可以使用多个Lock锁,每个Lock锁上可以结合Condition对象。
    • synchronized同步中做不到将线程划分到不同的队列中。
  • Object类wait()和notify()都要和操作系统交互,并通知CPU挂起线程,唤醒线程,效率低。
  • Condition接口方法await()不和操作系统交互,而是让释放锁,并存放到线程队列容器中,当被signal()唤醒后,从队列中出来,从新获取锁后在执行。
  • 因此使用Lock和Condition的效率比Object要快很多。

四、线程池ThreadPool

1、能够理解线程池的思想

创建线程每次都要和操作系统进行交互,线程执行完任务后就会销毁,如果频繁的大量去创建线程肯定会造成系统资源开销很大,降低程序的运行效率。

线程池思想就很好的解决了频繁创建线程的问题,我们可以预先创建好一些线程,把他们放在一个容器中,需要线程执行任务的时候,从容器中取出线程,任务执行完毕后将线程在放回容器。

2、能够使用Executors类创建线程池

Callable接口类:

/**
 *线程执行的任务接口,类似于Runnable接口。
 *接口方法`public V call()throw Exception
 *线程要执行的任务方法
 *比起run()方法,call()方法具有返回值,可以获取到线程执行的结果。
 */
public class MyCallable implements Callable<String> {
    public String call()throws Exception{
        System.out.println("线程开启");
        return "线程结果字符串";
    }

实现类:

/**
 * java.util.concurrent.Callable<V>接口
 *
 *  V call() throws Exception;
 *  可以抛出异常,方法具有返回值
 *
 *  接口只能在线程池中使用
 *
 *  线程池对象 submit(  Callable接口 )提交线程任务
 *  方法的返回值是 Future接口,表示线程执行完毕后的返回值
 */
public class ThreadDemo {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        //线程池,提交任务,获取线程执行后的返回值
        ExecutorService es =  Executors.newFixedThreadPool(2);
        //接受submit方法的返回值
        Future<String> future =  es.submit(new MyCallable());
        //接口中的方法 get()获取线程的返回值
        String str = future.get();
        System.out.println(str);
    }

代码举例:需求:创建有2个线程的线程池,分别提交线程执行的任务,一个线程执行字符串切割,一个执行1+100的和。

实现Callable接口,字符串切割功能:

public class MyStringCallable implements Callable<String[]> {

    private  String str;

    public MyStringCallable(String str ){
        this.str = str;
    }

    @Override
    public String[] call() throws Exception {
        return str.split(" +");
    }
}

实现Callable接口,1+100求和:

public class MySumCallable implements Callable<Integer> {
    @Override
    public Integer call() throws Exception {
        int sum = 0;
        for(int x = 1 ; x<= 100; x++){
            sum+=x;
        }
        return sum;
    }
}

测试类:

 public static void main(String[] args) throws Exception {
     //创建有2个线程的线程池
     ExecutorService es = Executors.newFixedThreadPool(2);
     //提交执行字符串切割任务
     Future<String[]> futureString = es.submit(new MyStringCallable("aa bbb   cc    d       e"));
     System.out.println(Arrays.toString(futureString.get()));
     //提交执行求和任务
     Future<Integer> futureSum =  es.submit(new MySumCallable());
     System.out.println(futureSum.get());
     executorService.shutdown();
    }
3、能够说出Runnable和Callable接口的区别

相同点:两者都是接口;都可用来实现多线程程序;都需要调用Thread.start()启动线程;

不同点:

  1. 两者最大的不同点是:实现Callable接口的任务线程能返回执行结果;而实现Runnable接口的任务线程不能返回结果;
  2. Callable接口的call()方法允许抛出异常;而Runnable接口的run()方法的异常只能在内部消化,不能继续上抛;

五、Time定时器

1、能够使用Timer类定时执行任务
/**
 * 定时器: 在指定的时间隔内,进行操作
 * java.util.Timer 实现定时器
 * 构造方法直接new 无参数
 * 启动定时器方法:
 * 定时器任务         开始时间        毫秒间隔
 * void schedule(TimerTask task, Date firstTime, long period)
 * TimerTask参数,抽象类,传递子类对象,重写run()
    */
   public class TimerDemo {
   public static void main(String[] args) {
       Timer timer = new Timer();
       //开启定时器
       timer.schedule(new TimerTask(){
           public void run(){
               System.out.println("定时执行");
           }
       }, new Date(),3000);
   }
   }

   /*class MyTask extends TimerTask{
   public void run(){
       System.out.println("定时执行");
   }
   }*/

这是我的公众号,希望大家可以关注,让我们一起做最好的自我。
我也会把我自学视频分享在上面,供大家一起学习。

CodeBull

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值