JavaSE学习笔记(多线程)

1.线程相关概念

  1. 什么是进程

进程是指运行中的程序,比如使用QQ的时候,就启动了一个进程,操作系统会为该进程分配内存空间,使用别的程序,又会启动一个进程,操作系统会为该进程分配新的内存空间....

进程是程序一次执行过程,或是正在运行的一个程序,又比如Java中的run“main”

线程是动态过程:有它自身的产生、存在和消亡的过程

  1. 什么是线程

线程由进程创建的,是进程的一个实体,一个进程可以拥有多个线程

  1. 其他

单线程:同一个时刻,只允许执行一个线程

多线程:同一个时刻,可以执行多个线程,比如一个qq进程,可以同时打开多个聊天窗口

并发:同一个时刻,多个任务交替执行,造成一种“貌似同时”的错觉,单核cpu实现的多任务就是并发。比如一边打电话一边开车就是并发,一个大脑交替执行这两个任务

并行:同一个时刻,多个任务同时执行。多核cpu可以实现并行。并发和并行可以同时存在

2.线程基本使用

创建线程的两种方式

继承关系示意图

1.继承Thread类,重写run方法

package com.xiaol.threaduse;

public class Thread01 {
    public static void main(String[] args) {
        Cat cat = new Cat();
        cat.start();
    }
}
//当一个类继承了Thread类,该类就可以当成线程使用
class Cat extends Thread{
    int time = 0;
    @Override
    public void run() {
        super.run();
        while (true){
            System.out.println("喵喵" + ++time);
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            if (time==8){
                break;
            }
        }

    }
}
/**
关于运行的线程的理解:
运行代码时,先启用了进程(Application),
然后创建了main线程(执行完main方法的内容线程就销毁了)
但是在main中执行的Thread-0线程还没执行完(此时,进程和Thread-0都还在)
当Thread-0线程执行完,Thread-0销毁了,进程随之销毁
*/

2.实现Runnable接口,重写run方法

package com.xiaol.threaduse;

