Java学习第二十九天——多线程_线程控制

1.线程控制

1.1 线程控制之休眠线程(掌握)

A:线程休眠:

public static void sleep(long millis) 线程休眠

B:案例演示: 线程休眠
public static void main(String[] args) throws InterruptedException {
    //
    System.out.println("主线程开始执行了");
    Thread.sleep(2000);
    System.out.println("主线程下面的代码");
    //线程休眠:可以让当前正在执行的线程, 睡一会。
    //Thread(String name) 分配新的 Thread 对象。
    //通过有参构造,可以给线程起个名字
    MyThread th1 = new MyThread("A线程");
    MyThread th2 = new MyThread("B线程");

    th1.start();
    th2.start();
}
public class MyThread extends Thread{
    public MyThread(){}
    public MyThread(String name) {
        super(name);
    }

    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            try {
                Thread.sleep(1000); //单位是毫秒
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            String name1 = Thread.currentThread().getName();
            System.out.println(name1 + "===" + i);
        }
    }
}

1.2 线程控制之加入线程(掌握)

A:加入线程:

public final void join()
意思就是: 等待该线程执行完毕了以后,其他线程才能再次执行
注意事项: 在线程启动之后,在调用方法

B:案例演示: 加入线程
public static void main(String[] args) throws InterruptedException {
   /* A:
    加入线程:
    public final void join ()
    意思就是:等待该线程执行完毕了以后, 其他线程才能再次执行
    注意事项:
    在线程启动之后, 在调用方法
    join ()可以让多个线程并发执行,变成串行(挨个排队执行,不用抢)
    */

    MyThread th1 = new MyThread();
    MyThread th2 = new MyThread();
    MyThread th3 = new MyThread();
    th1.setName("刘备");
    th2.setName("关羽");
    th3.setName("张飞");
    th1.start();
    //注意:在线程启动之后, 在调用join()方法
    th1.join();
    th2.start();
    th2.join();
    th3.start();
    th3.join();
}
public class MyThread extends Thread{
    public MyThread(){}
    public MyThread(String name) {
        super(name);
    }

    @Override
    public void run() {
        for (int i = 0; i <20; i++) {
            String name1 = Thread.currentThread().getName();
            System.out.println(name1 + "===" + i);
        }
    }
}

1.3 线程控制之礼让线程(了解)

A:礼让线程:

public static void yield(): 暂停当前正在执行的线程对象,并执行其他线程。

B:案例演示: 礼让线程

按照我们的想法,这个礼让应该是一个线程执行一次,但是通过我们的测试,效果好像不太明显.
那是为什么呢?
这个礼让是要暂停当前正在执行的线程,这个暂停的时间是相当短的,如果在这个线程暂停完毕以后,其他的线程还没有
抢占到CPU的执行权,那么这个时候这个线程应该再次和其他线程抢占CPU的执行权.

public static void main(String[] args) throws InterruptedException {
   /* A:
    加入线程:
    public final void join ()
    意思就是:等待该线程执行完毕了以后, 其他线程才能再次执行
    注意事项:
    在线程启动之后, 在调用方法
    join ()可以让多个线程并发执行,变成串行(挨个排队执行,不用抢)
    */

    MyThread th1 = new MyThread();
    MyThread th2 = new MyThread();
    th1.setName("刘备");
    th2.setName("关羽");
    th1.start();
    th2.start();
}
public class MyThread extends Thread{
    public MyThread(){}
    public MyThread(String name) {
        super(name);
    }

    @Override
    public void run() {
        for (int i = 0; i <2000; i++) {
            String name1 = Thread.currentThread().getName();
            //线程礼让
            Thread.yield();
            System.out.println(name1 + "===" + i);
        }
    }
}

1.4 线程控制之守护线程(了解)

A:守护线程:

public final void setDaemon(boolean on):
将该线程标记为守护线程,当用户线程结束后,那么守护线程,就要马上结束。

B:案例演示: 守护线程
public static void main(String[] args) throws InterruptedException {
    Thread.currentThread().setName("刘备:主线程");
    for (int i = 0; i < 10; i++) {
        System.out.println(Thread.currentThread().getName()+"=="+i);
    }

    MyThread th1 = new MyThread();
    MyThread th2 = new MyThread();
    th1.setName("张飞");
    th2.setName("关羽");
    //设置为守护线程 当主线程死亡后,守护线程要立马死亡掉。
    //注意:setDaemon(true)该方法必须在启动线程前调用。
    th1.setDaemon(true);
    th2.setDaemon(true);
    th1.start();
    th2.start();
    System.out.println(Thread.currentThread().getName()+"退出了");
}
public class MyThread extends Thread{
    public MyThread(){}
    public MyThread(String name) {
        super(name);
    }

