【阿里云Java高级编程】多线程编程

一、线程和进程

       线程是在进程基础之上划分的更小的程序单元,线程是在进程基础上创建并且使用的,所以线程依赖于进程的支持,但是线程的启动速度要比进程快许多,所以当使用多线程进行并发处理的时候,其执行的性能要高于进程

       进程是在操作系统上的划分,线程是在进程上的划分,线程的执行速度比进程更快,Java是多线程的编程语言,所以Java在进行并发访问处理的时候可以得到更高的处理性能。

二、Thread实现多线程

       Thread的run()方法不能直接被调用,因为牵扯到操作系统的资源调度问题,所以要想启动多线程必须使用start()启动线程,调用start()方法,最终执行的是run()方法,并且所有的线程对象都是交替执行的。Thread的start相当于是准备执行,run的执行是由操作系统决定的。

        疑问? 为什么多线程的启动不直接使用run()方法而必须使用Thread类中的start()方法呢?

查看start()源码

public synchronized void start() {
    /**
     * This method is not invoked for the main method thread or "system"
     * group threads created/set up by the VM. Any new functionality added
     * to this method in the future may have to also be added to the VM.
     *
     * A zero status value corresponds to state "NEW".
     */
    if (threadStatus != 0)//如果线程重复启动(两次start)则报错
        throw new IllegalThreadStateException();

    /* Notify the group that this thread is about to be started
     * so that it can be added to the group's list of threads
     * and the group's unstarted count can be decremented. */
    group.add(this);

    boolean started = false;
    try {
        start0();
        started = true;
    } finally {
        try {
            if (!started) {
                group.threadStartFailed(this);
            }
        } catch (Throwable ignore) {
            /* do nothing. If start0 threw a Throwable then
              it will be passed up the call stack */
        }
    }
}

       在Java程序执行的过程之中考虑到对于不同层次开发者 的需求,所以其支持有本地的操作系统函数调用,这项技术就称为JNI(Java Native Inteface)技术,但是Java开发过程之中并不推荐这样使用,利用这项技术可以使用一些操作系统提供的底层函数进行一些特殊的处理,而在Thread类里面提供的start0()就表示需要将此方法依赖于不同的操作系统实现

       每一个操作系统对于自己的资源调度都有自己不同的实现。

       Thread的执行分析:

在这里插入图片描述
任何情况下,只要定义了多线程,多线程的启动永远只有一种方案:Thread类中的start方法

三、Runnable实现多线程

       JDK1.8之后使用@FunctionalInterface将Runnable变为了一个函数式接口。

       Runnable没有start()方法,不使用Thread.start()方法是无法进行多线程的。所以需要用Runnable实现多线程需要把初始化好的Runnable的实现类对象当作参数通过Thread的一个带参构造函数,返回Thread,使用Thread的start()方法。

       以后的开发中对于多线程的实现,有限考虑的就是Runnable接口实现,并且永恒都是通过Thread对象启动线程。

四、Thread和Runnable的关系

       使用Runnable是最方便的,因为其可以避免单继承的局限,同时也可以更好的进行功能的扩充。

       从结构上要观察Thread和Runnable的联系

       Thread类是Runnable接口的实现类。
在这里插入图片描述

       多线程的设计之中,使用了代理设计模式的结构,用户自定义的线程主体只是负责项目核心功能的实现,而所有的辅助实现全部交给Thread来处理。

       在进行Thread启动多线程的时候调用的是start()方法。而后找到的是run()方法。当通过Thread类的构造方法传递了一个Runnable接口对象的时候,该接口对象将被Thread类中的target属性保存。在start方法执行的时候会调用thread里面的run方法,会调用保存的target所实现的run方法。

       多线程开发的本质实质上是在于多个线程可以进行统一资源的抢占,Thread主要描述的线程,资源的描述是通过Runnable的子类来描述的
在这里插入图片描述
利用买票程序来实现多个线程并发操作

class MyThread implements Runnable {
    private int ticket = 5;
    @Override
    public void run() {
        for (int i=0;i<100;i++){
            if (this.ticket>0){
                System.out.println("卖票,当前票数= "+ticket--);
            }
        }
    }
}
public class _3_ThreadTest extends Thread {
    public static void main(String[] args) {
        MyThread myThread = new MyThread();
        new Thread(myThread).start();
        new Thread(myThread).start();
        new Thread(myThread).start();

    }
}

