第17章 多线程基础

一、线程介绍

1.程序

程序是为完成特定任务,用某种语言编写的一组指令的集合。简单说:就是我们写的代码

2.进程

  • 进程是指运行中的程序

例如:使用QQ,就启动了一个进程,操作系统就会为该进程分配内存空间;使用迅雷,又启动了一个进程,操作系统将为迅雷分配新的内存空间。

  • 进程是程序的一次执行过程,或是正在运行的一个程序。是动态过程,有自身的产生、存在、消亡的过程。

3.线程

  • 线程由进程创建的,是进程的一个实体。
  • 一个进程可以拥有多个线程。(例如:在迅雷中,同时下载多部电影,就是一个进程中存在多个线程。)
  • 单线程:同一个时刻,只允许执行一个线程。
  • 多线程:同一个时刻,可以执行多个线程。

4.并发

同一个时刻,多个任务交替执行,造成一种“貌似同时”的错觉。简单说:单核CPU实现多任务就是并发。

5.并行

  • 同一个时刻,多个任务同时执行。多核CPU可以实现并行。

  • 并发和并行可能同时存在。

6. 查看当前电脑有多少cpu/核心?

方法1:

方法2:

方法3:

public class cpuNumber {
    public static void main(String[] args) {
        //java.lang.Runtime是经典的单例模式
        Runtime runtime = Runtime.getRuntime();
        //获取当前电脑的cpu数量/核心数
        int cpuNums = runtime.availableProcessors();
        System.out.println("当前电脑的cpu数量:" + cpuNums);//8
    }
}

二、线程使用

使用线程的两种方式:
1.继承Thread类,重写run方法。
2.实现Runnable接口,重写run方法。

1.继承Thread类

(1)线程应用案例1

  • 编写程序:开启一个线程,该线程每隔1秒,在控制台输出“喵喵,我是小猫咪”。
  • 改进上题:当输出80次“喵喵,我是小猫咪”,结束该线程。
  • 使用JConsole监控线程执行情况,并画出程序示意图。
1)线程的执行情况

运行程序“Run‘Thread01.main()’”启动一个进程,随后进入main方法启动一个main线程,当main线程执行到tom.start()时启动一个子线程(Thread-0)。

  • 当main线程启动一个子线程Thread-0,主线程不会阻塞,会继续执行。此时,main线程与子线程交替执行。
  • 当所有线程都结束后,进程才结束。

 2)为何在main方法调用start方法,而不是run方法呢?

start():启动线程,最终会执行run方法。main线程不会因为子线程而阻塞,会继续执行;此时,main线程和Thread-0线程交替执行。

run():run方法是一个普通方法,不会启动线程。会把run方法执行完毕后,才向下执行,run方法仍旧在main线程中。

public class Thread01 {
    public static void main(String[] args) throws InterruptedException {

        //创建Cat对象,可以当作线程使用
        Cat tom = new Cat();
        //启动线程-->最终会执行tom的run方法
        //main线程不会因子线程而阻塞,会继续执行;此时,main线程和Thread-0线程交替执行
        tom.start();
        /*start源码:
            (1)调用父类Thread的start方法,start方法中调用start0方法
            public synchronized void start() {
                start0();
            }
            (2)start0方法是本地方法,是JVM调用的,底层是c/c++实现的
               真正实现多线程效果的是start0,而不是run
            private native void start0();
         */
        //run方法时一个普通方法,没有真正地启动一个线程
        //会把run方法执行完毕后,才向下执行。run方法中打印的线程名是main。
        tom.run();

        for (int i = 0; i <= 6; i++) {
            System.out.println("主线程" + Thread.currentThread().getName() + " i=" + i );//main
            //让main线程休眠1s
            Thread.sleep(1000);
        }
    }

