Java中的线程

目录

一、什么是进程

 二、什么是线程

1、单线程

2、多线程

3、并发

4、并行

 三、创建线程的两种方式 

1、继承Thread类,重写run方法。

(1)为什么是start方法开启线程?

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

3、继承Thread类 VS 实现Runnable接口

四、多线程执行

五、使用多线程,模拟三个窗口同时售票100张

六、线程退出

七、线程的常用方法

1、第一组(线程中断)

2、第二组(线程插队)

 3、第三组(守护线程)

八、线程的生命周期

 九、线程同步机制(Synchronized)

1、为什么要有同步机制?

2、同步的实现方式

(1)同步代码块

(2)同步方法

十、互斥锁

1、分析同步原理

 2、互斥锁

十一、线程死锁

1、什么是线程死锁

十二、释放锁

1、什么时候释放锁?

2、下面的操作,不会释放锁


一、什么是进程

进程就是运行中的程序。比如打开QQ,就是启动了一个进程,操作系统会为该进程分配内存空间。再打开迅雷,就又启动了另一个进程,操作系统为迅雷也分配一个内存空间。

进程是动态过程,有产生、存在和销毁的过程。

 二、什么是线程

迅雷中同时下载多个任务,每一个下载任务就是一个线程。

 

  •  线程是由进程创造的。
  • 一个进程可以拥有多个线程。

1、单线程

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

2、多线程

同一个时刻,可以执行多个线程。例如:迅雷同时下载多个文件;QQ同时打开多个聊天窗口。

3、并发

同一个时刻,多个任务交替执行,但是由于切换的速度很快,给人一种"貌似同时进行"的错觉。单核cpu实现多任务就是并发。(因为cpu只有一个,一次只能干一件事情)

4、并行

同一时刻,多个任务同时执行。多核cpu实现的就是并行。

 三、创建线程的两种方式 

1、继承Thread类,重写run方法。

注意:该线程的业务逻辑(即要做的事情),就写在run方法中。

Thread类的体系图:

 下面举一个例子,需求是:1、让程序每隔1s,输出一次"我是猫"。2、输出80次,结束该线程。

package com.hspedu.System_;

public class Thread_01 {
    public static void main(String[] args) {
        //创建Cat对象,可以当作线程使用
        Cat cat = new Cat();
        cat.start();  //启动线程
    }
}

//1.当一个类继承了Thread类,它就可以当作线程使用
//2.重写run方法,写上自己的业务代码
//3.run方法实际上是Runnable接口中的方法
class Cat extends Thread{
    @Override
    public void run() {  //重写run方法
        while (true) {
            //该线程每隔1s,输出一次"我是猫"
            System.out.println("我是猫");
            //让该线程休眠1s
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
    }
}

(1)为什么是start方法开启线程?

问题解答:

还记得上面"我是猫"的例子吗?cat.start() 启动了线程,实际上是调用了Cat类里面的run方法,为什么要这样用start多此一举呢?

答案:如果用cat.run() 程序就会把run方法执行完毕再往下走,因为run方法就只是一个普通的方法,并没有真正启动线程。

其实,真正实现多线程效果的是start0方法

 

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

为什么要用这种方式实现多线程?

答案:java是单继承的,如果已经继承了别的类,就无法再继承Thread类创建多线程。

下面举一个例子:需求是每隔1s,输出一次”小狗汪汪叫“

package com.hspedu.System_;

public class Thread_02 {
    public static void main(String[] args) {
        Dog dog = new Dog();
        //创建Thread对象,把dog对象放入Thread
        Thread thread = new Thread(dog);   //通过实现Runnable接口创建线程,不能直接start
        thread.start();
        //上面这种用法很神奇!成为 "代理模式"
    }
}

class Dog implements Runnable{  //通过实现Runnable接口,创建线程

    int count = 0;

    @Override
    public void run() {
        while (true){
            //Thread.currentThread().getName() 是获取当前线程名字
            System.out.println("小狗汪汪叫" + (++count) + Thread.currentThread().getName());

            //休眠1秒
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
    }
}

3、继承Thread类 VS 实现Runnable接口

  • 它俩的本质一样,都是通过start0方法开启线程。
  • Thread类实现了Runnable接口。
  • 实现Runnable接口的方式更适合多个线程共享一个资源的情况,并且避免了单继承的限制。

因此,建议使用Runnable接口。

四、多线程执行

上面启动的都是单线程,下面写一个同时启动两个线程。

需求:一个线程每隔1s输出"hello world",输出10次退出,另一个每隔1s输出"hi",输出5次退出。

package com.hspedu.System_;

import java.util.concurrent.TransferQueue;

public class Thread_03 {
    public static void main(String[] args) {
        T1 t1 = new T1();
        T2 t2 = new T2();
        Thread thread1 = new Thread(t1);
        Thread thread2 = new Thread(t2);
        thread1.start();  //启动第一个线程
        thread2.start();  //启动第二个线程

    }
}

class T1 implements Runnable{

    int count = 0;

