Java多线程
- 一、多线程介绍
- 概念
- 并发
- 什么是主线程及子线程
- 二、线程的创建
- 通过继承Thread类实现多线程
- 通过Runnable接口实现多线程
- 线程的执行流程
- 线程的生命周期
- 线程的五个状态
- 三、线程的使用
- 终止线程
- 暂停线程执行
- 线程联合
- Thread类中其他常用方法
- 四、线程的优先级
- 五、守护线程
- 六、线程同步
- 实现线程同步
- 七、死锁及解决方案
- 死锁解决
一、多线程介绍
概念
在知道多线程之前,我们需要知道什么是线程,当我们运行一个程序时,我们运行程序的可执行文件后,程序就被加载到了cpu中,这时候计算机就产生了一个进程。那我们知道进程后,就好理解线程了,线程也叫轻量级进程,它是由进程产生的,一个进程可以创建多个线程,所以我们叫它多线程。那多线程先对与进程有什么区别呢?主要区别就是以下几点,1.线程比进程消耗的资源少。2.线程的通信是在同一地址空间上进行,所以通信快。3.资源是共享的。
并发
并发就是在同一时间同时做多件事,如果只有一个cpu那么计算机就会采取时间片轮转法来运行。
什么是主线程及子线程
主线程就是最先被启动的程序,即main对应的线程,他是程序开始就被执行的。而在主线程创建并启动的线程,一般就被称为子线程。
二、线程的创建
通过继承Thread类实现多线程
具体的步骤是:
1.继承Thread类定义线程类
2.重写类中的run()方法,这个方法也叫线程体
3.实例化线程并通过start()方法启动线程
具体流程就是这样,但是,纸上得来终觉浅,绝知此事要躬行,所以我们来使用idea浅浅测试一下吧!
由此我们可以看出主线程不一定是最后执行完的,当然子线程也不一定就是先执行完的,这些我们后面会提到
通过Runnable接口实现多线程
这个也是创建多线程的一种方式,话不多说,直接上图
看来运行之后,有小伙伴想知道具体的线程执行流程和生命周期吗?小编当然也准备了
线程的执行流程
线程的生命周期
我们人这一生也就是幼年、青年、壮年、老年这几个状态,线程的一生也就是这五个状态,不同的是,我们人只能是幼年,青年,壮年,老年这样。线程就不一样了,他可以在几个状态之间反复横跳。
线程的五个状态
1.新生状态
用new关键字建立线程对象后,就是新生状态,他有自己的内存空间,当调用start方法后就进入就绪状态。
2.就绪状态
就绪,就是已经准备好运行了,他处于线程就绪队列,当系统给他cpu之后,线程进入运行状态,并调用自己的run方法。就绪状态并不只有新建线程时会有,还有3种情况会进入就绪状态
a.阻塞线程:阻塞接触,进入就绪状态
b.运行线程:调用yield方法,进入就绪状态
c.运行线程:jvm将cpu资源从本线程切换带其他线程。
这就是我刚刚说的会反复横跳的原因,当然并不只这一个地方会反复横跳,等说到的时候我们再具体说
3.运行状态
线程执行run方法中的代码,知道调用其他方法而终止或等待某资源而阻塞或完成任务死亡。如果在时间片内没有执行完,就会被系统换下来到就绪队列中,成为就绪状态。也有可能由于某些导致阻塞的事件而进入阻塞状态
4.阻塞状态
阻塞指的是暂停一个线程的执行以等待某个条件发生(或资源就绪)。有4中原因会导致阻塞
a.执行sleep方法
b.执行wait方法
c.线程运行时,某个操作进入阻塞状态,比如IO流操作(read()/write()方法本身就是阻塞的方法),当引起阻塞的原因消失后,进入就绪状态,并不是直接继续运行。
d.join()线程联合:当某个线程执行结束后,才能继续执行,使用join()方法。
5.死亡状态
死亡是最后一个阶段,不能反复横跳了,导致死亡有两个原因
a.正常运行完run()中的代码
b.执行stop()或destroy()方法,不推荐,已经被jdk废弃
所以只有新生和死亡状态不能反复横跳,其他状态都是可以的。
三、线程的使用
终止线程
之前就说jdk提供的stop()和destroy()方法被废弃,所以通常我们自己定义一个变量,当这个变量变为false时,终止线程运行。
![stop方法](https://img-
blog.csdnimg.cn/bddb395b9d14434dbed66f6c87e6432d.png)
暂停线程执行
有两个方法可以暂停线程执行sleep()和yield()方法
区别是:sleep()方法可以让正在运行的线程进入阻塞,当时间到了进入就绪状态,而yield()方法则是直接让出cpu使用权,进入就绪状态
实际上运行yield方法无法保证一定达到让步,因为,让步的线程可能被线程调度程序再次选中
线程联合
当前线程邀请调用方法的线程优先执行,在调用方法的线程执行结束之前,当前线程不能再次执行。线程A在运行期间,可以调用B的join方法,让A和B联合。这样A就必须等B执行完毕才能继续执行。
举个栗子:儿子买烟
Thread类中其他常用方法
Thread.currentThread.getName():获取线程名字,也可以通过setName设置线程名
isAlive():判断是否处于活动状态,线程处于运行或者就绪状态就认为是存活。
四、线程的优先级
每一个线程都是有优先级的,我们可以为每一个线程定义优先级,但是并不能保证高优先级会在低优先级线程前执行。线程优先级用数字1-10表示,一个线程的缺省优先级是5。注意:不是优先级高就先执行,是执行的概率高。
获得线程优先级的方法是getPriority()
设置线程优先级的方法是setPriority()
五、守护线程
在Java中有两类线程,一类是用户线程,另一类是守护线程(垃圾回收线程就是典型的守护线程)。守护线程会随着用户线程的死亡而死亡。
六、线程同步
当多个线程访问同一个对象,并且还想修改这个对象,这时候我们就需要线程同步,线程同步就是一种等待机制,多个需要同时访问此对象的线程进入这个对象的等待池形成队列,等待前面的使用完,下一个线程再使用。
举个栗子:当两个人去同一个账户取钱,两个人同时取钱,第一个人取完,账户信息还没有更新,第二个人又取了,这就导致账户只有一百块钱,但是两个人一共取出了两百块钱,使用线程同步就是让第一个人取钱时不许其他人用这个账户,等第一个人用完,第二个人才能使用。
实现线程同步
语法:
synchronized(锁对象){同步代码}
我们就用上面提到的例子来写这个代码
class Account {
private String account;
private double balance;
public Account(String account, double balance) {
this.account = account;
this.balance = balance;
}
public String getAccount() {
return account;
}
public void setAccount(String account) {
this.account = account;
}
public double getBalance() {
return balance;
}
public void setBalance(double balance) {
this.balance = balance;
}
public Account() {
}
}
//取款线程
class DrawThread extends Thread{
private Account account;
private double drawMoney;
public DrawThread(String name,Account account,double drawMoney){
super(name);
this.account=account;
this.drawMoney=drawMoney;
}
@Override
public void run() {
if (this.drawMoney<=this.account.getBalance()){
System.out.println(this.getName()+"取钱成功"+this.drawMoney);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
this.account.setBalance(this.account.getBalance()-this.drawMoney);
System.out.println("余额为"+this.account.getBalance());
}else {
System.out.println("余额不足");
}
}
}
public class DrawMoneyThread {
public static void main(String[] args) {
Account account = new Account("1234", 1000);
new DrawThread("老公",account,800).start();
new DrawThread("老婆",account,800).start();
}
}
当我们不加synchronized时就会出现这个情况当我们加上之后
@Override
public void run() {
synchronized (this.account){
if (this.drawMoney<=this.account.getBalance()){
System.out.println(this.getName()+"取钱成功"+this.drawMoney);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
this.account.setBalance(this.account.getBalance()-this.drawMoney);
System.out.println("余额为"+this.account.getBalance());
}else {
System.out.println("余额不足");
}
}
七、死锁及解决方案
死锁指的是:多个线程各自占有一些资源,并且相互等待其他线程占有的资源才能进行,导致都在等对方释放资源而都停止执行。
举个栗子
一条河上只有一座独木桥,两个人分别在对岸都想过去,但是都不愿意让对方先过去,于是都过不去,两个人相互等待,把这两个人看作线程,就是死锁。
死锁解决
同一个代码块不要用两个对象锁