    @Override
    public void run() {
        for (int i = 0; i <2000; i++) {
            String name1 = Thread.currentThread().getName();
            System.out.println(name1 + "===" + i);
        }
    }
}
Java用户线程和守护线程
1.用户线程和守护线程的区别

用户线程和守护线程都是线程,区别是Java虚拟机在所有用户线程dead后,程序就会结束。而不管是否还有守护线程还在运行,若守护线程还在运行,则会马上结束。很好理解,守护线程是用来辅助用户线程的,如公司的保安和员工,各司其职,当员工都离开后,保安自然下班了。

2.用户线程和守护线程的适用场景

由两者的区别及dead时间点可知,守护线程不适合用于输入输出或计算等操作,因为用户线程执行完毕,程序就dead了,适用于辅助用户线程的场景,如JVM的垃圾回收,内存管理都是守护线程,还有就是在做数据库应用的时候,使用的数据库连接池,连接池本身也包含着很多后台线程,监听连接个数、超时时间、状态等。

3.创建守护线程

调用线程对象的方法setDaemon(true),设置线程为守护线程。
1)thread.setDaemon(true)必须在thread.start()之前设置。
2)在Daemon线程中产生的新线程也是Daemon的。
3)不是所有的应用都可以分配给Daemon线程来进行服务,比如读写操作或者计算逻辑。
因为Daemon Thread还没来得及进行操作,虚拟机可能已经退出了。

4.Java守护线程和Linux守护进程

两者不是一个概念。Linux守护进程是后台服务进程,没有控制台。
在Windows中,你可以运行javaw来达到释放控制台的目的,在Unix下你加&在命令的最后就行了。所以守护进程并非一定需要的。

1.5 线程控制之中断线程(了解)

A:中断线程

public final void stop(): 停止线程的运行
public void interrupt(): 中断线程(这个翻译不太好),查看API可得当线程调用wait(),sleep(long time)方法的时候处于阻塞状态,可以通过这个方法清除阻塞

public static void main(String[] args) throws InterruptedException {
    MyThread th1 = new MyThread();
    th1.setName("张飞");
    th1.start();
    Thread.sleep(2000);
    //让线程死亡
   // th1.stop();
    //清除线程的阻塞状态
    th1.interrupt();
}
public class MyThread extends Thread{
    public MyThread(){}
    public MyThread(String name) {
        super(name);
    }
    
    @Override
    public void run() {
        try {
            //线程休眠,就是线程处于了一种阻塞状态
            Thread.sleep(6000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        for (int i = 0; i <2000; i++) {
            String name1 = Thread.currentThread().getName();
            System.out.println(name1 + "===" + i);
        }
    }
}

2.线程程序实现方式

2.1多线程程序实现的方式2)(掌握)

A:实现Runnable接口 这种方式扩展性强 实现一个接口 还可以再去继承其他类

a:如何获取线程名称
b:如何给线程设置名称
c:实现接口方式的好处
可以避免由于Java单继承带来的局限性。

B:案例演示: 多线程程序实现的方式2
public static void main(String[] args) {
    //创建线程的第二种方式
    //1. 定义一个类实现 Runnable 接口。
    //2.重写该接口 run 方法。
    // 3.然后可以分配该类的实例,
    //4在创建 Thread 对象时作为一个参数来传递并启动。

/*    Thread(Runnable target)
    分配新的 Thread 对象。*/

   // Runnable 任务 接口应该由那些打算通过某一线程执行其实例的类来实现。
    MyRunnable myRunnable = new MyRunnable();
    Thread th1 = new Thread(myRunnable);
    th1.setName("A线程");
    th1.start();
    Thread th2 = new Thread(new MyRunable2());
    th2.setName("B线程");
    th2.start();
}
public class MyRunnable implements Runnable{

    //让线程来执行的方法
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            //System.out.println(this.getName()+"==="+i);
            Thread th = Thread.currentThread();
            System.out.println(th.getName() + "===" + i);
        }
    }
}
public class MyRunable2 implements Runnable{
    @Override
    public void run() {
        System.out.println("另外一个任务");
    }
}

2.2 多线程程序实现的方式3(掌握)

A:实现 Callable 接口。

相较于实现 Runnable 接口的方式,方法可以有返回值,并且可以抛出异常。

B:执行 Callable 方式,需要 FutureTask 实现类的支持,用于接收运算结果。 FutureTask 是 Future 接口的实现类
C:实现步骤

1.创建一个类实现Callable 接口
2.创建一个FutureTask类将Callable接口的子类对象作为参数传进去
3.创建Thread类,将FutureTask对象作为参数传进去
4.开启线程