    /*执行cat.start()输出结果:
        main线程不会因子线程而阻塞,会继续执行
        喵喵,我是小猫咪1 线程名:Thread-0
        主线程main i=0
        主线程main i=1
        喵喵,我是小猫咪2 线程名:Thread-0
        主线程main i=2
        喵喵,我是小猫咪3 线程名:Thread-0
        喵喵,我是小猫咪4 线程名:Thread-0
        主线程main i=3
        主线程main i=4
        喵喵,我是小猫咪5 线程名:Thread-0
        主线程main i=5
        喵喵,我是小猫咪6 线程名:Thread-0
        主线程main i=6
        喵喵,我是小猫咪7 线程名:Thread-0
        喵喵,我是小猫咪8 线程名:Thread-0
     */
    /*执行cat.run()输出结果:
        喵喵,我是小猫咪1 线程名:main
        喵喵,我是小猫咪2 线程名:main
        喵喵,我是小猫咪3 线程名:main
        喵喵,我是小猫咪4 线程名:main
        喵喵,我是小猫咪5 线程名:main
        喵喵,我是小猫咪6 线程名:main
        喵喵,我是小猫咪7 线程名:main
        喵喵,我是小猫咪8 线程名:main
        主线程main i=0
        主线程main i=1
        主线程main i=2
        主线程main i=3
        主线程main i=4
        主线程main i=5
        主线程main i=6
     */
}

//通过继承Thread来使用线程,当一个类继承了Thread类,该类就可以当作线程使用
class Cat extends Thread {
    int times = 0;//记录输出“喵喵,我是小猫咪”的次数

    //重写run方法,写上自己的业务代码
    //Runnable接口中声明了run方法,Thread类实现该接口重写了run方法,Cat类继承Thread类重写run方法
    /*Thread类中的run方法源码:
        @Override
        public void run() {
            if (target != null) {
                target.run();
            }
        }
     */
    @Override
    public void run() {
        while (true) {
            //Thread.currentThread().getName():获得线程名称
            System.out.println("喵喵,我是小猫咪" + (++times) + " 线程名:" + Thread.currentThread().getName());//Thread-0

            //这里try-catch是保证该线程在sleep时还能感知响应,能够响应中断,不会睡死
            try {
                //让该线程休眠1s
                Thread.sleep(1000);//1000ms = 1s
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            //当times到80时,退出while循环,这时线程也就退出了
            if(times == 8) {
                break;
            }
        }
    }

}

2.实现Runable接口

来源:Java是单继承机制,在某些情况下一个类已经继承了某个父类,这时就不能使用“继承Thread类”的方法创建线程------>实现Runnable接口创建线程。

(1)线程应用案例2

请编写程序:每隔1s在控制台输出“小狗汪汪叫...hi”,当输出10次后,自动退出。
使用“实现Runnable接口”的方式实现,这里底层使用了设计模式(代理模式)

public class Thread02 {
    public static void main(String[] args) {
        Dog dog = new Dog();
//        dog.start();//报错,因为start方法是Thread类的方法,Runnable接口只有run方法
        //创建Thread对象,将实现了Runnable接口的dog放入Thread
        Thread thread = new Thread(dog);
        thread.start();
    }
}

//通过实现Runnable接口使用线程,当一个类实现了Runnable接口,该类就可以当作线程使用
class Dog implements Runnable {
    int count = 0;//记录输出“小狗汪汪叫...hi”的次数

    //重写run方法,写上自己的业务代码
    //Runnable接口声明了run方法,Dog类实现了Runnable接口重写run方法
    @Override
    public void run() {
        while (true) {
            System.out.println("小狗汪汪叫...hi" + (++count) + "线程名:" + Thread.currentThread().getName());

            //如果输出了10次,就结束while循环
            if(count == 10) {
                break;
            }
        }
    }
}
1)模拟一个最简单的代理模式(静态)
public class ProxyPattern {
    public static void main(String[] args) {
        Tiger tiger = new Tiger();//创建一个Tiger对象,tiger实现了Runnable接口
        ThreadProxy threadProxy = new ThreadProxy(tiger);
        threadProxy.start();
        //(1)调用threadProxy的start方法
        //(2)start方法调用start0方法
        //(3)start0方法调用run方法
        //(4)run方法调用了tiger的run方法(动态绑定)

    }
}

