多线程

线程(基础)

概念:

  • 什么是程序:

    为完成某种任务,用某种语言编写的一组指令的集合(就是我们写的代码)

  • 什么是进程:

    运行中的程序(就会占用内容空间)

  • 什么是线程:

    线程由进程创建的,是进程的实体,一个进程可以有多个线程(例如迅雷里,多个下载任务就是多个线程)

  1. 单线程:

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

  2. 多线程:

    同一个时刻,可以执行多个线程,比如qq可以打开多个聊天窗口,迅雷可以同时下载多个

  3. 并发:

    单核cpu实现的多任务就是并发;同一时刻,多个任务交替执行,造成一种“貌似同时”的错觉(例如,我们人开车的时候打电脑,其实这个时候就是并发,并不是我们可以一心二用,而是我们大脑在车和电话里来回切换)

  4. 并行:

    多核Cpu可以实现并行;同一时刻,多个任务同时执行(例:开车打电话的时候,车里有两个人,一个人开车,一个人打电话)

1641083027808

注:

并发和并行可以同时存在,当你开的进程太多,核(并行执行)不够用,就会有核来回切换并发执行

创建线程

1641083148630

继承Thread:

注:

  • 1.当一个类继承了Thread,那这个类就可以当作一个线程使用

  • 2.我们会重写一个run方法,run里写我们的业务代码

  • 3.这个run方法其实是Thread类继承了Runnable里的run

  • 4.当运行一个程序(启动进程),就会先运行main主线程(Test测试也是主线程),主线程可能先比子线程消亡那个的快,但是子线程还是会执行完,且子线程里还可以启动其他子线程

常用方法:

  1. Thread.sleep()休眠
  2. start()启动线程
  3. Thread.currentThread().getName() 获取当前线程名
public class Thread1 {
    public static void main(String[] args) throws InterruptedException {
//        创建对象cat,当成线程来使用
        Cat cat = new Cat();
//        启动线程
        cat.start();
//        当main线程启动了一个子线程Thread-0(cat对象)后,主线程不会阻塞,会继续执行
//        这时主线程和子线程会交替执行
        for (int i = 0; i <10 ; i++) {
            System.out.println("我是主线程"+Thread.currentThread().getName());
            Thread.sleep(1000);
        }
    }
}
/**
 * 1.当一个类继承了Thread,那这个类就可以当作一个线程使用
 * 2.我们会重写一个run方法,run里写我们的业务代码
 * 3.这个run方法其实是Thread类继承了Runnable里的run
 */