通过内存分析图来分析本程序的执行
在这里插入图片描述

五、Callable实现多线程

       从最传统的开发来讲如果要进行多线程的实现肯定依靠的就是Runnable,但是Runnable接口有一个缺点,当线程执行完毕之后无法获取返回值。所以从JDK1.5之后就提出了一个新的线程实现接口java.util.concurrent.Callable接口

@FunctionalInterface
public interface Callable<V> {
    /**
     * Computes a result, or throws an exception if unable to do so.
     *
     * @return computed result
     * @throws Exception if unable to compute a result
     */
    V call() throws Exception;
}

       可以发现Callable定义的时候可以设置一个泛型,此泛型的类型就是返回数据的类型,可以避免向下转型出现的安全隐患。
在这里插入图片描述

使用Callable实现多线程处理

class MyCallable implements Callable<String> {
    @Override
    public String call() throws Exception {
        for (int i=0;i<10;i++){
                System.out.println("线程执行 i = "+i);
        }
        return "线程执行完毕";
    }
}

public class _6_CallableTest {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        FutureTask<String> task = new FutureTask<>(new MyCallable());
        new Thread(task).start();
        System.out.println("线程返回数据:"+task.get());
    }
}

面试题:请解释Runnable与Callable的区别?

  • Runnable是在JDK1.0的时候提出的 多线程实现接口,而Callable是在JDK1.5之后提出的;
  • Runnable接口之中只提供有一个run()方法,并且没有返回值;
  • java.util.concurrent.Callable接口提供有call()方法,可以有返回值;

六、线程的运行状态

       对于多线程的开发而言,编写程序的过程之中总是按照:定义线程的主体类,而后通过Thread类进行线程的启动,但是并不意味着调用了start()方法后线程就已经开始运行了,因为整体的线程处理有自己的一套运行状态。
在这里插入图片描述
1、任何一个线程的对象都应该使用Thread类来进行封装,所有线程的启动使用的是start(),但是启动的时候若干个线程将进入到一种就绪状态,现在并没有执行;

2、进入到就绪状态之后就需要等待进行资源调度,当某一个线程调度成功之后则进入到运行状态(run()方法),但是所有的线程不可能一直持续执行下去,中间需要产生一些暂停的状态,例如:某个线程执行一段时间之后就需要让出资源,而后这个资源就会进入到阻塞状态,随后重新回归到就绪状态;

3、当run()方法执行完毕之后,实际上该线程的主要任务也就结束了,那么此时就可以直接进入到停止状态。

(start是准备执行,而没有真正执行,执行要看操作系统的脸色,操作系统认为你可以执行你就可以执行,线程状态是不可估计的)

七、线程的命名和取得

       多线程的主要操作方法都在Thread类中定义了。

       多线程的运行状态是不确定的,那么在程序的开发之中为了可以获取到一些需要使用到的线程,就只能够依靠线程的名字来进行操作。

  • 构造方法
  • 设置名字
  • 取得名字
class MyRunnable implements Runnable {
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName());
    }
}
public class _8_ThreadTest {
    public static void main(String[] args) {
        MyRunnable myRunnable = new MyRunnable();
        new Thread(myRunnable,"线程一").start();
        new Thread(myRunnable).start();
        new Thread(myRunnable,"线程三").start();
    }
}
/*
输出:

线程一
Thread-0
线程三
*/

Thread自动编号匿名线程

/* For autonumbering anonymous threads. */
private static int threadInitNumber;
private static synchronized int nextThreadNum() {
    return threadInitNumber++;
}

直接调用run()方法

class MyRunnable implements Runnable {
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName());
    }
}
public class _8_ThreadTest {
    public static void main(String[] args) {
        MyRunnable myRunnable = new MyRunnable();
        new Thread(myRunnable,"线程一").start();
        new Thread(myRunnable).start();
        new Thread(myRunnable,"线程三").start();
        new Thread(myRunnable).run();
        new Thread(myRunnable,"线程五").run();
    }
}
/*
输出:

main
main
线程一
Thread-0
线程三
*/