class Animal{}
class Tiger extends Animal implements Runnable{
    @Override
    public void run() {
        System.out.println("老虎嗷嗷叫...");
    }
}

//线程代理类:将ThreadProxy类视为Thread类
class ThreadProxy implements Runnable{
    //属性类型:Runnable
    private Runnable target = null;

    //有参构造器,传入一个实现Runnable接口的对象,赋给target
    public ThreadProxy(Runnable target) {
        this.target = target;
    }

    //start方法中调用start0方法
    public void start() {
        start0();
    }

    //start0方法调用run方法
    public void start0() {
        run();
    }

    @Override
    public void run() {
        if(target != null) {
            target.run();//动态绑定 tiger 编译类型:Runnable 运行类型:Tiger
        }
    }
}

(2)线程应用案例3——多线程执行

请编写程序:创建2个线程,一个线程每隔1s输出“hello,world”,输出10次,退出;一个线程每隔1s输出“hi”,输出5次,退出。

public class Thread03 {
    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();//启动第1个线程
        thread2.start();//启动第2个线程

        /*输出结果:
            hello,world 1
            hi 1
            hi 2
            hello,world 2
            hello,world 3
            hi 3
            hi 4
            hello,world 4
            hi 5
            hello,world 5
            hello,world 6
            hello,world 7
            hello,world 8
            hello,world 9
            hello,world 10
         */
    }
}

class T1 implements Runnable {
    int count = 0;//记录输出"hello,world "的次数

    @Override
    public void run() {
        while (true) {
            System.out.println("hello,world " + (++count));
            try {
                //线程休眠1s
                Thread.sleep(1000);//1000ms = 1s
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            //当输出10次"hello,world "后,跳出while循环
            if(count == 60) {
                break;
            }
        }
    }
}

class T2 implements Runnable {
    int count = 0;//记录输出"hi "的次数

