心得:
我是一名正在自学的java的即将毕业的大学生
总结笔记是为了让自己更好的理解和加深印象。可能不是那么美观,自己可以看懂就好
所有的文档和源代码都开源在GitHub: https://github.com/kun213/DailyCode上了。希望我们可以一起加油,一起学习,一起交流。
day10【多线程】
今日学习内容-2020.10.9
一、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()启动线程;
不同点:
- 两者最大的不同点是:实现Callable接口的任务线程能返回执行结果;而实现Runnable接口的任务线程不能返回结果;
- 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("定时执行");
}
}*/
这是我的公众号,希望大家可以关注,让我们一起做最好的自我。
我也会把我自学视频分享在上面,供大家一起学习。