​ 这里可以发现如果直接调用run方法, 都是通过主线程main来执行,所以得出一个结论,主方法也是一个线程,那么现在的问题来了,所有的线程都是在进程上的划分,那么进程在哪里
在这里插入图片描述

       每当使用java命令执行程序的时候就表示启动了一个JVM的进程,一台电脑上可以同时启动若干个JVM进程,所以每一个JVM进程都会有各自的线程。

       在任何的开发之中,主线程可以创建若干个子线程。创建子线程的目的是可以将一些复杂的逻辑或者比较耗时的逻辑交由子线程处理;

       范例:子线程不会影响主线程的执行,主线程负责处理整体流程,子线程负责处理耗时操作。

八、线程休眠

       如果希望某一个线程可以暂缓执行一次,就可以使用休眠的从护理,在Thread里面有一个sleep的方法参数为m

public static native void sleep(long millis) throws InterruptedException;//参数为秒
public static void sleep(long millis, int nanos);//参数为秒和纳秒

       休眠的主要特点是可以自动实现线程的唤醒,以继续进行后续的处理。但是需要注意的是,如果现在有多个线程对象,休眠也是有先后顺序的。

       从程序的执行的感觉上就像是很多个线程一起休眠一起唤醒。每一个对象都执行start方法,所有对象同时进入run方法一起执行,真正执行的时候也是有先有后的执行,彼此满了一点点,不规律。各自执行run操作的速度也各不相同。

九、线程中断

       在之前发现线程的休眠里面提供有一个中断异常,实际上就证明线程的休眠是可以被打断的,而这种打断肯定是由其它线程完成的。在Thread类里面提供有这种中断执行的处理方法

  • 判断线程是否被中断:public static boolean interrupted();
  • 终端线程执行:public void interrupt();

范例:观察线程的中断操作

public class _10_ThreadTest {
    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(() -> {
            System.out.println("*** 72个小时的疯狂我需要睡觉补充精力。");
            try {
                Thread.sleep(10000);//预计准备休眠十秒
                System.out.println("*** 睡足了,可以出去继续祸害别人了。");
            } catch (InterruptedException e) {
                System.out.println("敢打扰我睡觉,老子宰了你。");
            }
        });
        thread.start();//开始睡
        if (!thread.isInterrupted()) {//该线程中断了吗?
            Thread.sleep(1000);//等你1秒
            System.out.println("我偷偷的打扰一下你的睡眠。");
            thread.interrupt();//中断执行
        }
    }
}

       所有正在执行的线程都是可以被中断的,中断线程必须进行异常的处理。

十、线程强制运行

       所谓的线程的强制执行指的是当满足某些条件之后,某一个线程对象将可以一直独占资源,一直到该线程的程序执行结束。

范例:观察一个没有强制执行的程序

public class _11_ThreadDemo2 {
    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(() -> {
            for (int x = 0; x < 100; x++) {
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + " 执行、x = " + x);
            }
        }, "玩耍的线程");
        thread.start();
        for (int x = 0; x < 100; x++) {
            Thread.sleep(100);
            System.out.println("【霸道的main线程】number = " + x);
        }
    }
}

       主线程和子线程交替执行,但是如果希望主线程独占执行。那么就可以利用Thread的强制执行。

  • 强制执行方法:public final void join() throws InterruptedException;
public class _11_ThreadDemo2 {
    public static void main(String[] args) throws InterruptedException {
        Thread mainThread = Thread.currentThread();
        Thread thread = new Thread(() -> {
            for (int x = 0; x < 100; x++) {
                if (x==3){//现在霸道的线程来了
                    try {
                        mainThread.join();//霸道的线程要先执行
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + " 执行、x = " + x);
            }
        }, "玩耍的线程");
        thread.start();
        for (int x = 0; x < 100; x++) {
            Thread.sleep(100);
            System.out.println("【霸道的main线程】number = " + x);
        }
    }
}

       在进行线程强制执行的时候一定要获取强制执行线程对象之后才可以执行join()调用。

十一、线程礼让

       线程的礼让是先将资源让出去让别的线程先执行。线程的礼让可以使用Thread中提供的方法。