    @Override
    public void run() {
        while (true) {
            //每隔1s输出"hello world",输出10次
            System.out.println("hello, world" + (++count));
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }if (count == 10){
                break;
            }
        }
    }
}

class T2 implements Runnable{

    int count = 0;

    @Override
    public void run() {
        while (true) {
            //每隔1s输出"hi",输出5次
            System.out.println("hi" + (++count));
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }if (count == 5){
                break;
            }
        }
    }
}

五、使用多线程,模拟三个窗口同时售票100张

六、线程退出

退出逻辑:让t1退出run方法,从而中止t1线程。

package com.hspedu.ticket;

public class ThreadExit {
    public static void main(String[] args) throws InterruptedException {
        T t1 = new T();
        t1.start();
        //如果希望main线程去控制t1线程的终止,必须可以修改loop
        //通知方式:让t1退出run方法,从而中止t1线程

        //让主线程休眠10s,再通知t1线程退出
        Thread.sleep(10*1000);
        t1.setLoop(false);
    }
}

class T extends Thread{
    private int count = 0;
    //设置一个控制变量
    private boolean loop = true;

    @Override
    public void run() {
        while (loop){
            try {
                Thread.sleep(50);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("T 运行中..." + (++count));
        }
    }
    public void  setLoop(boolean loop){
        this.loop = loop;
    }
}

七、线程的常用方法

1、第一组(线程中断)

 注意细节:

  • start方法的底层会创建线程,而调用run,run方法就是一个普通的方法,不会创建线程。
  • interrupt方法是中断线程,但并没有真正的结束线程。一般用于中断正在休眠的线程。
  • sleep方法是线程的static方法,使当前线程休眠。
  • Thread.currentThread().getName();获取当前线程名字。

2、第二组(线程插队)

 3、第三组(守护线程)

八、线程的生命周期

 

 九、线程同步机制(Synchronized)

1、为什么要有同步机制?

  • 在多线程中,一些敏感数据不允许被多个线程同时访问,此时就使用同步访问技术,保证数据在任何同一时刻,最多只有一个线程访问,以保证数据的完整性。
  • 即当有一个线程在对内存进行操作时,其他线程都不可以对这个内存地址进行操作,一直到该线程完成操作,其他线程才能对该内存地址进行操作。

2、同步的实现方式

(1)同步代码块

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

(2)同步方法

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

public synchronized void m(String name){
    //需要被同步的代码
}

通俗理解:大黄去上厕所,把门关上(上锁),完事开门出来(解锁),然后其他的人才可以上厕所。

十、互斥锁

1、分析同步原理

t1、t2、t3分别代表一个线程,哪个线程先拿到锁(锁是在对象上的,所以叫做对象锁),哪个线程就去执行里面的内容,执行完后回来,锁放回去,下一个线程再重复该步骤。

 2、互斥锁

上面提到的那把锁,就叫做互斥锁。

互斥锁介绍:

  • Java中引入互斥锁的概念,保证共享数据操作的完整性。
  • 每个对象都对应一个可称为"互斥锁"的标记,该标记用于保证在任一时刻,只能有一个线程访问该对象。
  • 关键字synchronized用于与对象的互斥锁联系。当某个对象用synchronized修饰时,表明该对象在任一时刻时,只能由一个线程访问。

注意细节:

  • 同步:会降低程序的执行效率。
  • 同步方法(非static):默认锁对象是this,也可以是其他对象(要求是同一个对象)。
  • 同步方法(静态):默认锁对象是当前类。

实现互斥锁的步骤:

  1. 先分析要上锁的代码。
  2. 选择同步代码块or同步方法。
  3. 要求多个线程的锁对象为同一个即可。

十一、线程死锁

1、什么是线程死锁

多个线程都占用了对方的锁资源,且不肯相让,就导致了死锁,编程过程中一定要避免这种情况。

举个小例子:(这就是死锁)

妈妈:你先完成作业,才让你玩手机。

大黄:你先让我玩手机,我才去写完作业。

十二、释放锁

1、什么时候释放锁?

  • 当前线程的同步方法、同步代码块执行结束。eg:上厕所,完事出来。
  • 当前线程在同步代码块、同步方法中遇到break、return。eg:厕所没上完,但是有急事,不得已也出去。
  • 当前线程在同步代码块、同步方法中出现了未处理的Error或Exception,导致异常结束。eg:没有正常的完事,但是发现忘带纸了,不得不出来。
  • 当前线程在同步代码块、同步方法中执行了线程对象的wait()方法,当前线程暂停,并释放锁。eg:没有正常完事,觉得需要再酝酿一下,所以出来,等会再进去。

2、下面的操作,不会释放锁

线程执行同步代码块or同步方法时,程序调用Thread.sleep()、Thread.yield()方法,暂停了当前线程的执行,此时不会释放锁。eg:上厕所太困了,在坑上眯了一会。

线程执行同步代码块时,其他线程调用了该线程的suspend()方法,将该线程挂起,该线程不会释放锁。提示:应该尽量避免suspend()和resume()方法来控制线程,因为这些方法不再推荐使用。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

m1m-FG

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值