    @Override
    public void run() {
        while (true) {
            System.out.println("hi " + (++count));
            try {
                //线程休眠1s
                Thread.sleep(1000);//1000ms = 1s
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            //当输出5次“hi”时,跳出while循环
            if(count == 50) {
                break;
            }
        }
    }
}

(3)线程应用案例4——售票系统

编程模拟3个售票窗口售票,分别使用“继承Thread类”和“实现Runnable接口”方式,并分析有什么问题?

答:会出现超售现象,例如当余票为1时,若三个线程同时抵达ticket <= 0时,会都执行--ticket,余票会出现-1等情况。---->Synchronized

public class SellTicket {
    public static void main(String[] args) {

//        SellTicket01 sellTicket01 = new SellTicket01();
//        SellTicket01 sellTicket02 = new SellTicket01();
//        SellTicket01 sellTicket03 = new SellTicket01();
//
//        sellTicket01.start();//启动第1个线程
//        sellTicket02.start();//启动第2个线程
//        sellTicket03.start();//启动第3个线程

        /*输出结果(部分):
            窗口 Thread-1售出一张票,余票=10
            窗口 Thread-2售出一张票,余票=9
            窗口 Thread-1售出一张票,余票=8
            窗口 Thread-0售出一张票,余票=7
            窗口 Thread-2售出一张票,余票=6
            窗口 Thread-1售出一张票,余票=5
            窗口 Thread-0售出一张票,余票=4
            窗口 Thread-0售出一张票,余票=3
            窗口 Thread-1售出一张票,余票=2
            窗口 Thread-2售出一张票,余票=1
            窗口 Thread-1售出一张票,余票=0
            售票结束..
            窗口 Thread-0售出一张票,余票=-1
            售票结束..
            窗口 Thread-2售出一张票,余票=-2
            售票结束..
         */
        //会出现超卖现象

//        SellTicket02 sellTicket02 = new SellTicket02();
//        new Thread(sellTicket02).start();
//        new Thread(sellTicket02).start();
//        new Thread(sellTicket02).start();

        /*输出结果(部分):
            窗口 Thread-3售出一张票,余票=9
            窗口 Thread-2售出一张票,余票=7
            窗口 Thread-1售出一张票,余票=8
            窗口 Thread-3售出一张票,余票=6
            窗口 Thread-1售出一张票,余票=5
            窗口 Thread-2售出一张票,余票=4
            窗口 Thread-2售出一张票,余票=3
            窗口 Thread-1售出一张票,余票=1
            窗口 Thread-3售出一张票,余票=2
            窗口 Thread-3售出一张票,余票=0
            售票结束..
            窗口 Thread-1售出一张票,余票=-1
            售票结束..
            窗口 Thread-2售出一张票,余票=-2
            售票结束..
         */
        //会出现超卖现象

        SellTicket03 sellTicket03 = new SellTicket03();
        new Thread(sellTicket03).start();
        new Thread(sellTicket03).start();
        new Thread(sellTicket03).start();

        /*输出结果:
            窗口 Thread-1售出一张票,余票=5
            窗口 Thread-1售出一张票,余票=4
            窗口 Thread-1售出一张票,余票=3
            窗口 Thread-1售出一张票,余票=2
            窗口 Thread-1售出一张票,余票=1
            窗口 Thread-1售出一张票,余票=0
            售票结束..
            售票结束..
            售票结束..
          当票售空后,3个线程都会再访问一次sell方法,输出"售票结束.."
         */
    }
}

//使用”继承Thread类“的方式使用线程
class SellTicket01 extends Thread {
    private static int ticketNum = 100;//让多个线程共享 ticketNum

    @Override
    public void run() {
        while (true) {
            if(ticketNum <= 0) {
                System.out.println("售票结束..");
                break;
            }

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

            System.out.println("窗口 " + Thread.currentThread().getName() + "售出一张票,余票=" + (--ticketNum));

        }
    }
}

//使用”实现Runnable接口“的方式使用线程
class SellTicket02 implements Runnable {
    private int ticketNum = 100;//让多个线程共享 ticketNum

    @Override
    public void run() {
        while (true) {
            if(ticketNum <= 0) {
                System.out.println("售票结束..");
                break;
            }

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

            System.out.println("窗口 " + Thread.currentThread().getName() + "售出一张票,余票=" + (--ticketNum));

        }
    }
}

class SellTicket03 implements Runnable {
    private int ticketNum = 100;//让多个线程共享 ticketNum
    private boolean loop = true;
    Object object = new Object();

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

    //synchronized修饰sell方法-->sell为同步方法-->在任何同一时刻,最多只有一个线程访问sell方法
    //synchronized修饰非静态方法,锁的是this对象
    public /*synchronized*/ void sell() {

        //synchronized修饰代码块-->同步代码块-->锁的是this对象
        //synchronized也可以锁其他对象(但要求是同一个对象)
//        synchronized (this) {
        synchronized (object) {
            if (ticketNum <= 0) {
                System.out.println("售票结束..");
                loop = false;//使run方法结束循环买票
                return;//结束sell方法
            }

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

            System.out.println("窗口 " + Thread.currentThread().getName() + "售出一张票,余票=" + (--ticketNum));
        }
    }

    //synchronized修饰静态方法,锁的是类SellTicket03.class
    public synchronized static void m1() {}
    //在静态方法中,使用同步代码块,锁的只能是类SellTicket03.class
    public static void m2() {
        synchronized (SellTicket03.class) {
            System.out.println("m2");
        }
    }
}

 3.通知线程退出