  • 礼让:public static void yield()。

范例:使用礼让操作

public class _12_ThreadDemo {
    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(() -> {
            for (int x = 0; x < 100; x++) {
                if (x%3==0){
                    Thread.yield();//线程礼让
                    System.out.println("###  玩耍的线程礼让执行  ###");
                }
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + " 执行、x = " + x);
            }
        }, "玩耍的线程");
        thread.start();
        for (int x = 0; x < 100; x++) {
            Thread.sleep(100);
            System.out.println("【霸道的main线程】number = " + x);
        }
    }
}

​ 礼让执行的时候每一次调用yield()方法都只会礼让一次当前的资源

十二、线程的优先级

       从理论上来讲,线程的优先级越高越有可能先执行(越有可能先抢占到资源)。在Thread类里面针对优先级的操作有两个处理方法。

  • 设置优先级:public final void setPriority(int new Priority);

  • 获取优先级:public final int getPriority();

       在进行优先级定义的时候都是通过int型的数字来完成的,而对于此数字的选择在Thread李就定义有三个常量

  • 最高优先级10:public static final int MAX_PRIORITY;

  • 中等优先级5:public static final int NORM_PRIORITY;

  • 最低优先级1:public static final int MIN_PRIORITY;

范例:观察优先级

public class _13_ThreadDemo {
    public static void main(String[] args) throws InterruptedException {
        Runnable runnable=()->{
            for (int i=0;i<10;i++){
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName()+"执行。");
            }
        };
        Thread threadA = new Thread(runnable,"线程对象A");
        Thread threadB = new Thread(runnable,"线程对象B");
        Thread threadC = new Thread(runnable,"线程对象C");

        threadA.setPriority(Thread.MIN_PRIORITY);
        threadB.setPriority(Thread.MIN_PRIORITY);
        threadC.setPriority(Thread.MAX_PRIORITY);

        threadA.start();
        threadB.start();
        threadC.start();
    }
}

       优先级高的有可能先执行,但并不是一定先执行。

       主线程的优先级为5。默认的线程的优先级也是5。中等优先级。

十三、同步问题引出

       在现实的处理之中,可以利用Runnable描述多个线程操作的资源,而Thread描述每一个线程对象,于是当多个线程访问同一资源的时候如果处理不当就会产生数据的错误操作。

       同步问题的引出:下面编写一个简单的卖票程序,将创建若干个线程对象实现卖票的处理操作。

范例:买票操作

class MyThread implements Runnable {
    private int ticket = 10;//总票数
    @Override
    public void run() {
        while (true) {
            if (this.ticket > 0) {
                System.out.println(Thread.currentThread().getName() + "卖票,ticket = " + this.ticket--);
            }else {
                System.out.println("******  票已抢光  ******");
                break;
            }
        }
    }
}

public class _14_ThreadDemo {
    public static void main(String[] args){
        MyThread myThread = new MyThread();
        //创建三个线程对象进行10张票的操作
        new Thread(myThread,"票贩子A").start();
        new Thread(myThread,"票贩子B").start();
        new Thread(myThread,"票贩子C").start();
    }
}

       此时的程序创建三个线程对象,并且这个三个线程对象将进行5张票的出售。此时的程序在进行卖票处理的时候并没有任何问题(假象)。下面可以模拟一下卖票中的延迟操作。

class MyThread implements Runnable {
    private int ticket = 10;//总票数
    @Override
    public void run() {
        while (true) {
            if (this.ticket > 0) {
                try {
                    Thread.sleep(100);//模拟网络延迟
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + "卖票,ticket = " + this.ticket--);
            }else {
                System.out.println("******  票已抢光  ******");
                break;
            }
        }
    }
}

public class _14_ThreadDemo {
    public static void main(String[] args){
        MyThread myThread = new MyThread();
        //创建三个线程对象进行10张票的操作
        new Thread(myThread,"票贩子A").start();
        new Thread(myThread,"票贩子B").start();
        new Thread(myThread,"票贩子C").start();
    }
}

       有了网络延迟之后会发现有很严重的并发问题:”第-1张票被卖出“。说明在ticket=1的时候,三个线程都进入了if(this.ticket>0){}里面。(三个线程都在ticket=1的时候过了判断这一关)

十四、线程同步处理

       解决同步问题的关键是锁,当某一个线程执行操作的时候,其它线程在外面等待。同步代码块的使用:

class MyThread implements Runnable {
    private int ticket = 10;//总票数
    @Override
    public void run() {
        while (true) {
            synchronized (this) {
                if (this.ticket > 0) {
                    try {
                        Thread.sleep(100);//模拟网络延迟
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                } else {
                    System.out.println("******  票已抢光  ******");
                    break;
                }
                System.out.println(Thread.currentThread().getName() + "卖票,ticket = " + this.ticket--);
            }
        }
    }
}

public class _14_ThreadDemo {
    public static void main(String[] args) {
        MyThread myThread = new MyThread();
        //创建三个线程对象进行10张票的操作
        new Thread(myThread, "票贩子A").start();
        new Thread(myThread, "票贩子B").start();
        new Thread(myThread, "票贩子C").start();
    }
}

       加入同步之后,程序的整体的性能下降了。同步实际上会造成性能的降低。

2、利用同步方法解决:只需要在方法定义上使用synchronized关键字即可。

class MyThread implements Runnable {
    private int ticket = 100;//总票数
    public synchronized boolean sale(){
        if (this.ticket > 0) {
            try {
                Thread.sleep(50);//模拟网络延迟
                System.out.println(Thread.currentThread().getName() + "卖票,ticket = " + this.ticket--);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return true;
        } else {
            System.out.println("******  票已抢光  ******");
            return false;
        }
    }
    @Override
    public void run() {
        while (true) {
            if (this.sale()){
                continue;
            }else {
                break;
            }

        }
    }
}

public class _14_ThreadDemo {
    public static void main(String[] args) {
        MyThread myThread = new MyThread();
        //创建三个线程对象进行10张票的操作
        new Thread(myThread, "票贩子A").start();
        new Thread(myThread, "票贩子B").start();
        new Thread(myThread, "票贩子C").start();
    }
}

       在日后学习Java类库的时候会发现,系统中许多的类上使用的同步处理采用的都是同步方法。同步会造成性能下降!!

十五、死锁

       死锁指的是若干个线程彼此相互等待的状态。

class _15_DeadLock implements Runnable {
    private Jian jj = new Jian();
    private Qiang qq = new Qiang();
    @Override
    public void run() {
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        jj.say(qq);
    }

    public _15_DeadLock(){
        new Thread(this).start();
        qq.say(jj);
    }

    public static void main(String[] args) {
        new _15_DeadLock();
    }
}

class Jian{
    public synchronized void  say(Qiang qq){
        System.out.println("阿健说:此路是我开,要想从此过,留下10块钱。");
        qq.get();
    }
    public synchronized void get(){
        System.out.println("阿健说:得到了10块钱,可以买饭吃了,于是让出了路。");
    }
}
class Qiang{
    public synchronized void say(Jian jj){
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("小强说:让我先跑,我再给你钱。");
        jj.get();
    }
    public synchronized void get(){
        System.out.println("小强说:逃过一劫,可以继续送快餐了。");
    }
}

       若干个线程访问同一资源时一定要进行同步处理,而过多的同步会造成死锁。模拟死锁需要用Thread.sleep()延时来保证这多个线程均已占用到初始分配的资源。

十六、生产者与消费者基本模型

       在多线程的开发过程之中最为著名的案例就是生产者与消费者操作,该操作的主要流程如下:

  • 生产者负责信息内容的生产;

  • 每当生产者生产完成一项完整的信息之后,消费者要从这里面取走信息;

  • 如果生产者没有生产完成则消费者要等待它生产完成,如果消费者还没有对信息进行消费,则生产者应该等待消费处理完成后再继续生产。

       程序的基本实现:

可以将生产者与消费者定义为两个独立的线程对象,但是对于现在生产的数据,可以使用如下的组成

  • 数据一:title = 小王、content = 宇宙大帅哥;
  • 数据二:title = 小高、content = 猥琐第一人;

       因为生产者和消费者是两个独立的线程,那么这两个独立的线程之间就需要有一个数据的保存的集中点,口语单独定义一个Message类实现数据的保存。

public class _17_ThreadDemo {
    public static void main(String[] args) {
        Message msg = new Message();
        new Thread(new Producer(msg)).start();
        new Thread(new Consumer(msg)).start();
    }
}

//生产者
class Producer implements Runnable {

