Java--线程基础篇

一、线程相关概念

  • 进程
    1、进程是指运行中的程序,比如我们使用QQ,就启动一个进程,操作系统就会为该进程分配内存空间。当我们使用迅雷,又启动了一个线程,操作系统将为迅雷分配新的内存空间
    2、进程是程序的一次执行过程,或是正在运行的一个程序。是动态过程,有它自身的产生、存在和消亡过程
  • 线程
    1、线程由进程创建的,是创建的一个实体
    2、一个进程可以有多个线程
  • 其他相关概念
    1、单线程:同一时刻,只允许执行一个线程
    2、多线程:同一时刻,可以执行多个线程,比如:一个qq进程,可以同时打开多个聊天窗口,一个迅雷进程,可以同时下载多个文件
    3、并发:同一时刻,多个任务交替执行,造成一种“貌似相同”的错觉,简单的说,单核cpu实现的多任务就是并发
    4、并行:同一时刻,多个任务同时执行,多核cpu可以实现并行

二、线程基本使用

  • 创建线程的两种方式
    在Java中线程来使用有两种方式
    1、继承Thread类,重写run方法
    2、实现Runnable接口,重写run方法

案例:Thread01.java

public class Thread01{
    public static void main(String[] args) {
        Cat cat = new Cat();
        cat.start();
    }
}

//1,当一个类继承了Thread类,该类就可以当作线程使用
//2,我们会去重写run方法,写上自己业务代码
//3,run Thread类实现了Runnable 接口的run方法
class Cat extends Thread{
    int num = 0;

    @Override
    public void run() { //重写run方法,写上自己的业务逻辑

        while (true){
            //该线程每隔1秒。在控制台输出“喵喵,我是小猫咪”
            System.out.println("喵喵,我是小猫咪"+(++num));
            //该线程休眠1秒
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            if (num == 8){
                break;  //当num到8,退出while,这时线程就退出..
            }

        }


    }
}

案例二 实现Runnable接口,底层使用了设计模式【代理模式】Thread02.java

public class Thread02 {
    public static void main(String[] args) {
        Dog dog = new Dog();
        Thread one = new Thread(dog);
        one.start();

        for (int i = 0; i < 5; i++) {
            System.out.println("主线程"+i+Thread.currentThread().getName());
        }

        Tiger tiger = new Tiger();
        ThreadProxy threadProxy = new ThreadProxy(tiger);
        threadProxy.start();
    }
}
class Animal{}

class  Tiger extends  Animal implements Runnable{

    @Override
    public void run() {
        System.out.println("老虎嗷嗷叫~~");
    }
}

//线程代理类,模拟了一个极简的Thread类
class  ThreadProxy implements Runnable{    //可以把ThreadProxy类当作Thread

    private Runnable target = null; //属性,类型是Runnable

    @Override
    public void run() {
        if (target != null){
            target.run();
        }

    }

    public ThreadProxy(Runnable target) {
        this.target = target;
    }

    public void start(){
        start0();
    }
    public void start0(){
        run();
    }
}




class Dog implements Runnable{  //通过实现Runnable接口,开发线程

    int num = 0;
    @Override
    public void run() {
        while (true){

            System.out.println("小狗汪汪叫>>"+(++num)+Thread.currentThread().getName());

            //休眠1秒
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            if (num ==10){
                break;
            }
        }
    }
}

继承Thread和实现Runnable接口的区别

1、从Java的设计来看,通过继承Thread或者实现Runnable接口来创建线程本质上没有区别,从jdk帮助文档我们可以看到Thread类本身就实现了Runnable接口
2、实现Runnable接口方式更加适合多个线程共享一个资源的情况,并且避免了单继承的限制

案例3:售票系统,编程模拟三个售票窗口售票,分别使用继承Thread和实现Runnable方式,并分析有什么问题?SellTicket.java

线程终止

  • 基本说明
    1、当线程完成任务后,会自动退出
    2、还可以通过使用变量来控制run方法退出的方式停止线程,即通知方式

案例ThreadExit_.java

public class ThreadExit_ {
    public static void main(String[] args) throws InterruptedException {
        T t1 = new T();
        t1.start();

        //如果希望main线程去控制t1线程终止,必须可以修改loop
        //让t1退出run方法,从而终止 t1线程-->通知方式


        //让主线程休眠十秒再通知t1线程退出
        System.out.println("主线程休眠10秒");
        Thread.sleep(10*1000);
        t1.setLoop(false);

    }
}



class T extends Thread{
    int num = 0;
    Boolean loop = true;