  • 线程完成任务后,会自动退出。
  • 使用变量来控制run方法退出进而停止线程,即通知方式
    (在子线程定义控制变量,并提供一个公共的set方法可以修改该控制变量,在main线程中修改控制变量,以达到main线程控制子线程终止的目的)
public class ThreadExit_ {
    public static void main(String[] args) throws InterruptedException {
        T t1 = new T();
        t1.start();

        //修改loop-->main线程控制t1线程的终止-->通知方式
        Thread.sleep(10 * 1000);//main线程休眠10s
        t1.setLoop(false);
    }
}

//通过"继承Thread类"使用线程
class T extends Thread {
    private int count = 0;//记录输出"T 线程正在运行..." + (++count)"的次数
    //设置一个控制变量,用于main线程控制T线程的终止
    private boolean loop = true;

    //提供公共的方法,用于更新loop
    public void setLoop(boolean loop) {
        this.loop = loop;
    }

    //重写run方法,写上自己的业务代码
    //Runnable接口中声明了run方法,Thread类实现了Runnable接口重写了run方法,T类继承Thread类重写run方法
    //run方法:每隔50ms输出"T 线程正在运行..." + (++count)"
    @Override
    public void run() {
        while (loop) {
            try {
                Thread.sleep(50);//休眠50ms
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            System.out.println("T 线程正在运行..." + (++count));
        }

    }
}

三、线程方法

1.常用方法1

  • setName:设置线程名称,使之与参数name相同。
  • getName:返回线程名称。
  • start启动新线程,Java虚拟机底层调用该线程的start0方法,然后调用run方法。
  • run:调用线程对象的run方法,就是一个普通方法,不会启动新线程
  • setPriority:更改线程的优先级。
  • getPriority:获取线程的优先级。

线程优先级(java.lang.Thread):

MAX_PRIOPRITY10
MIN_PRIORITY1
NORM-PRIORITY5
  • sleep:在指定的毫秒数内让当前正在执行的线程休眠(暂停执行),线程的静态方法。
  • interrupt:中断线程,不是终止线程,一般用于中断正在休眠的线程。
public class ThreadMethod01 {
    public static void main(String[] args) throws InterruptedException {
        T t1 = new T();
        //t1.setName():设置线程名称为老韩
        t1.setName("老韩");
        //t1.setPriority():设置线程优先级,MIN_PRIORITY=1
        t1.setPriority(Thread.MIN_PRIORITY);
        //启动线程
        t1.start();

        //main线程每隔1s输出“hi”,输出5次后,中断t1线程的休眠
        for (int i = 0; i < 5; i++) {
            Thread.sleep(1000);//休眠1s
            System.out.println("hi" + i);
        }
        //t1.getName():获得线程的名称
        //t1.getPriority():获得线程的优先级
        System.out.println(t1.getName() + " 优先级是: " + t1.getPriority());
        t1.interrupt();//中断t1线程的休眠
        
        /*输出结果:循环输出下面的结果
            老韩吃包子..0
            老韩吃包子..1
            老韩吃包子..2
            老韩吃包子..3
            老韩吃包子..4
            老韩吃包子..5
            老韩吃包子..6
            老韩吃包子..7
            老韩吃包子..8
            老韩吃包子..9
            老韩休眠中..
            hi0
            hi1
            hi2
            hi3
            hi4
            老韩 优先级是: 1
            老韩被中断了
            老韩吃包子..0
            老韩吃包子..1
         */
    }
}

//通过”继承Thread类“使用线程
class T extends Thread {
    //重写run方法,写上自己的业务代码
    //在Runnable接口中声明了run方法,Thread类实现了Runnable接口重写了run方法,T类继承Thread类重写run方法
    //run方法:每隔20s连续1输出Thread.currentThread().getName() + "吃包子.."10次
    @Override
    public void run() {
        while (true) {
            for (int i = 0; i < 10; i++) {
                //Thread.currentThread().getName():获取当前线程的名称
                System.out.println(Thread.currentThread().getName() + "吃包子.." + i);
            }

            try {
                System.out.println(Thread.currentThread().getName() + "休眠中..");
                //Thread.sleep():在指定的毫秒数内让当前正在执行的线程休眠(暂停执行),线程的静态方法
                Thread.sleep(20000);//休眠20000ms=20s
            } catch (InterruptedException e) {
                //当线程执行到interrupt方法时,就会catch中断异常,可以加入自己的业务代码
                //InterruptedException:捕获到一个中断异常
                System.out.println(Thread.currentThread().getName() + "被中断了");
            }
        }
    }
}

2.常用方法2