    private Message msg;

    public Producer(Message msg) {
        this.msg = msg;
    }

    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            if (i % 2 == 0) {
                this.msg.setTitle("小王");
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                this.msg.setContent("宇宙大帅哥");
            } else {
                this.msg.setTitle("小高");
                try {
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                this.msg.setContent("猥琐第一人");

            }
        }
    }
}

//消费者
class Consumer implements Runnable {
    private Message msg;

    public Consumer(Message msg) {
        this.msg = msg;
    }

    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(this.msg.getTitle() + "  -  " + this.msg.getContent());
        }
    }
}
//产品
class Message {
    private String title;
    private String content;

    public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.title = title;
    }

    public String getContent() {
        return content;
    }

    public void setContent(String content) {
        this.content = content;
    }
}

       通过整个代码执行发现有两个问题

  • 问题一:数据不同步
  • 问题二:生产一个取走一个,但是发现重复生产和重复取出的问题

十七、解决生产者消费者同步问题

       如果要解决问题,首先解决的就是数据同步的处理问题,如果想要解决数据同步问题,最简单的方法是使用synchronized关键字定义同步代码块或同步放啊,这种同步处理九可以直接在Message类中;

public class _18_ThreadDemo {
    public static void main(String[] args) {
        Message2 msg = new Message2();
        new Thread(new Producer2(msg)).start();
        new Thread(new Consumer2(msg)).start();
    }
}

//生产者
class Producer2 implements Runnable {

    private Message2 msg;

    public Producer2(Message2 msg) {
        this.msg = msg;
    }

    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            if (i % 2 == 0) {
                try {
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                this.msg.set("小王","宇宙大帅哥");
            } else {
                try {
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                this.msg.set("小高","猥琐第一人,牛");

            }
        }
    }
}

//消费者
class Consumer2 implements Runnable {
    private Message2 msg;

    public Consumer2(Message2 msg) {
        this.msg = msg;
    }

    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(this.msg.get());
        }
    }
}

//产品
class Message2 {
    private String title;
    private String content;

    public synchronized void set(String title,String content){
        try {
            Thread.sleep(10);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        this.title=title;
        this.content=content;
    }
    public synchronized String get(){
        try {
            Thread.sleep(10);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return this.title + "  -  " + this.content;
    }
}

       在进行同步处理的时候肯定需要有一个同步处理的对象,那么此时肯定要将同步操作交由Message类处理是最合适的

18、利用Object类解决重复操作

       如果说现在要想解决生产者与消费者的问题,那么最好的解决方案就是使用等待与唤醒机制。而对于等待与环形的机制,主要依靠的是Object类中提供的方法处理的:

  • 等待机制:
    • 死等:public final void wait() throws InterruptedException;
    • 设置等待时间:public final void wait(long timeout) throws InterruptedException;
    • 设置等待时间:public final void wait(long timeout,int nanos) throws InterruptedException;
  • 唤醒线程:
    • 唤醒第一个等待线程,其它线程继续等待:public final void notify());
    • 唤醒全部等待线程,哪个线程的优先级高,就有可能先执行:public final void notifyAll();
public class _19_ThreadDemo {
    public static void main(String[] args) {
        Message3 msg = new Message3();
        new Thread(new Producer3(msg)).start();
        new Thread(new Consumer3(msg)).start();
    }
}

//生产者
class Producer3 implements Runnable {

    private Message3 msg;

    public Producer3(Message3 msg) {
        this.msg = msg;
    }

    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            if (i % 2 == 0) {
                try {
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                this.msg.set("小王","宇宙大帅哥");
            } else {
                try {
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                this.msg.set("小高","猥琐第一人,牛");

            }
        }
    }
}

//消费者
class Consumer3 implements Runnable {
    private Message3 msg;

    public Consumer3(Message3 msg) {
        this.msg = msg;
    }

    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(this.msg.get());
        }
    }
}

//产品
class Message3 {
    private String title;
    private String content;
    private boolean flag = true;//表示生产或消费的形式
    //flag=true:允许生产,不允许消费;
    //flag=false:允许消费,不允许生产;

