一、如何创建多线程
1、继承Thread类
如果调用run方法,相当于还是只有一条main线程,会把run的线程当成一条普通对象,如下,t会执行完再往下执行,这样t就不是一个线程类,而是一个普通的对象,所以必须调用start方法,才是启动线程。start方法向cpu注册单独的线程,如果调用run方法就不会注册线程,而是当成普通的对象往下跑。
不要把主线程任务放在子线程之前,因为他会全部跑完子线程前边的代码,才会执行子线程的内容,所以要将主线程的任务放到子线程之后。
2、实现Runnable接口
3、实现Callable接口
二、线程的常用方法
1、为线程设置名字
线程有默认的名字,但是咱不能区分是哪个线程,咱们可以在执行线程中获取线程名字
注意setName一定要在start之前,否则有可能线程执行完了,名字才赋上,所以一定要在start之前setName
给线程赋名字,还有另外一种方式,Thread提供了有参构造器,如下:
2、sleep和join
sleep就是让线程暂停一下,到了时间,线程再继续执行,作用就是让程序跑的慢一点
join作用:让当前调用这个方法的线程先执行完,然后再继续往下执行,如下
调用join方法就会让当前线程执行完
三、线程安全问题
1、模拟取钱案例
出现线程问题了
需要线程同步来解决这个问题
四、线程同步解决线程安全问题
加锁的常见实现方式:
1、同步代码块
这样可以解决线程安全的问题。这个只是同一个账户,如果多个账户同时取钱,就不能用同一个锁名字了,如下:
程序会认为是一个账户,咱们可以用this,线程的共享资源
如果静态方法中加锁,咱们可以使用类名.class,如下:
因为本身静态方法就是通过类名被所有线程进行访问的。
2、同步方法
同步方法默认以this作为锁,只是你看不到而已。
如果是静态方法加上synchronized,默认的是类名.class作为锁。
同步代码块性能更好,都执行到同步代码块才开始等待。
3、Lock锁
为啥要在Account类中创建锁对象呢,因为一个账户就应该有一个锁。下边看一下如何加锁和解锁。
运行没有问题。建议锁对象加上final修饰,说明这个所对象是不允许被人替换的,显得你很专业。
还有一点,咱现在代码很少,感觉没啥问题,如果后期代码很多,可能会出现异常,如果出现异常没有进行解锁的话,其他线程就运行不了了,所以咱们最好将其放在try catch finally中,unlock放在finally中,这样才能保证线程出现异常下次还能执行。
五、线程通信
做好包子之后唤醒别人,等待自己,一定先唤醒别人,不然自己都睡着了,怎么唤醒别人呢
六、线程池
1、认识线程池
每个线程处理完,就复用线程处理下个任务
2、如何创建线程池
常用的实现类是ThreadPoolExecutor
第二个参数一般大于第一个参数,多出来的就是临时线程
3、线程池处理Runnable任务
三个核心线程,下边在执行第四个线程的时候,等前边的线程有执行完的,就执行第四个。
线程池执行完并不会死忙,想要死亡可以点击红点,也可以调用shutdown方法,这个方法会等前边的线程都执行完了才会关掉线程池。还有一个方法是shutdownNow,这个会立即关闭线程池,不管里面的线程是否执行完,这个肯定不是咱们想要的,代码演示如下:
shutdown方法,所有的线程都执行完,才关闭线程池
shutdownNow方法,执行了几个就关闭线程池了,后边的线程就会报错了。
测试下什么时候创建临时线程,这个需要将三个核心线程处于一直忙的状态,咱们加个线程等待的时间,进行测试
三个核心线程一直在忙,下边创建的四个线程是任务队列里面的,下边再创建线程的时候就到了创建临时线程的时间了,如下:
任务什么时候拒绝呢,当核心线程在忙,任务队列也占满了,临时线程在忙,再来新任务就要拒绝了
执行结果如下:
4、线程池处理Callable任务
七、并行、并发
八、线程的生命周期
线程sleep后,并不会释放锁,等待几秒后就自动醒了,继续执行。调用wait方法,会自动释放锁,等待几秒后,如果得到锁就会继续执行。