  • yield:线程的礼让。让出CPU,让其他线程执行,但礼让的时间不确定,所以也不一定礼让成功。
  • join:线程的插队。插队的线程一旦插队成功,则肯定先执行完插入的线程所有的任务后再执行原线程。

案例:创建一个子线程(小弟),每隔1s吃1个包子,共吃20个;主线程(老大)也每隔1s吃1个包子,共吃20个。要求:两个线程同时执行,当主线程(小弟)吃了5个包子后,就让子线程(老大)先吃完20个,主线程(小弟)再吃。

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);//休眠1s
            System.out.println("主线程(小弟)吃了" + i + "个包子");
            if(i == 5) {
                System.out.println("主线程(小弟)让子线程(老大)先吃");
                //t2.join():线程插队,一定会成功。
                t2.join();
                //Thread.yield():线程礼让,不一定会成功。
//                Thread.yield();
                System.out.println("子线程(老大)吃完了,主线程(小弟)接着吃..");
            }
        }
    }
    /*输出结果:
        主线程(小弟)吃了1个包子
        子线程(老大)吃了1个包子
        子线程(老大)吃了2个包子
        主线程(小弟)吃了2个包子
        主线程(小弟)吃了3个包子
        子线程(老大)吃了3个包子
        主线程(小弟)吃了4个包子
        子线程(老大)吃了4个包子
        子线程(老大)吃了5个包子
        主线程(小弟)吃了5个包子
        主线程(小弟)让子线程(老大)先吃
        子线程(老大)吃了6个包子
        子线程(老大)吃了7个包子
        子线程(老大)吃了8个包子
        子线程(老大)吃了9个包子
        子线程(老大)吃了10个包子
        子线程(老大)吃了11个包子
        子线程(老大)吃了12个包子
        子线程(老大)吃了13个包子
        子线程(老大)吃了14个包子
        子线程(老大)吃了15个包子
        子线程(老大)吃了16个包子
        子线程(老大)吃了17个包子
        子线程(老大)吃了18个包子
        子线程(老大)吃了19个包子
        子线程(老大)吃了20个包子
        子线程(老大)吃完了,主线程(小弟)接着吃..
        主线程(小弟)吃了6个包子
        主线程(小弟)吃了7个包子
        主线程(小弟)吃了8个包子
        主线程(小弟)吃了9个包子
        主线程(小弟)吃了10个包子
        主线程(小弟)吃了11个包子
        主线程(小弟)吃了12个包子
        主线程(小弟)吃了13个包子
        主线程(小弟)吃了14个包子
        主线程(小弟)吃了15个包子
        主线程(小弟)吃了16个包子
        主线程(小弟)吃了17个包子
        主线程(小弟)吃了18个包子
        主线程(小弟)吃了19个包子
        主线程(小弟)吃了20个包子
     */
}

//使用“继承Thread类”来使用线程
class T2 extends Thread {
    //重写run方法,写上自己的业务代码
    //在Runnable接口中声明了run方法,Thread类实现了Runnable接口重写了run方法,T2类继承Thread类重写run方法
    //run方法:每隔1s输出"JoinThread.."
    @Override
    public void run() {
        for (int i = 1; i <= 20; i++) {
            try {
                Thread.sleep(1000);//休眠1s
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("子线程(老大)吃了" + i + "个包子");
        }
    }
}

3.用户线程和守护线程