class Cat extends Thread{
    @Override
    public void run(){
        int time=0;
        while (true){
            time++;
//            获取线程名
            System.out.println("喵喵,喔"+time+Thread.currentThread().getName());
            try {
//            让线程休眠1s
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            if (time==80){
                break;
            }
        }
    }
//    测试一样是当main用的(获取的当前线程名为mian)
    @Test
    public void test() throws InterruptedException {
        Cat cat = new Cat();
        cat.start();
        for (int i = 0; i <60 ; i++) {
            System.out.println("test测试"+Thread.currentThread().getName());
            Thread.sleep(1000);
        }
    }

图解线程运行:

1641086355133

疑问:启动线程为什么是调用start而不是直接调用run方法

run只是一个普通的方法,调用run,就没有真正执行子线程,才会往下执行

cat.start();> start0();
真正实现多线程的是start0方法,而不是run(普通方法)
private native void start0();
》start0是一个本地方法,由jvm调用,底层是c/c++实现
且我们在启动了start0后只是让线程处于可运行状态具体run()的调用,由cpu决定

实现Runnable接口

Runnable和Thread的区别:

为什么有了继承Thread,还要有这个接口

首先,Thread和Runnable本质上没有什么区别,都是调用start==》start0()

其次,java继承是单继承,当一个类已经继承过一个父类后,那这个类就不能实现线程了,而且实现Runnable接口方便多个线程共享一个资源(一个run),而Thread是实例化3个资源对象,所以建议使用Runnable

使用接口要使用Thread构造器的原因

使用了代理模式,
 //线程代理类
class Threadproxy extends Thread{
    private static Runnable targe=null;

    public static void main(String[] args) {
        if (targe!=null){
            targe.run();
        }
    }
    Threadproxy(Runnable targe){//这就是那个调用Thread构造器的原因
            targe=this.targe;
    }
    public void start(){
        start0();//这个是实现线程的核心方法
    }
    public void start0(){
        run();
    }
}

实现Runnable接口实现线程代码:

  • 由于Runnable就是个什么都没有的接口,没有启动线程的方法start()

  • 此时我们发现Thread有个构造器的参数是放入接口Runnable的,我们可以用Thread的start方法

public class Thread2 {
    public static void main(String[] args) throws InterruptedException {
        Dog dog = new Dog();
//        由于Runnable就是个什么都没有的接口,没有启动线程的方法start()
        要将dog对象放到Thread里
        Thread thread = new Thread(dog);
// public Thread(Runnable target) {init(null, target, "Thread-" + nextThreadNum(), 0);}
//        此时我们发现Thread有个构造器的参数是放入接口Runnable的,我们可以用Thread的start方法
        thread.start();
        for (int i = 0; i <20 ; i++) {
            System.out.println("主线程"+i+Thread.currentThread().getName());
            Thread.sleep(1000);
        }
    }
}
class Dog implements Runnable{
int time=0;
    @Override
    public void run() {
        while (true){
            time++;
            System.out.println("小狗汪汪汪!"+time+Thread.currentThread().getName());
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            if (time==80){
                break;
            }
        }
    }
}  

线程常用方法:

  1. setName //设置线程名称,使之与参数name相同
  2. getName//返回该线程的名称
  3. start//使该线程开始执行;Java虚拟机底层调用该线程的start0方法
  4. run//调用线程对象run方法;
  5. setPriority //更改线程的优先级
  6. getPriority//获取线程的优先级
  7. sleep/在指定的毫秒数内让当前正在执行的线程休眠(暂停执行)
  8. interrupt //中断线程(并没有真正结束线程,所以一般用来中断正在休眠的线程)
  9. yield:线程的礼让,让出cpu,,让其他线程执行,但礼让的时间不确认,所以有可能礼让不成功
  10. join:线程的插入;就像你让别人插队在你前面,你必须让它先执行完,你才能执行(在主线程中执行子线程.join,就是让子线程先执行完在执行主线程
  11. wait:导致当前线程等待,直到调用notify()和notifyAll()
    public static void main(String[] args) throws InterruptedException {
        One one = new One();
        one.setName("刘得很");
        one.setPriority(1);
        one.start();
        for (int i = 0; i <10 ; i++) {
            Thread.sleep(1000);
            System.out.println("主");
        }
        System.out.println("优先级:"+one.getPriority());
//        实现中断
        one.interrupt();
    }
}
class One extends Thread{
    @Override
    public void run(){
        while (true){
            for (int i = 0; i <100 ; i++) {
                System.out.println(Thread.currentThread().getName()+"吃包子"+" "+(i+1)+"个");
            }
            try {
                System.out.println("休眠中~~");
                Thread.sleep(20000);
            } catch (InterruptedException e) {
//            当该线程执行了一个Interrupted的时候就会进入到这里
//            捕获到一个中断异常不是中止
                System.out.println(Thread.currentThread().getName()+"被Interrupted了");
            }
        }
    }

插入和礼让:

    public static void main(String[] args) throws InterruptedException {
        M m = new M();
        m.start();
        for (int i = 0; i <20; i++) {
            Thread.sleep(1000);
            System.out.println("小弟(主线程)吃"+(i+1)+"个包子"+Thread.currentThread().getName());
            if (i==5){
                System.out.println("小弟主线程礼让,大哥子线程开始吃");
                m.join();//线程插队
//                m.yield();//线程礼让,不一定成功
                System.out.println("大哥子线程吃完,主线程小弟继续吃");
            }
        }
    }
}
class M extends Thread{
    @Override
    public void run() {
        for (int i = 0; i <20 ; i++) {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("大哥(子线程)吃"+(i+1)+"个包子"+" "+Thread.currentThread().getName());
        }
    }
}

用户线程和守护线程

概念:

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

  2. 守护线程:一般为工作线程服务,当所有的用户线程结束,守护线程自动结束

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

    设置守护线程:setDaemon(bool);

    代码:

 public static void main(String[] args) throws InterruptedException {
        green green = new green();
//        当主线程的内容(其他用户线程)先结束,子线程(设置成守护线程)就会自己结束
        green.setDaemon(true);
        green.start();
        for (int i = 0; i <5 ; i++) {
            System.out.println("宝强在辛苦工作");
            Thread.sleep(1000);
        }
    }
}
class green extends Thread{
    @Override
    public void run() {
        while (true){
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("马蓉和宋hhh");
        }
    }
}

线程7种生命周期

1641106930689