    @Override
    public void run() {
        while (loop){

            try {
                Thread.sleep(50);   //让线程休眠50毫秒
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("T运行中..."+(++num));
        }
    }

    public void setLoop(Boolean loop) {
        this.loop = loop;
    }
}

线程常用方法

  • 常用方法第一组
    1、setName //设置线程名,使之与参数name相同
    2、getName //返回该线程名称
    3、start //使该线程开始执行;Java虚拟机底层调用该线程的start0方法
    4、run //调用线程对象run方法
    5、setPriority //更改线程的优先级
    6、getPriority //获取线程的优先级
    7、sleep //在指定的毫秒数内让当前正在执行的线程休眠
    8、interrupt //中断线程

  • 注意事项
    1、start底层会创建新的线程,调用run,run就是一个简单的方法调用,不会启动新的线程
    2、线程优先级的范围
    3、interrupt,中断线程,但并没有真正结束线程。所以一般用于中断正在休眠的线程
    4、sleep:线程的静态方法,使当前线程休眠

  • 常用方法第二组
    1、yield:线程的礼让。让出cpu,让其它线程执行,但礼让的时间不确定,所以不一定礼让成功
    2、join:线程的插队,插队的线程一旦插队成功,则肯定先执行完插入的线程所有的任务
    案例:ThreadMethod02.java 创建一个子线程,每隔1s输出hello,输出20次,主线程每隔1秒,输出hi,输出20次。要求:两个线程同时执行,当主线程输出5次后,就让子线程运行完毕,主线程再继续

public class ThreadMethod02 {
    public static void main(String[] args) throws InterruptedException {
        T2 t2 = new T2();
        t2.start();

        for (int i = 1; i <= 20; i++) {
            Thread.sleep(1000);
            System.out.println("主线程  吃了"+i+"个包子");
            if (i == 5){
                System.out.println("主线程(小弟)让   子线程(老大)先吃完");
//                t2.join();
                Thread.yield();
                System.out.println("线程(老大) 吃完了   主线程(小弟)接着吃..");
            }
        }
    }
}

class T2 extends Thread{
    @Override
    public void run() {
        for (int i = 1; i <= 20; i++) {
            try {
                Thread.sleep(1000);     //休眠1秒
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("子线程 吃了"+i+"个包子");
        }
    }
}
  • 用户线程和守护线程
    1、用户线程:也叫工作线程,当线程的任务执行完或通知方式结束
    2、守护线程:一般是为工作线程服务的,当所有的用户线程结束,守护线程自动结束
    3、常见的守护线程:垃圾回收机制

案例:ThreadMethod03.java

//将一个线程设置成守护线程
public class ThreadMethod03 {
    public static void main(String[] args) throws InterruptedException {
        T3 t3 = new T3();
        //如果我们希望当mian线程结束,子线程自动结束
        //只需要将子线程设置为守护线程即可
        t3.setDaemon(true);
        t3.start();


        for (int i = 0; i < 10; i++) {  //主线程
            System.out.println("宝强在幸苦的工作。。。。");
            Thread.sleep(1000);
        }

    }
}


class T3 extends Thread{
    @Override
    public void run() {
        for ( ; ; ){
            try {
                Thread.sleep(50);   //休眠50毫秒
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("马蓉和宋哲快乐聊天,哈哈哈~~~");
        }
    }
}

线程的生命周期

  • JDK中用Thread.State枚举表示了线程的几种状态

Synchronized

  • 线程同步机制

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

2、也可以这样理解:线程同步,即当有一个线程在对内存进行操作时,其他线程都不可以对这个内存地址进行操作,直到该线程完成操作,其他线程才能对该内存地址进行操作

  • 同步具体方法-Synchronized

1、同步代码块
synchronized(对象) { //得到对象锁,才能操作同步代码
//需要被同步代码
}

2、synchronized还可以放在方法中声明,表示整个方法为同步方法
public synchronized void m(String name){
//需要被同步的代码
}

3、如何理解:
就好像某小伙伴上厕所前先把门关上(上锁),完事后再出来(解锁),那么其他小伙伴就可在使用厕所了

4、使用Synchronized解决售票问题

代码

public class SellTicket {
    public static void main(String[] args) {
        
        //测试
        SellTicket03 sellTicket03 = new SellTicket03();
        new Thread(sellTicket03).start();   //第1个线程-窗口
        new Thread(sellTicket03).start();   //第2个线程-窗口
        new Thread(sellTicket03).start();   //第3个线程-窗口

    }

}


//使用synchronized实现线程同步
class SellTicket03 implements Runnable{

    private  int ticketNum = 100;   //让多个线程共享ticketNum
    private Boolean loop = true;