    public synchronized void set(String title,String content){
        if (!flag){
            //无法进行生产,应该等待被消费
            try {
                super.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        try {
            Thread.sleep(10);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        this.title=title;
        this.content=content;
        System.out.println("\n生产:"+this.title+"  -  "+this.content);
        this.flag=false;//已经生产过了
        super.notify();//唤醒等待的线程
    }
    public synchronized String get(){
        if (this.flag){
            //还未生产需要等待
            try {
                super.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        try {
            Thread.sleep(10);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }finally {
            //不管如何都要执行
            this.flag = true;//继续生产
            super.notify();//唤醒等待线程
        }
        return this.title + "  -  " + this.content;
    }
}

这种处理形式就是在进行多线程开发过程之中最原始的处理方案,整个的等待、同步、唤醒机制都由开发者根据原生代码实现控制。

19、优雅的停止线程

       在多线程的操作之中启动多线程肯定使用的Thread中的start()方法,而如果队友多线程需要进行停止处理,Thread类原本提供有stop()方法,(基本上禁止使用了),这些方法从JDK1.2版本开始就已经将其废除了,现在也不建议出现在代码中,而除了stop()之外,还有几个方法也被禁用了。destroy()

  • 停止多线程:public void stop();

  • 销毁多线程:public void destroy();

  • 挂起线程(暂停执行):public void suspend();

  • 恢复挂起的线程执行:public void resume();

       废除这些方法方法的主要原因是因为这些方法可能导致线程的死锁,所以从JDK1.2开始都不建议使用了。如果这个时候想实现线程的停止需要通过一种柔和的方式进行处理

public class _20_ThreadDemo {
    public static boolean flag = true;
    public static void main(String[] args) throws InterruptedException {
        new Thread(()->{
            long num = 0;
           while (flag){
               try {
                   Thread.sleep(50);
               } catch (InterruptedException e) {
                   e.printStackTrace();
               }
               System.out.println(Thread.currentThread().getName()+"正在运行,num="+num++);
           }
        },"执行线程").start();
        Thread.sleep(200);//运行200毫秒
        flag=false;//停止线程
    }
}

       万一现在有其他的线程去控制flag的内容,那么这个时候对于线程的停止也不是说停就能停的,而是会在执行中判断flag 的内容来完成的。慢慢停下来,强制性停止就会死锁。

20、后台守护线程

       守护就是保护,假设一个人有一个保镖,这个保镖就是在这个人活着的时候保护。多线程中可以进行守护线程的定义,如果现在主线程的程序或者其它的线程还在执行的时候,那么守护线程将一直存在,并且运行在后台状态。

       在Thread类里面提供有如下的守护线程的操作方法:

  • 设置为守护线程:public final void setDaemon(boolean on);
  • 判断是否为守护线程:public final boolean isDaemon();
public class _21_ThreadDemo {
    public static boolean flag = true;
    public static void main(String[] args) throws InterruptedException {
        Thread userThread = new Thread(()->{
            for (int i = 0;i<10;i++){
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName()+"正在运行,i = "+i);
            }
        },"用户线程");//完成核心的业务

        Thread daemonThread = new Thread(()->{
            for (int i = 0;i<Integer.MAX_VALUE;i++){
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName()+"正在运行,i = "+i);
            }
        },"守护线程");//完成核心的业务

        
        daemonThread.setDaemon(true);//设置为守护线程
        userThread.start();
        daemonThread.start();
    }
}

       守护线程都是围绕在用户线程的周围,如果程序执行完毕了,守护线程就消失了,JVM里面最大的守护线程就是GC线程。

       程序执行中GC线程会一直存在,如果程序执行完毕,GC线程也将消失

21、volatile关键字

       在多线程中,volatile关键字主要是在属性定义上使用的,表示此属性为直接数据操作,而不进行副本的拷贝处理,这样的话在一些书上就将其错误的理解为同步属性了。

       在正常进行变量处理的时候往往会经历如下几个步骤:

  • 获取变量原有的数据内容副本;
  • 利用副本为变量进行数学计算;
  • 将计算后的变量,保存到原始空间总中;

       如果一个属性上追加了volatile关键字,表示的就是不使用副本,而是直接操作原始变量,相当于节约了:拷贝副本+重新放回保存的步骤。
在这里插入图片描述

       面试题:情节是volatile与synchronized的区别?

  • volatile主要在属性上使用,而synchronized是在代码块与方法上使用的;
  • volatile无法描述同步的处理,它只是一种直接内存的处理,避免了副本的操作,而synchronized是实现同步的。
  • volatile可以让我们更快速的进行变量的处理

22、多线程综合案例

22.1、数字加减

       设计4个线程对象,两个线程执行减操作,两个线程执行加操作

       这一个程序的核心本质在于:加一个减一个

public class _23_ThreadDemo {
    public static void main(String[] args) throws InterruptedException {
        Resource resource = new Resource();
        AddThread at = new AddThread(resource);
        SubThread st = new SubThread(resource);
        new Thread(at, "加法线程1").start();
        new Thread(st, "减法线程1").start();
        new Thread(at, "加法线程2").start();
        new Thread(st, "减法线程2").start();
        Thread.sleep(20000);
        System.out.println(resource.count);
    }
}

class AddThread implements Runnable {
    private Resource resource;

    public AddThread(Resource resource) {
        this.resource = resource;
    }

    @Override
    public void run() {
        for (int i = 0; i < 50; i++) {
            try {
                this.resource.add();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

class SubThread implements Runnable {
    private Resource resource;

    public SubThread(Resource resource) {
        this.resource = resource;
    }

    @Override
    public void run() {
        for (int i = 0; i < 50; i++) {
            try {
                this.resource.sub();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}


//定义一个操作的资源
class Resource {
    private int num = 0;//进行加减操作的数据
    public int count = 0;
    private boolean flag = true; //加减的切换
    //flag = true:表示可以进行加法操作,但是无法进行减法操作;
    //flag = false:表示可以进行减法操作;但是无法进行加法操作。

    public synchronized void add() throws InterruptedException {
        if (!this.flag) {
            //现在需要执行的是加法操作,减法操作要等待
            this.wait();
        }
        Thread.sleep(100);
        this.num++;
        System.out.println("【加法操作 + " + Thread.currentThread().getName() + "】" + "num = " + this.num);
        this.flag = false;//加法操作执行完毕,需要执行减法处理
        this.count++;
        super.notifyAll();//唤醒全部等待线程
    }

    public synchronized void sub() throws InterruptedException {
        if (this.flag) {
            //现在需要执行的是减法操作,加法操作要等待
            this.wait();
        }
        Thread.sleep(100);
        this.num--;
        System.out.println("【减法操作 - " + Thread.currentThread().getName() + "】" + "num = " + this.num);
        this.flag = true;
        this.count++;
        super.notifyAll();
    }
}
22.2、生产电脑

       设计一个生产电脑和搬运电脑类,要求生产出一台电脑就搬走一台电脑,如果没有新的电脑生产出来,则搬运工要等待新电脑生产出;如果生产出的电脑没有搬走,则要等待电脑搬走之后再生产,并统计出生产的电脑数量。

       类似于22.1

22.3、竞争抢答

       实现一个竞拍抢答程序:要求设置三个抢答者(三个线程),而后同时发出抢答指令,抢答成功者给出成功提示,未抢答成功者给出失败提示。

       对于这一个多线程的操作,由于里面需要牵扯到数据的返回问题,那么现在最好使用Callable是比较方便的一种处理形式。

class MyThread implements Callable<String>{
    private boolean flag = false;// 抢答处理
    @Override
    public String call() throws Exception {
        synchronized (this){//数据同步
            if (this.flag==false){//抢答成功
                this.flag=true;
                return Thread.currentThread().getName()+"抢答成功!";
            }else {//抢答成功
                return Thread.currentThread().getName()+"抢答失败!";
            }
        }
    }
}

public class _25_ThreadDemo {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        MyThread mt = new MyThread();
        FutureTask<String> taskA = new FutureTask<>(mt);
        FutureTask<String> taskB = new FutureTask<>(mt);
        FutureTask<String> taskC = new FutureTask<>(mt);
        new Thread(taskB,"竞赛者B").start();
        new Thread(taskA,"竞赛者A").start();
        new Thread(taskC,"竞赛者C").start();
        System.out.println(taskA.get());
        System.out.println(taskB.get());
        System.out.println(taskC.get());
    }
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值