day13. 多线程
课前回顾:
Math:数学类
方法:数学计算
ceil()->向上取整
round()->四舍五入
max()->取较大值
min()->取较小值
abs()->绝对值
BigInteger:
处理比long型还大的数据
方法:
add:加法
subtract:减法
multiply:乘法
divide:除法
BigDecimal:
处理小数,防止精度损失
方法:
add:加法
subtract:减法
multiply:乘法
divide:除法->如果除不尽会报错
divide(BigDecimal,scala,舍入方式)
scala:保留几位小数
舍入方式:
直接舍去
四舍五入
向上+1
Date:日期类,精确到毫秒
构造:
Date():获取当前系统时间
Date(long time):设置时间,从时间原点开始算(我们是东八区,会比时间原点多8个小时)
方法:
setTime(long time):设置时间,从时间原点开始算(我们是东八区,会比时间原点多8个小时)
getTime:获取时间毫秒值
Calendar:日历类,抽象类
获取:getInstance
方法:
add:给指定的字段设置偏移量
get:获取指定字段的值
set:给指定字段设置值
SimpleDateFormat:日期格式化类
构造:
SimpleDateFormat("日期格式规则")
y:年 M:月 d:天 H:时 m:分 s:秒
yyyy-MM-dd HH:mm:ss-> 连接符可以随意,但是字母不能随意写
方法:
String format(Date) 将日期按照指定的格式去转成字符串
Date parse(String s):将符合规则的字符串转成Date对象
jdk8新日期类
LocalDate
获取:now() of(年,月,日)
get开头的:获取日期字段
with开头的:设置日期
puls:向后偏移
minus:向前偏移
Period:计算日期差值
Duration:计算时间差值
between:计算差值
DateTimeFormatter:日期格式化
Arrays:专门操作数组
sort:排序
toString:按照指定格式打印
binarySearch:二分查找
copyOf:数组扩容->底层依靠System.arrayCopy
System:
exit:退出jvm
currentTimeMills:获取系统时间毫秒值
gc:调用垃圾回收器
arrayCopy:数组赋值
今日重点:
1.三种多线程创建方式(继承,实现,匿名内部类)
2.会使用同步代码块去解决线程安全问题
3.会使用同步方法去解决线程安全问题
4.知道什么情况下出现死锁
5.知道wait方法和sleep方法的区别
6.完成等待唤醒案例
第一章.多线程基本了解
1.多线程之线程和进程
进程:进入到内存中运行的应用程序
线程:是进程中的一个执行单元,负责当前进程中程序的执行,一个进程中至少有一个线程。一个进程中是可以有多个线程的,这个应用程序也可以称之为多线程程序
并发: 同一个时刻多个线程同时操作了同一个数据
并行: 同一个时刻多个线程同时执行不同的程序
2.CPU调度
1.分时调度:指让所有的线程轮流获得cpu的使用权,并且平均分配每个线程占用的cpu的时间片
2.抢占式调度:java程序
多个线程去抢占CPU使用权,谁先抢到CPU使用权,谁先执行
优先级高的线程抢到CPU使用权几率大
3.主线程介绍
1.cpu和内存之间开辟一个专门为main方法服务的通道->主线程
public class Test01 {
public static void main(String[] args) {
for (int i = 0; i < 5; i++) {
System.out.println("哈哈哈哈");
}
System.out.println(Math.abs(-1));
}
}
第二章.创建线程的方式
1.创建线程的第一种方式(extends Thread)(重点)
1.创建一个类,extends Thread类
2.重写Thread中的run方法->在run中设置线程任务(该线程能干啥)
3.创建自己定义的线程类对象,调用start方法(开启线程,jvm会自动执行run方法)
4.start方法和run方法的区别
run()->仅仅是设置线程任务,没有开启线程的功能
start()->先开启线程,然后jvm自动调用run方法,执行线程任务
public class MyThread extends Thread{
@Override
public void run() {
for (int i = 0; i < 5; i++) {
System.out.println("MyThread线程执行了....."+i);
}
}
}
public class Test02 {
public static void main(String[] args) {
//创建线程类对象
MyThread myThread = new MyThread();
//调用Thread类中的start方法,开启线程,并执行run方法
myThread.start();
//myThread.run();
// ==============================
for (int i = 0; i < 5; i++) {
System.out.println("Main执行了..."+i);
}
}
}
2.多线程在内存中的运行原理
注意:
一个线程对象,不能连续start多次
如果还想创建新的线程,那么重新new一次线程对象,再调用start方法
3.Thread类中的方法
String getName() -> 获取线程名字
static Thread currentThread() -> 获取当前正在执行的线程对象
在哪个线程中用,获取的就是哪个线程对象
void setName(String name) -> 给线程设置名字
static void sleep(long millis) -> 线程睡眠
millis->设置的是线程睡多长时间->毫秒值
public class MyThread extends Thread{
@Override
public void run() {
for (int i = 0; i < 5; i++) {
try {
Thread.sleep(1000L);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(getName()+"...执行了"+i);
}
}
}
public class Test01 {
public static void main(String[] args) {
//创建线程类对象
MyThread myThread = new MyThread();
myThread.setName("尼古拉斯赵四");
//调用Thread类中的start方法,开启线程,并执行run方法
myThread.start();
// ==============================
for (int i = 0; i < 5; i++) {
try {
Thread.sleep(2000L);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"执行了..."+i);
}
}
}
问题:在run方法中为什么不能throws异常?
答案:因为Thread中的run方法没有抛异常,所以重写之后不能throws
4.创建线程的第二种方式(实现Runnable接口)(重点)
1.定义一个类,实现(implements)Runnable接口
2.重写Runnable接口中的run方法,设置线程任务
3.创建自定义类对象,将对象放到Thread对象中
4.调用Thread类中的start方法开启线程
Thread类中的构造:
Thread(Runnable target)
Thread(Runnable target, String name) -> 根据实现类创建Thread对象,设置线程名字
public class MyRunnable implements Runnable{
@Override
public void run() {
for (int i = 0; i < 5; i++) {
System.out.println(Thread.currentThread().getName()+"...执行了"+i);
}
}
}
public class Test01 {
public static void main(String[] args) {
MyRunnable myRunnable = new MyRunnable();
Thread thread = new Thread(myRunnable,"广坤");
thread.start();
for (int i = 0; i < 5; i++) {
System.out.println(Thread.currentThread().getName()+"...执行了"+i);
}
}
}
5.两种实现多线程的方式(区别)
1.方式1:继承Thread方式 -> 单继承,不能多继承,有局限性
2.方式2:实现Runnable接口 -> 解决了单继承的限制-> 一个类可以继承父类的同时实现一个或者多个接口
6.使用匿名内部类方式创建多线程
1.匿名内部类回顾:
new 父类/接口(){
重写方法
}.重写的方法;
或者:
父类/接口类型 对象名 = new 父类/接口(){
重写方法
}
对象名.重写的方法
public class Test01 {
public static void main(String[] args) {
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 5; i++) {
System.out.println("线程1执行了");
}
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 5; i++) {
System.out.println("线程2执行了");
}
}
}).start();
}
}
小结:
创建线程三种方式:
- 继承Thread类
a.创建一个类,继承Thread类
b.重写run方法,设置线程任务
c.创建线程类对象,调用start方法- 实现Runnable接口
a.创建一个类,实现Runnable接口
b.重写run方法,设置线程任务
c.创建实现类对象,将对象传递到Thread对象中
d.调用start方法开启线程- 匿名内部类形式:
new Thread(new Runnable(){
run(){}
}).start();
第三章.线程安全
1.线程安全问题的概述
1.当多个线程访问同一个资源的时候就会出现线程安全问题
2.线程安全问题的代码实现(重点)->有线程安全问题
public class Ticket implements Runnable{
int ticket = 100;
@Override
public void run() {
while(true){
if (ticket>0){
//买票
System.out.println(Thread.currentThread().getName()+"买了第"+ticket+"张票");
ticket--;
}
}
}
}
public class Test {
public static void main(String[] args) {
Ticket ticket = new Ticket();
Thread t1 = new Thread(ticket, "无忌");
Thread t2 = new Thread(ticket, "三丰");
Thread t3 = new Thread(ticket, "翠山");
//开启三个线程
t1.start();
t2.start();
t3.start();
}
}
3.线程安全问题的产生原理
cpu在多个线程之间做高速切换
4.解决线程安全问题的第一种方式使用同步代码块(重点)
1.格式:
synchronized(任意对象){
可能会出现线程安全的代码
}
2.任意对象->充当的就是锁对象
3.注意:想要实现线程安全问题,必须要保证是多个线程使用的是同一个锁对象
public class Ticket implements Runnable{
int ticket = 100;
//创建对象
Object obj = new Object();
@Override
public void run() {
while(true){
try {
Thread.sleep(100L);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (obj){
if (ticket>0){
//买票
System.out.println(Thread.currentThread().getName()+"买了第"+ticket+"张票");
ticket--;
}
}
}
}
}
public class Test {
public static void main(String[] args) {
Ticket ticket = new Ticket();
Thread t1 = new Thread(ticket, "无忌");
Thread t2 = new Thread(ticket, "三丰");
Thread t3 = new Thread(ticket, "翠山");
//开启三个线程
t1.start();
t2.start();
t3.start();
}
}
5.同步的原理
进了同步代码块就相当于抢到了锁,其他的线程就抢不到锁;出了同步代码块,相当于释放锁,其他的线程才有资格抢锁
6.解决线程安全问题的第二种方式:使用同步方法(重点)
1.1普通的同步方法
1.格式:
修饰符 synchronized 返回值类型 方法名(参数){
可能出现线程安全的代码
}
2.默认锁:this
public class Ticket implements Runnable{
int ticket = 100;
//创建对象
Object obj = new Object();
@Override
public void run() {
while(true){
try {
Thread.sleep(100L);
} catch (InterruptedException e) {
e.printStackTrace();
}
method();
}
}
//同步方法
public synchronized void method(){
if (ticket>0){
//买票
System.out.println(Thread.currentThread().getName()+"买了第"+ticket+"张票");
ticket--;
}
}
/*public void method(){
synchronized (this){
if (ticket>0){
//买票
System.out.println(Thread.currentThread().getName()+"买了第"+ticket+"张票");
ticket--;
}
}
}*/
}
public class Test {
public static void main(String[] args) {
Ticket ticket = new Ticket();
Thread t1 = new Thread(ticket, "无忌");
Thread t2 = new Thread(ticket, "三丰");
Thread t3 = new Thread(ticket, "翠山");
//开启三个线程
t1.start();
t2.start();
t3.start();
}
}
1.2.静态同步方法
1.格式:
修饰符 static synchronized 返回值类型 方法名(参数){
可能出现线程安全的代码
}
2.默认锁:当前类.class
public class Ticket implements Runnable{
static int ticket = 100;
//创建对象
Object obj = new Object();
@Override
public void run() {
while(true){
try {
Thread.sleep(100L);
} catch (InterruptedException e) {
e.printStackTrace();
}
method();
}
}
//同步方法
public static synchronized void method(){
if (ticket>0){
//买票
System.out.println(Thread.currentThread().getName()+"买了第"+ticket+"张票");
ticket--;
}
}
/*public static void method(){
synchronized (Ticket.class){
if (ticket>0){
//买票
System.out.println(Thread.currentThread().getName()+"买了第"+ticket+"张票");
ticket--;
}
}
}*/
}
public class Test {
public static void main(String[] args) {
Ticket ticket = new Ticket();
Thread t1 = new Thread(ticket, "无忌");
Thread t2 = new Thread(ticket, "三丰");
Thread t3 = new Thread(ticket, "翠山");
//开启三个线程
t1.start();
t2.start();
t3.start();
}
}
第四章.死锁(了解)
1.死锁介绍(锁嵌套就有可能产生死锁)
死锁指的是两个或者两个以上的线程在执行的过程中,由于竞争同步锁而产生的一种阻塞现象;如果没有外力的作用,他们将无法继续执行下去,这种情况就称之为死锁.
根据上图所示:线程T1正在持有R1锁,但是T1线程必须再拿到R2锁,才能继续执行;而线程T2正在持有R2锁,但是T2线程需要再次拿到R1锁,才能继续执行.这时两个线程会处于互相等待的状态,即死锁.在程序中的死锁将出现在同步代码块的嵌套中
2.死锁的分析
3.代码实现
public class LockA {
static LockA lockA = new LockA();
}
public class LockB {
static LockB lockB = new LockB();
}
public class DieThread implements Runnable{
private boolean flag;
public DieThread(boolean flag) {
this.flag = flag;
}
@Override
public void run() {
//线程1执行的
if (flag){
synchronized (LockA.lockA){
System.out.println("if...lockA");
synchronized (LockB.lockB){
System.out.println("if...lockB");
}
}
//线程2执行的
}else{
synchronized (LockB.lockB){
System.out.println("else...lockB");
synchronized (LockA.lockA){
System.out.println("else...lockA");
}
}
}
}
}
public class Test {
public static void main(String[] args) {
DieThread dieThread = new DieThread(true);
new Thread(dieThread).start();
DieThread dieThread1 = new DieThread(false);
new Thread(dieThread1).start();
}
}
知道什么情况下会产生死锁即可:锁嵌套
第五章.线程状态
1.线程状态介绍
当线程被创建并启动以后,它既不是一启动就进入了执行状态,也不是一直处于执行状态。在线程的生命周期中,有几种状态呢?在API中java.lang.Thread.State这个枚举中给出了六种线程状态:
这里先列出各个线程状态发生的条件,下面将会对每种状态进行详细解析。
线程状态 | 导致状态发生条件 |
---|---|
NEW(新建) | 线程刚被创建,但是并未启动。还没调用start方法。 |
Runnable(可运行) | 线程可以在java虚拟机中运行的状态,可能正在运行自己代码,也可能没有,这取决于操作系统处理器。 |
Blocked(锁阻塞) | 当一个线程试图获取一个对象锁,而该对象锁被其他的线程持有,则该线程进入Blocked状态;当该线程持有锁时,该线程将变成Runnable状态。 |
Waiting(无限等待) | 一个线程在等待另一个线程执行一个(唤醒)动作时,该线程进入Waiting状态。进入这个状态后是不能自动唤醒的,必须等待另一个线程调用notify或者notifyAll方法才能够唤醒。 |
Timed Waiting(计时等待) | 同waiting状态,有几个方法有超时参数,调用他们将进入Timed Waiting状态。这一状态将一直保持到超时期满或者接收到唤醒通知。带有超时参数的常用方法有Thread.sleep 、Object.wait。 |
Teminated(被终止) | 因为run方法正常退出而死亡,或者因为没有捕获的异常终止了run方法而死亡。或者调用过时方法stop() |
2.线程状态图
第六章.等待唤醒
一.等待唤醒案例分析(线程之间的通信)
二.等待唤醒案例实现
public class BaoZiPu {
//定义count,表示包子
private int count;
//定义flag,证明有么有包子
private boolean flag;
public BaoZiPu() {
}
public BaoZiPu(int count, boolean flag) {
this.count = count;
this.flag = flag;
}
//消费线程要调用的方法,证明消费包子
public void getCount() {
System.out.println("消费了第..."+count+"个包子");
}
//生产者要调用的方法,证明生产包子
public void setCount() {
count++;
System.out.println("生产了第........."+count+"个包子");
}
public boolean isFlag() {
return flag;
}
public void setFlag(boolean flag) {
this.flag = flag;
}
}
//生产者
public class Product implements Runnable{
private BaoZiPu baoZiPu;
public Product(BaoZiPu baoZiPu) {
this.baoZiPu = baoZiPu;
}
@Override
public void run() {
while(true){
try {
Thread.sleep(100L);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (baoZiPu){
if (baoZiPu.isFlag()==true){
//证明有包子,生产线程wait
try {
baoZiPu.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//出了if就要生产包子
baoZiPu.setCount();
//改变flag状态,为true,证明生产完毕,有包子了
baoZiPu.setFlag(true);
//唤醒消费线程
baoZiPu.notify();
}
}
}
}
//消费者
public class Consumer implements Runnable{
private BaoZiPu baoZiPu;
public Consumer(BaoZiPu baoZiPu) {
this.baoZiPu = baoZiPu;
}
@Override
public void run() {
while(true){
try {
Thread.sleep(100L);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (baoZiPu){
if (baoZiPu.isFlag()==false){
//证明没有包子,消费线程wait
try {
baoZiPu.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//出了if就要消费包子
baoZiPu.getCount();
//改变flag状态,为false,证明消费完毕,没有包子了
baoZiPu.setFlag(false);
//唤醒生产线程
baoZiPu.notify();
}
}
}
}
public class Test {
public static void main(String[] args) {
BaoZiPu baoZiPu = new BaoZiPu();
Product product = new Product(baoZiPu);
Consumer consumer = new Consumer(baoZiPu);
new Thread(product).start();
new Thread(consumer).start();
}
}
三.用同步方法改造等待唤醒案例
public class BaoZiPu {
//定义count,表示包子
private int count;
//定义flag,证明有么有包子
private boolean flag;
public BaoZiPu() {
}
public BaoZiPu(int count, boolean flag) {
this.count = count;
this.flag = flag;
}
//消费线程要调用的方法,证明消费包子
public synchronized void getCount() {
if (flag == false) {
//证明没有包子,消费线程wait
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//出了if就要消费包子
System.out.println("消费了第..." + count + "个包子");
//改变flag状态,为false,证明消费完毕,没有包子了
flag = false;
//唤醒生产线程
this.notify();
}
//生产者要调用的方法,证明生产包子
public synchronized void setCount() {
if (flag == true) {
//证明有包子,生产线程wait
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//出了if就要生产包子
count++;
System.out.println("生产了第........." + count + "个包子");
//改变flag状态,为true,证明生产完毕,有包子了
flag = true;
//唤醒消费线程
this.notify();
}
public boolean isFlag() {
return flag;
}
public void setFlag(boolean flag) {
this.flag = flag;
}
}
//生产者
public class Product implements Runnable{
private BaoZiPu baoZiPu;
public Product(BaoZiPu baoZiPu) {
this.baoZiPu = baoZiPu;
}
@Override
public void run() {
while(true){
try {
Thread.sleep(100L);
} catch (InterruptedException e) {
e.printStackTrace();
}
//生产
baoZiPu.setCount();
}
}
}
//消费者
public class Consumer implements Runnable{
private BaoZiPu baoZiPu;
public Consumer(BaoZiPu baoZiPu) {
this.baoZiPu = baoZiPu;
}
@Override
public void run() {
while(true){
try {
Thread.sleep(100L);
} catch (InterruptedException e) {
e.printStackTrace();
}
//消费
baoZiPu.getCount();
}
}
}
public class Test {
public static void main(String[] args) {
BaoZiPu baoZiPu = new BaoZiPu();
Product product = new Product(baoZiPu);
Consumer consumer = new Consumer(baoZiPu);
new Thread(product).start();
new Thread(consumer).start();
}
}