    public synchronized void sell(){ //同步方法,在同一时刻,只能有一个线程来执行run方法
        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(); //sell方法是一个同步方法

        }
    }
}

互斥锁

1、Java语言中,引入了对象互斥锁的概念,来保证共享数据操作的完整性
2、每个对象都对应于一个可称为“互斥锁”的标记,这个标记用来保证在任一时刻,只能有一个线程访问该对象。
3、关键字synchronized来与对象的互斥锁联系,当某个对象用synchronized修饰时,表明该对象在任一时刻只能由一个线程访问
4、同步的局限性:导致程序的执行效率降低
5、同步方法(非静态的)的锁可以是this,也可以是其他对象(要求是同一个对象)
6、同步方法(静态的)的锁为当前类本身

  • 注意事项
    1、同步方法如果没有使用static修饰:默认锁对象为this
    2、如果方法使用static修饰,默认锁对象:当前类.class

3、实现的落地步骤:

  • 需要先分析上锁的代码
  • 选择同步代码块或同步方法
  • 要求多个线程的锁对象为同一个即可

线程的死锁

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

  • 应用案例
    妈妈:你先完成作业,才让你玩手机
    小明:你先让我玩手机,我才完成作业

应用案例:DeadLock01.java

//模拟线程死锁
public class DeadLock01 {
    public static void main(String[] args) {
        DeadLockDemo A = new DeadLockDemo(true);
        DeadLockDemo B = new DeadLockDemo(false);
        A.setName("A线程");
        B.setName("B线程");
        A.start();
        B.start();


    }
}

class DeadLockDemo extends Thread{
    static Object o1 = new Object();
    static Object o2 = new Object();

    boolean flag;

    public DeadLockDemo(boolean flag){
        this.flag = flag;
    }



    @Override
    public void run() {
//下面业务逻辑的分析
        //1、如果flag 为 T,线程A就会先得到/持有o1对象锁,然后尝试去获取 o2 对象锁
        //2、如果线程A 得不到  o2对象锁,就会Blocked
        //3、如果flag 为F,线程B就会先得到/持有 o2 对象锁,然后尝试去获取 o1 对象锁
        //4、如果线程B得不到 o1 对象锁,就会Blocked
        if (flag){
            synchronized (o1){
                System.out.println(Thread.currentThread().getName()+"进入1");
                synchronized (o2){
                    System.out.println(Thread.currentThread().getName()+"进入2");
                }
            }
        }else {
            synchronized (o2){
                System.out.println(Thread.currentThread().getName()+"进入3");
                synchronized (o1){
                    System.out.println(Thread.currentThread().getName()+"进入4");
                }
            }
        }
    }
}

释放锁

  • 下面操作会释放锁
    1、当前线程的同步方法、同步代码块执行结束

2、当前线程在同步代码块、同步方法中遇到break、return。
案例:没有正常的完事,经理叫他修改bug,不得已出来

3、当前线程在同步代码块、同步方法中出现了未处理的Error或Exception,导致异常结束

4、当前线程在同步代码块、同步方法中执行了线程对象的wait()方法,当前线程暂停,并释放锁

  • 下面操作不会释放锁
    1、线程执行同步代码块或同步方法时,程序调用Thread.sleep()、Thread.yield()方法暂停当前线程的执行,不会释放锁

2、线程执行同步代码块或同步方法时,其他线程调用了该线程的suspend()方法将该线程挂起,该线程不会释放锁。

| 提示:应尽量避免使用suspend()和resume()来控制线程,方法不再推荐使用

作业1:编程取款的线程

代码

//编程取款的线程
public class Homework03 {
    public static void main(String[] args) {
        T t = new T();
        Thread thread1 = new Thread(t);
        Thread thread2 = new Thread(t);
        thread1.setName("t1");
        thread2.setName("t2");
        thread1.start();
        thread2.start();


    }
}

//因为这里涉及到多个线程共享资源问题,所以我们使用实现Runnable方式
class  T implements Runnable{

    private int money = 10000;

    @Override
    public void run() {
        while (true){
            //这里使用了synchronized实现了线程同步,当多个线程执行到这里时就会争夺this对象锁
            synchronized (this){
                //先判断余额是否够
                if (money < 1000){
                    System.out.println("余额不足");
                    break;
                }

                money -= 1000;
                System.out.println(Thread.currentThread().getName()+" 取出了1000块"+"当前余额="+money);
            }

            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

    }
}

该文章仅用于学习,借此希望该文章能对大家有所帮助!! 感觉可以点个赞赞呗~

该文章笔记内容源自B站韩顺平零基础30天学Java视频,如有侵权,请私信删除文章。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

友情链接更多精彩内容