public class Thread02 {
    public static void main(String[] args) {
        new Thread(new Dog()).start();
    }
}
class Dog implements Runnable{
    int count = 0;
    @Override
    public void run() {

        while (count++ != 5){
            System.out.println("修狗" + count);
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

关于start()方法

start()方法,实际是调用的start0()【源码:private native void start0();,native 原生的是跟操作系统相关的】方法,调用start0()方法后,该线程并不一定会立马执行,只是将线程变成了可运行状态。具体什么时候执行,取决于CPU,由CPU统一调度

多线程案例

package com.xiaol.threaduse;


public class Thread03 {
    public static void main(String[] args) {
        T1 t1 = new T1();
        T2 t2 = new T2();
        new Thread(t1).start();
        new Thread(t2).start();
    }
}

class T1 implements Runnable{
    int count =0;
    @Override
    public void run() {
        while (true){
            System.out.println("hi" + ++count);
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            if (count == 10){
                break;
            }
        }
    }
}

class T2 implements Runnable{
    int count =0;
    @Override
    public void run() {
        while (true){
            System.out.println("kkkk" + ++count);
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            if (count == 5 ){
                break;
            }
        }
    }
}
继承 Thread vs 实现 Runnable 的区别
  1. Thread类本身就实现类Runnable接口,所以从java的设计来看,通过继承Thread货值实现Runnable接口来创建线程本质上没有区别
  2. 实现Runnable接口方式更适合多个线程共享一个资源的情况,并避免了单继承的限制【比如Cat继承了Animal类,还想实现多线程,只能implements Runnable】,建议使用Runnable

3.线程终止

当线程完成任务后,会自动退出

还可以通过使用变量来控制run方法推退出的方式停止线程,即【通知方式】(常用)

由于需要调用外网数据,需要在预发环境搭建代理,接下来需要考虑代理的通用性,在其他需要外网数据配合的需求中可以直接使用。

4.线程常用方法

  1. yield:线程的礼让。让出cpu,让其他线程执行,但礼让的时间不确定,所有也不一定礼让成功,因为有时候CPU觉得它忙的过来
  2. join:线程的插队。插队的线程一旦插队成功,则肯定先执行插入的线程的所有任务
package com.xiaol.threaduse;

public class ThreadExercise {
    public static void main(String[] args) throws InterruptedException {
        int i = 0;
        Thread thread = new Thread(new T3());
        while (true){
            System.out.println("hi" + ++i);

            if (i == 5){
                thread.start();
                thread.join();
            }
            if (i == 10){
                System.exit(3);
            }
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

    }
}

class T3 implements Runnable{
    int count;
    @Override
    public void run() {
        while (true){
            System.out.println("hello" + ++count);
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            if (count == 10){
                break;
            }
        }
    }
}

其他常用方法

  1. setName //设置线程名称
  2. getName //返回线程的名称
  3. start //使该线程开始执行;java虚拟机层调用该线程的start0方法
  4. run //调用线程对象的run方法 【直接调用run方法不会启用线程的!必须调用start】
  5. setPriority //更改线程的优先级
  6. getPriority //获取线程的优先级
  7. sleep //在指定毫秒数内让当前执行的线程休眠(暂停执行)
  8. interrup //中断线程

注意事项和细节

  1. start底层会创建新的线程,调用run,run就是一个简单的方法调用,不会启动新的线程
  2. interrupt,中断线程,但并没有结束线程,一般用于中断正在休眠的线程
  3. sleep是线程的静态方法,事当前线程休眠

用户线程和守护线程

用户线程:也叫工作线程,当线程的任务执行完或通知方式结束

守护线程:一般是为工作线程服务的,当所用用户线程结束,守护线程自动结束 t.setDaemo(true)

常见守护线程:垃圾回收机制

5.线程的生命周期

当线程被创建并启动以后,它既不是一启动就进入了执行状态,也不是一直处于执行状态,在线程的生命周期中,它要经过新建(New)、就绪(Runnable)、运行(Running)、阻塞(Blocked)和死亡(Dead)五种状态

线程状态转换图:

6.线程的同步

在多线程编程中,一些敏感数据不允许被多个线程同时访问,此时就使用同步访问技术,保证数据在任何同一时刻,最多有一个线程访问,以确保数据的完整性

具体同步方法-synchronized

  1. 同步代码块

synchronized(对象){ //得到对象的锁才能操作同步代码块

//需要被同步的代码【理论上范围越小越好,效率越高】

}

  1. synchronized还可以放在方法声明中,表示整个方法为同步方法

public synchronized void sell(){//同步方法,在同一时刻只能有一个线程来执行sell方法

需要被同步的代码

}

7.线程的锁

1.互斥锁

模拟多个窗口售票问题

package com.xiaol.threaduse.ticket;

public class SellTicket {
    public static void main(String[] args) {
        
        SellTicket03 sellTicket03 = new SellTicket03();

        new Thread(sellTicket03).start();
        new Thread(sellTicket03).start();
        new Thread(sellTicket03).start();
    }
}
//使用synchronized实现线程同步
class SellTicket03 implements Runnable{
    private int ticketNum = 100;//让多个线程共享num
    private boolean loop = true;

    public synchronized void sell(){//同步方法,在同一时刻只能有一个线程来执行sell方法
        if (ticketNum <= 0){
            System.out.println("售票结束");
            loop = false;
            return;
        }
        try {
            Thread.sleep(50);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("窗口:" + Thread.currentThread().getName() + "  售出一张票,剩余票数:"
                + --ticketNum );
    }

    @Override
    public void run() {
        while (loop){
            sell();
        }
    }
}

注意事项和细节

  1. 同步方法是非静态的,默认锁对象是this
  2. 如果方法是静态的,默认锁对象是 当前类.class
  3. 实现互锁的步骤
    • 分析需要上锁的代码
    • 选择同步代码块【推荐】或同步方法
    • 要求多个线程的锁对象为同一个

2.线程的死锁

多个线程都占用了对方的锁资源,但不肯相让,导致了死锁,在编程中一定要避免死锁的发生

3.释放锁

释放锁的几种情形:

  1. 当线程的同步方法、同步代码块执行结束
  2. 当线程在同步代码块、同步方法遇到break/return
  3. 当线程在同步代码块、同步方法中出现了未处理的Error或Exception,导致异常结束
  4. 当线程在同步代码块、同步方法中执行了线程的wait()方法,当前线程暂停,并释放锁

不会释放锁的几种情形:

  1. 调用Thread.sleep()、Thread.yield()方法暂停当前线程执行,不会释放锁
  2. 其他线程调用了该线程的suspend()方法将该线程挂起,该线程不会释放锁
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值