一阶段:第14天:多线程基础(8.10)
一.进程和线程
1.1进程(Process)
- 正在运行的程序,是一个程序的运行状态和资源占用(内存,CPU)的描述,通过进程ID区分。
- 进程是程序的一个动态过程,它指的是从代码加载到执行完毕的一个完成过程。
- 目前操作系统支持多进程多任务。
- 进程的特点:
a.独立性:不同的进程之间是独立的,相互之间资源不共享
b.动态性:进程在系统中不是静止不动的,而是在系统中一直活动的
c.并发性:多个进程可以在单个处理器上同时进行,且互不影响
1.2线程
- 对于每个线程,栈空间是独立的,堆空间是共享的
- 线程就是一条执行路径。是进程的组成部分,一个进程可以有多个线程,每个线程去处理一个特定的子任务。
- 线程的特点:线程的执行是抢占式的,多个线程在同一个进程中可以并发执行,其实就是CPU快速的在不同的线程之间切换,也就是说,当前运行的线程在任何时候都有可能被挂起,以便另外一个线程可以运行。
1.3线程和进程的联系与区别
a.一个程序运行后至少有一个进程
b.一个进程可以包含多个线程,但是至少需要有一个线程,否则这个进程是没有意义的
c.进程间不能共享资源,但线程之间可以
d.系统创建进程需要为该进程重新分配系统资源,而创建线程则容易的多,因此使用线程实现多任务并发比多进程的效率高
* java程序运行时,会自动创建主线程,执行main方法
* 在主线程(main)创建的线程是子线程
* 虽然子线程是在主线程中创建的,但是start()后他们就是分别独立的线程了
*
* 线程通过线程id和名称区分---推荐使用第二种
* (1)super.getId() super.getName();
* (2)Thread.currentThread.getId() Thread.currentThread.getName()
*
public class Demo1 {
public static void main(String[] args) {
//创建线程对象
MyThread myThread=new MyThread("少泊");
myThread.start();
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getId()+" "+Thread.currentThread().getName()+"主线程----"+i);
}
}
}
public class MyThread extends Thread{
public MyThread(String name){
super(name);
}
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(super.getId()+" "+super.getName()+"子线程---"+i);
}
}
}
二.多线程的实现
2.1继承Thread
Thread类是所有线程类的父类,实现了对线程的抽取和封装
继承Thread类创建并启动多线程的步骤:
a.定义一个类,继承自Thread类,并重写该类的run方法,该run方法的方法体就代表了线程需要完成的任务,因此,run方法的方法体被称为线程执行体
b.创建Thread子类的对象,即创建了子线程
c.用线程对象的start方法来启动该线程
public class TicketWin extends Thread{
private int ticket=100;
public TicketWin(String name){
super(name);
}
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getId()+Thread.currentThread().getName()+"卖了"+(i+1)+"张票");
ticket--;
}
}
}
// 4个窗口卖票
public class Demo2 {
public static void main(String[] args) {
TicketWin ticketWin=new TicketWin("窗口1");
TicketWin ticketWin1=new TicketWin("窗口2");
TicketWin ticketWin2=new TicketWin("窗口3");
TicketWin ticketWin3=new TicketWin("窗口4");
ticketWin.start();
ticketWin1.start();
ticketWin2.start();
ticketWin3.start();
//不要写成tickWin.run();这样就跟线程无关了
}
}
2.2实现Runnable接口
每个线程.start()时都会创建一个新的栈空间,然后运行线程.run(),都去调用堆空间中的数据
实现Runnable接口创建并启动多线程的步骤:
a.定义一个Runnable接口的实现类,并重写该接口中的run方法,该run方法的方法体同样是该线程的线程执行体
b.创建Runnable实现类的实例,并以此实例作为Thread的target来创建Thread对象,该Thread对象才是真正的线程对象
c.调用线程对象的start方法来启动该线程
public class Ticket implements Runnable{
private int ticket=100;
@Override
public void run() {
while (true){
if (ticket<1){
break;
}
System.out.println(Thread.currentThread().getName()+" 卖了"+(100-ticket+1)+"张票");
ticket--;
}
}
}
* 案例:4个窗口共卖100张票
public class Demo1 {
public static void main(String[] args) {
/*//1.创建票对象
Ticket ticket=new Ticket();
//2.创建线程对象---卖票窗口
Thread w1=new Thread(ticket, "窗口1");
Thread w2=new Thread(ticket, "窗口2");
Thread w3=new Thread(ticket, "窗口3");
Thread w4=new Thread(ticket, "窗口4");
//启动线程
w1.start();
w2.start();
w3.start();
w4.start();*/
//代码简化
Runnable ticket=new Runnable() {
@Override
public void run() {
int ticket=100;
while (true){
if (ticket<1){
break;
}
System.out.println(Thread.currentThread().getName()+" 卖了"+(100-ticket+1)+"张票");
ticket--;
}
}
};
new Thread(ticket).start();
new Thread(ticket).start();
new Thread(ticket).start();
new Thread(ticket).start();
}
}
2.2.1综合案例
// 案例:银行卡
public class Demo2 {
public static void main(String[] args) {
//1.创建银行卡
BankCard card=new BankCard();
//2.创建存钱和取钱功能
AddMoney addMoney=new AddMoney(card);
SubMoney subMoney=new SubMoney(card);
//3.创建线程对象
Thread shaobo=new Thread(addMoney,"少泊");
Thread xiaohai=new Thread(subMoney,"小海");
//4.启动
shaobo.start();
xiaohai.start();
//简化代码
BankCard card1=new BankCard();
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
card.setMoney(card.getMoney()+1000);
System.out.println("存了1000,余额是"+card.getMoney());
}
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
if (card.getMoney()>=1000){
card.setMoney(card.getMoney()-1000);
System.out.println("取了1000,余额是"+card.getMoney());
}else {
System.out.println("余额不足,及时存钱");
i--;
}
}
}
}).start();
}
}
public class BankCard{
private double money;
public BankCard() {
}
public BankCard(double money) {
this.money = money;
}
public double getMoney() {
return money;
}
public void setMoney(double money) {
this.money = money;
}
}
public class AddMoney implements Runnable{
private BankCard card;
public AddMoney(BankCard card) {
this.card = card;
}
@Override
public void run() {
//存钱
for (int i = 0; i < 10; i++) {
card.setMoney(card.getMoney()+1000);
System.out.println(Thread.currentThread().getName()+"存了1000元,余额为"+card.getMoney());
}
}
}
public class SubMoney implements Runnable{
private BankCard card;
public SubMoney(BankCard card) {
this.card = card;
}
public BankCard getCard() {
return card;
}
public void setCard(BankCard card) {
this.card = card;
}
@Override
public void run() {
for (int i = 0; i < 10; i++) {
if (card.getMoney()>=1000){
//取钱
card.setMoney(card.getMoney()-1000);
System.out.println(Thread.currentThread().getName()+"取了1000,余额为"+card.getMoney());
}else{
System.out.println("余额不足,及时存钱");
i--;
}
}
}
}
2.3 比较Thread和Runnable
- 继承Thread类的方式
a.没有资源共享,编写简单
如果要访问当前线程,除了可以通过Thread.currentThread()方式之外,还可以使用getName()获取线程名字。
b.弊端:因为线程类已经继承了Thread类,则不能再继承其他类【单继承】 - 实现Runnable接口的方式
a.可以多个线程共享同一个资源,所以非常适合多个线程来处理同一份资源的情况
b.资源类实现了Runnable接口。如果资源类有多个操作,需要把功能提出来,单独实现Runnable接口。
c.弊端:编程稍微复杂,不直观,如果要访问当前线程,必须使用Thread.currentThread()
总结:实际上大多数的多线程应用都可以采用实现Runnable接口的方式来实现【推荐使用匿名内部类】
2.4 Callable接口实现多线程
public class Demo3 {
public static void main(String[] args) throws Exception{
//1.创建可调用对象
MyCallable callable=new MyCallable();
//把 callable变成一个任务
FutureTask<Integer> task=new FutureTask<>(callable);
//2.创建线程
//传递任务可以,不能传递callable
Thread thread=new Thread(task);
//3.启动
thread.start();
//获取结果
Integer sum=task.get();//sleep 会阻塞,直到进程结束返回结果
System.out.println("结果是:"+sum);
}
}
public class MyCallable implements Callable<Integer> {
@Override
public Integer call() throws Exception {
int sum=0;
for (int i = 0; i <= 100; i++) {
Thread.sleep(100);
sum+=i;
System.out.println(i);
}
return sum;
}
}
三.线程的常用方法
线程对象.setName(); //设置线程名
Thread.currentThread().getName();//获取当前线程的线程名
3.1线程休眠
使得当前正在执行的线程休眠一段时间,释放时间片,导致线程进入阻塞状态(单位毫秒)。跟线程的优先级无关,当对应的时间到了之后,还会再继续执行。
public class Demo1 {
public static void main(String[] args) {
SleepThread sleepThread=new SleepThread();
Thread thread=new Thread(sleepThread);
thread.start();
}
}
public class SleepThread extends Thread {
@Override
public void run() {
for (int i = 0; i < 50; i++) {
System.out.println(Thread.currentThread().getName()+"..."+i);
try {
//如果想让程序运行快点,可以适当调整大小
Thread.sleep(10000);
}catch (InterruptedException e){
e.printStackTrace();
}
}
}
}
3.2线程优先级
默认情况下,每个线程的优先级都与创建它的父线程具有相同的优先级,例如:main线程具有普通优先级,则由main线程创建的子线程也有相同的普通优先级。
优先级范围1~10,默认为5,对应的数值越大,说明优先级越高,这个方法的设置一定要在start之前。
线程的优先级低并不意味着争抢不到时间片,只是抢到时间片的概率比较低而已。
p1.setPriority(1);
p3.setPriority(10);
3.3合并(加入)线程(join)
优先执行合并进来的线程,join之前,一定要将线程处于准备状态start
public class Demo3 {
public static void main(String[] args) /*throws Exception*/{
JoinThread joinThread=new JoinThread();
joinThread.start();
//加入线程,阻塞当前线程(main),直到加入线程(joinThread)执行完毕,才继续执行
try {
joinThread.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
for (int i = 0; i < 20; i++) {
System.out.println(Thread.currentThread().getName()+" "+i);
}
}
}
3.4后台线程
后台线程:隐藏起来一直在默默运行的线程,直到进程结束,又被称为守护线程,JVM的垃圾回收线程就是典型的后台线程。特征:如果所有的前台线程都死亡,后台线程会自动死亡。
前台线程:默认的线程都是前台线程,如果前台不执行完毕,程序不会退出。
设置为后台线程
daemonThread.setDaemon(true);
daemonThread.start();
3.5线程让步
可以让当前正在执行的线程暂停,但它不会阻塞该线程,他只是将该线程转入就绪状态,只有优先级与当前线程相同,或者优先级比当前线程更高的就绪状态的线程才会获得执行的机会。完全可能出现的情况是:当某个线程调用了yield方法暂停之后,线程调度器又将其调度出来重新执行。
Thread.yield();
3.6线程中断 interrupt()
程序在等待过程中,可以使用interrupt方法打断。
所有能抛出InterruptedException异常的,都可以被打断。
interrupt是打断,stop是打死
四.生命周期
五个状态:
1.新生—>2.就绪---->3.运行(让步回2.)---->(4.阻塞—打醒或休眠结束回2.)----->5.死亡
- 新生:start()方法之前
- 就绪:start()
- 运行:抢到cup
- 阻塞:sleep(),wait(),join()
- 死亡:run()结束或stop()
七状态: 将阻塞细分解成3种情况