public static void main(String[] args) throws ExecutionException, InterruptedException {
    //创建线程的第三种方式
    //我们有一种需求:当子线程执行完,我想获取子线程执行完之后的结果。

    MyCallable myCallable = new MyCallable(100);
    FutureTask<Integer> task = new FutureTask<>(myCallable);
    Thread th = new Thread(task);
    th.start();

    //获取线程执行完之后返回的结果
    Integer integer = task.get();
    System.out.println(integer);

    MyCallable myCallable2 = new MyCallable(1000);
    FutureTask<Integer> task2 = new FutureTask<>(myCallable2);
    Thread th2 = new Thread(task2);
    th2.start();

    Integer integer1 = task2.get();
    System.out.println(integer1);

    //Runnable 任务 让线程来执行 run() 没有返回值,这个方法不能抛出异常,只能抓
    //Callable 任务 让线程来执行 call() 有返回值,而且可以抛出异常。
}
public class MyCallable implements Callable<Integer> {
    private int num;
    public MyCallable(int num) {
        this.num = num;
    }

    //call()方法 让线程来执行的方法
    @Override
    public Integer call() throws Exception {
        //System.out.println("线程执行了此代码");
        //求1-num之间的和
        int sum=0;
        for (int i = 1; i <= num; i++) {
            sum+=i;
        }
        return sum;
    }
}

3.多线程之买电影票

3.1 继承Thread类的方式卖电影票案例(理解)

A:案例演示

需求:某电影院目前正在上映贺岁大片,共有100张票,而它有3个售票窗口售票,请设计一个程序模拟该电影院售票。
通过继承Thread类实现

分析:
a: 三个窗口其实就是3个线程
b: 定义票的数量100张
c: 创建线程对象,启动线程. 每卖一张这个票数应该–

public static void main(String[] args) {
 
 //三个串口,设计为三个线程 并发执行 切换速度很快感觉三个线程在同时买票
    CellThread th1= new CellThread("窗口1");
    CellThread th2 =new CellThread("窗口2");
    CellThread th3 =new CellThread("窗口3");
    th1.start();
    th2.start();
    th3.start();
}
public class CellThread extends Thread {
    //把票这个数据 设置为共享变量,让三个线程来共享
    static int piao = 100;
    public CellThread(String name) {
        super(name);
    }
    
    @Override
    public void run() {
        while (true){
            if(piao>=1){
                System.out.println(this.getName()+"正在出售第:"+(piao--)+" 张票");
            }
        }
    }
}

3.2 实现Runnable接口的方式卖电影票(理解)

A:案例演示

需求:某电影院目前正在上映贺岁大片,共有100张票,而它有3个售票窗口售票,请设计一个程序模拟该电影院售票。
通过实现Runnable接口实现

public static void main(String[] args) {
    //创建了一次任务 100
    CellRunnable cellRunnable = new CellRunnable();
    Thread th1 = new Thread(cellRunnable, "窗口1");
    Thread th2 = new Thread(cellRunnable, "窗口2");
    Thread th3= new Thread(cellRunnable, "窗口3");
    th1.start();
    th2.start();
    th3.start();
}
public class CellRunnable implements Runnable{
    //这个票让三个线程共享
    static int piao=100;
    @Override
    public void run() {
        while (true) {
            if (piao >= 1) {
                System.out.println(Thread.currentThread().getName() + "正在出售第:" + (piao--) + " 张票");
            }
        }
    }
}

3.3 买电影票出现了同票和负数票的原因分析(理解)

A:加入延迟

我们前面讲解过电影院售票程序,从表面上看不出什么问题,但是在真实生活中,
售票时网络是不能实时传输的,总是存在延迟的情况,所以,在出售一张票以后,需要一点时间的延迟
改实现接口方式的卖票程序,每次卖票延迟100毫秒

B:出现问题了问题
public static void main(String[] args) {
    //创建了一次任务 100
    CellRunnable cellRunnable = new CellRunnable();
    Thread th1 = new Thread(cellRunnable, "窗口1");
    Thread th2 = new Thread(cellRunnable, "窗口2");
    Thread th3= new Thread(cellRunnable, "窗口3");
    th1.start();
    th2.start();
    th3.start();

    //我们写的这段售票代码,出现了线程安全问题。多线程的环境下,在对共享数据进行操作时,有可能会出现线程安全问题。
    //为什么会出现线程安全问题,以及我们如何来解决他,且听下回分解。
}
public class CellRunnable implements Runnable{
    //这个票让三个线程共享
    static int piao=100;
    @Override
    public void run() {
        while (true) {
            if (piao >= 1) {
                //模拟一下真实的售票环境,有网络延迟。
                try {
                    Thread.sleep(50);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + "正在出售第:" + (piao--) + " 张票");
            }
        }
    }
}