  • 用户线程:也叫工作线程,线程任务执行完毕或者以通知方式来终止线程。
  • 守护线程:一般是为工作线程服务的,当所有的用户线程结束,守护线程自动结束。
    常见的守护线程:垃圾回收机制。
myDaemonThread.setDaemon(true);
public class ThreadMethod03 {
    public static void main(String[] args) throws InterruptedException {
        MyDaemonThread myDaemonThread = new MyDaemonThread();
        //将myDaemonThread线程设为守护线程——主线程结束,守护线程也会结束
        myDaemonThread.setDaemon(true);
        myDaemonThread.start();

        for (int i = 1; i <= 10; i++) {
            System.out.println("宝强在努力工作...");
            Thread.sleep(1000);//休眠1s
        }
    }
}

//通过"继承Thread类"使用线程
class MyDaemonThread extends Thread {
    //重写run方法,写上自己的业务逻辑
    //在Runnable接口中声明了run方法,Thread类实现了Runnable接口重写了run方法,MyDaemonThread类继承Thread类重写run方法
    //run方法:每隔1s输出"马蓉和宋喆快乐聊天..."
    @Override
    public void run() {
        for( ; ; ) {//无限循环
            try {
                Thread.sleep(1000);//休眠1s
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("马蓉和宋喆快乐聊天...");
        }
    }
}

四、线程生命周期

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

NEW初始状态线程被创建出来,但还未被调用(start())
RUNNABLE可运行状态

线程被调用了start()

Ready(就绪状态)、Runing(运行状态)

BLOCKED阻塞状态需要等待锁释放
WAITING等待状态线程需要等待其他线程做出一些特定动作(通知或中断)
TIMED_WAITING超时等待状态线程在指定的时间后自行返回,而不是像WAITING一样一直等待
TERMINATED终止状态表示线程已经执行完毕

public class ThreadState_ {
    public static void main(String[] args) throws InterruptedException {
        T t = new T();
        //创建t线程后,输出t线程状态
        System.out.println("线程:" + t.getName() + " 状态:" + t.getState());

        //启动t线程后,只要t线程没有终止,每隔0.5s输出一次线程状态
        t.start();
        while (Thread.State.TERMINATED != t.getState()) {
            System.out.println("线程:" + t.getName() + " 状态:" + t.getState());
            Thread.sleep(500);
        }

        //t线程终止后,输出线程状态
        System.out.println("线程:" + t.getName() + " 状态:" + t.getState());
    }
    /*输出结果:
        线程:Thread-0 状态:NEW
        线程:Thread-0 状态:RUNNABLE
        线程:Thread-0 状态:TIMED_WAITING
        线程:Thread-0 状态:RUNNABLE
        hi1
        线程:Thread-0 状态:TIMED_WAITING
        hi2
        线程:Thread-0 状态:TIMED_WAITING
        线程:Thread-0 状态:TIMED_WAITING
        hi3
        线程:Thread-0 状态:TIMED_WAITING
        线程:Thread-0 状态:TIMED_WAITING
        hi4
        线程:Thread-0 状态:TIMED_WAITING
        线程:Thread-0 状态:TIMED_WAITING
        hi5
        线程:Thread-0 状态:TIMED_WAITING
        线程:Thread-0 状态:TIMED_WAITING
        hi6
        线程:Thread-0 状态:TIMED_WAITING
        线程:Thread-0 状态:TIMED_WAITING
        hi7
        线程:Thread-0 状态:TIMED_WAITING
        线程:Thread-0 状态:TIMED_WAITING
        hi8
        线程:Thread-0 状态:TIMED_WAITING
        线程:Thread-0 状态:TIMED_WAITING
        hi9
        线程:Thread-0 状态:TIMED_WAITING
        线程:Thread-0 状态:TIMED_WAITING
        hi10
        线程:Thread-0 状态:TERMINATED
     */
}

class T extends Thread {
    @Override
    public void run() {
        for (int i = 1; i <= 10; i++) {
            try {
                Thread.sleep(1000);
                System.out.println("hi" + i);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

五、Synchronized

1.同步

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

2.同步具体方法

(1)同步代码块

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

(2)synchronized修饰方法,表示整个方法为同步方法

public synchronized void 方法名 (形参列表) {
        //需要被同步的代码
}

3.如何理解?

某小伙伴上厕所前先把门关上(上锁),完事后再打开门(解锁),那么其他小伙伴就可以使用厕所了。

三个线程t1、t2、t3同时抢一把锁。假如t1抢到了锁,执行synchronized修饰的方法/代码块。执行完毕后,将锁释放。然后三个线程再同时抢锁...

公平锁非公平锁
锁被释放后,先申请的线程先得到锁。锁被释放后,后申请的线程可能会先获取到锁,是随机或者按照其他优先级排序的。
性能较差一些,因为公平锁为了保证时间上的绝对顺序,上下文切换更频繁。性能更好,但可能会导致某些线程永远无法获取到锁。

六、互斥锁

  • 保证共享数据操作的完整性。
  • 每个对象都对应于一个可称为“互斥锁”的标记,用来保证在任一时刻,只能有一个线程访问该对象。关键字synchronized就是互斥锁。
  • 同步的局限性:程序的执行效率要降低。
  • 同步方法(非静态的):锁this对象/其他对象(要求是多个线程锁的是同一个对象)
  • 同步方法(静态的):锁当前类.class

七、死锁

1.基本介绍

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

public class DeadLock_ {
    public static void main(String[] args) {
        DeadLockDemo A = new DeadLockDemo(true);
        A.setName("A线程");//setName():给当前线程设置名字
        DeadLockDemo B = new DeadLockDemo(false);
        B.setName("B线程");
        A.start();
        B.start();

        /*输出结果:(死锁了)
            A线程进入1
            B线程进入3
         */
    }
}

//通过"继承Thread类"使用线程
class DeadLockDemo extends Thread {
    static Object o1 = new Object();//使用static,保证多个线程共享一个对象
    static Object o2 = new Object();
    boolean flag;

    //构造器
    public DeadLockDemo(boolean flag) {
        this.flag = flag;
    }

    //业务逻辑分析:
    //1.如果 flag 为 true,线程A 就会先得到 o1 对象锁,然后尝试去获取 o2 对象锁
    //2.如果线程A 得不到 o2 对象锁,就会Blocked
    //3.如果 flag 为 false,线程B 就会先得到 o2 对象锁,然后尝试去获取 o1 对象锁
    //4.如果线程B 得不到 o1 对象锁,就会Blocked
    @Override
    public void run() {
        if(flag) {
            synchronized (o1) {//对象互斥锁,下面就是同步代码
                System.out.println(Thread.currentThread().getName() + "进入1");
                /*Thread类中:
                    public static native Thread currentThread();
                    方法currentThread():返回对当前执行的线程对象的引用。

                    public final String getName() {
                        return name;
                    }
                    方法getName():返回当前线程的名字
                 */
                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");
                }
            }
        }
    }
}

2.释放锁分析

下面操作释放锁:

  • 当前线程的同步方法、同步代码块执行结束
  • 当前线程在同步方法、同步代码块中遇到break\return
  • 当前线程在同步方法、同步代码块中出现了未处理的error或exception,导致异常结束
  • 当前线程在同步方法、同步代码块中执行了线程对象的wait()方法,当前线程暂停,并释放锁。

下面操作不会释放锁:

  • 线程执行同步方法或同步代码块时,程序调用Thread.sleep()、Thread.yield()方法暂停当前线程的执行,不会释放锁。
  • 线程执行同步方法或同步代码块时,其他线程调用了该线程的suspend()方法将该线程挂起,该线程不会释放锁。

注意:应尽量避免使用suspend()和resume()来控制线程。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值