    public static void main(String[] args) throws InterruptedException {
        One one = new One();
        新建时
        System.out.println("新建时线程名:"+one.getName()+"状态:"+one.getState());
        one.start();
        while (Thread.State.TERMINATED!=one.getState()){
            Thread.sleep(1000);
//            只要状态不等于结束时的状态就一直打印
            启动时
            System.out.println("启动时线程名:"+one.getName()+"状态:"+one.getState());
        }
        结束时
        System.out.println("结束时线程名:"+one.getName()+"状态:"+one.getState());
    }
}
class One extends Thread{
    @Override
    public void run() {
            for (int i = 0; i <10 ; i++) {
                System.out.println("你好");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
        }
    }
}

同步机制

多线程实现售票

问题代码:

 public static void main(String[] args) {
        T1 t1 = new T1();
        T1 t2 = new T1();
        T1 t3 = new T1();
//        这里会出现超卖的情况
        t1.start();
        t2.start();
        t3.start();
    }
}
class T1 extends Thread{
    private static int ticket=100;
    @Override
    public void run(){
        while (true){
            if (ticket<=0){
                System.out.println("售票结束。。。");
                break;
            }
            System.out.println("窗口"+Thread.currentThread().getName()+"卖出一张票:"+"剩余票数:"+(--ticket));
            try {
                Thread.sleep(50);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

上面的代码会出现重票超卖的问题

超卖:(可能超卖也可以不超卖,不安全),由于你给了休眠时间,当一个线程休眠后,另外一个线程执行刚好为0,第一个线程恢复又-1,所以就会出现超卖现象

线程终止

通知退出:在一个线程里设置一个控制变量,另一个线程控制这个控制变量

public static void main(String[] args) throws InterruptedException {
        T t = new T();
        t.start();
//        设置主线程的休眠时间然后控制子线程变量
        Thread.sleep(10000);
        t.setT(false);
    }
}
class  T extends Thread{
    //        设置控制变量
   private Boolean T=true;
   private int count = 0;
    @Override
    public void run() {
        while (T){
            System.out.println(Thread.currentThread().getName()+" "+(count++));
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
    public void setT(Boolean t) {
        T = t;
    }

解决卖票问题

同步机制

什么叫同步?

当有一个共享数据被多个线程访问到(例如上面卖票),这个时候就需要同步机制,保证数据在任何的同一时刻只能被一个线程所访问到

同步用到的方法

  1. 同步代码块

    ​ synchronized (对象){

    同步代码

    }

    2.同步方法

public synchronized void m(){

同步代码

}

理解:

就像上厕所一样为了你滴隐私(为了数据安全),会有一个门(同步锁),只有当一个人出来后,另一个人才能关上门(拿到锁)进去

同步方法解决卖票问题

同步方法这里是不能使用继承Thread的方式的,因为同步方法默认就是this当前对象,而实例化3个继承了Thread的对象就会有3个不同的对象,他们拿的锁的对象不同

    public static void main(String[] args) {
        T1 t1 = new T1();
        Thread T1 = new Thread(t1);
        Thread T2 = new Thread(t1);
        Thread T3 = new Thread(t1);
//        出现超卖的情况
        T1.start();
        T2.start();
        T3.start();
    }
}
class T1 implements Runnable{
       private static int ticket=100;
       private Boolean T=true;         //通知退出变量
       public synchronized void sell(){//同步方法,在同一个时刻只有一个线程来执行这个方法
               if (ticket<=0){
                   System.out.println("售票结束。。。");
                   T=false;
                   return;
               }
           try {
               Thread.sleep(50);
           } catch (InterruptedException e) {
               e.printStackTrace();
           }
               System.out.println("窗口"+Thread.currentThread().getName()+"卖出一张票:"+"剩余票数:"+(--ticket));
       }
    @Override
    public void run(){
        while (T) {
            sell();
        }
    }

互斥锁

  1. 每个对象都对应着一个可称为”互斥锁“的标记,这个标记保证任何时刻都只有一个线程访问该对象
  2. 同步的局限:变成单线程,影响效率
  3. 同步方法(静态)的锁为类本身,同步方法(非静态)的锁可以是this也可以是其他对象要求是同一个对象
  4. 只有拿到互斥锁,同步代码才能算是同步
核心点(*):

保证同步的关键就在保证锁是同一个对象,咱几个线程等的是同一个对象(就是必须保证是咱几个人等的是同一个厕所)

使用继承Thread的线程用同步代码块的方式解决售票问题    
public static void main(String[] args) {
        T t = new T();
        T t2 = new T();
        T t3= new T();
        t.start();
        t2.start();
        t3.start();
    }
}
class T extends Thread{
    public static int ticket=100;
    private static Object o=new Object();//共用一个对象o
    private Boolean T=true;         //通知退出变量
    @Override
    public void run(){
        while (T) {
            synchronized (o){
                if (ticket<=0){
                    System.out.println("售票结束。。。");
                    T=false;
                    return;
                }
                try {
                    Thread.sleep(50);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("窗口"+Thread.currentThread().getName()+"卖出一张票:"+"剩余票数:"+(--ticket));
            }
        }
    }
}

同步方法是静态或者非静态:

class t implements Runnable{
    public static  void m(){
        synchronized (t.class){
            System.out.println("当是静态方法时,锁必须是当前类");
        }
    }
    public void n(){
        synchronized (this){
            System.out.println("当不是静态方法,锁可以是当前对象或者其他对象");
        }
    }
    @Override
    public void run() {
            m();
    }

死锁

例:

比如两个掏枪的人,第一个人对另外一个人说,你放下枪,我就放下枪,

结果第二个人对第一个人也这么说,你放下枪,我就放下枪,两个人就陷入死循环

死锁代码及解决:
 public static void main(String[] args) {
        T t1 = new T(true);//true,使代码先执行o1,o1后面要用o2,而o2在下面那个线程那里
        T t2 = new T(false);//false,使代码先执行o2,o2后面要用o1,而o1在上面那个线程那里
        t1.start();
        t2.start();
    }
}
class T extends Thread{
    static Object o1 = new Object();
    static Object o2 = new Object();
    private Boolean T;
    T(Boolean T){
        this.T=T;
    }
    @Override
    public void run() {
            if (T){
                synchronized (o1){
                    System.out.println("A"+Thread.currentThread().getName()+"进入1");
                    synchronized (o2){
                        System.out.println("B"+Thread.currentThread().getName()+"进入2");
                    }
                }
            }else{
                synchronized (o2){
                    try {
//                        解决:设置休眠,就可以先让上面的代码拿到o2锁
                        Thread.sleep(10000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println("B"+Thread.currentThread().getName()+"进入3");
                    synchronized (o1){
                        System.out.println("A"+Thread.currentThread().getName()+"进入4");
                    }
                }
        }
}
}

释放锁

1641173884479

posted @ 2022-03-14 18:04  又菜又ai  阅读(44)  评论(0)    收藏  举报