• Java多线程学习


    线程离不开进程。如果进程消失了线程肯定消失,反之如果线程消失了进程不一定消失。


    • 目录


    多线程

    如果想要在Java中实现多线程有两种途径
    - 继承Thread类;
    - 实现Runnable接口(Callable接口);

    继承Thread类

    Thread类是一个支持多线程的功能类,只要有一个子类他就可以实现多线程的支持。

    class MyThread extends Thread { //这就是一个多线程的操作类
    
    }

    我们都知道,所有程序的起点是main()方法,但是所有的线程也有一个自己的起点,那么这个起点就是run()方法,也就是说在多线程的每个主体类中我们必须覆写Thread()类中的run()方法;

    public void run(){
    }

    这个方法没有返回值,也就是说明线程一旦开始就要一直执行下去不能够返回内容。
    所有的线程与进程都是一样的,都必须轮流去抢占资源,所以多线程的执行都应该是多个线程彼此交替执行,也就是说直接调用run方法并不能够启动线程,多线程的启动的唯一方法就是Thread类中的start()方法;

    public void start();
    //此方法调用的方法体是run方法定义的

    使用start()方法后,线程之间才可以交替执行。
    使用Thread类的start()方法不仅仅要启动多线程的执行代码,还要去根据不同的操作系统进行资源分配。

    实现Runnable接口

    虽然Thread类可以实现多线程主体类定义,但是它有一个问题,java具有单继承局限,所以我们应该尽量找的使用类得继承来实现多线程,为了解决单继承的闲置,在java中提供了Runnable接口,这个接口定义如下:

    @FunctionalInterface
    public interface Runnable{
        public void run();
    }

    所以只需要让一个类实现Runnable接口即可,并且也需要覆写run()方法;

    • 与继承Thread类相比,多线程的类在接口上与之前是没有区别的,但是有一点严重区别,如果此时继承了Thread,那么可以直接继承start()方法,但是如果实现的Runnable接口,并没有start()方法可以被继承。
    • 不管何种情况下,想要启动多线程,都需要依靠Thread类来完成,在Thread里面定义有
      构造方法(Thread(Runnable target));接收到额是Runnable接口对象
    class MyThread implements Runnable{
        private String name ;
        public MyThread (String Thread){
            this.name=name;
        }
        @Override
        public void run(){//覆写run方法
            for(int i=0;i<1000;i++){
                System.out.println(this.name+" --> "+i);
            }
        }
    }
    public class Text{
        public static void main (String args[]){//主类
            MyThread my1 = new MyThread();
            MyThread my2 = new MyThread();
            MyThread my3 = new MyThread();
            new Thread(my1).start();
            new Thread(my2).start();
            new Thread(my3).start();
        }
    }

    这样就避免了单继承的局限性,在实际工作中使用Runnable是最合适的

    多线程两种方法的区别?

    首先需要说明,Runnable接口和Thread类相比,解决了单继承的限制。
    Thread实现过程

    public class Thread extends Object implements Runnable
    //来自java开发文档

    这里我们可以看出来Thread实现了Runnable接口,而当我们使用Runnable接口来实现多线程的时候需要使用Thread中的方法
    这里写图片描述

    • 此时看起来像是代理设计模式,但是如果是代理设计模式,客户端调用的代理类的方法也应该是接口里提供的方法,那么也应该是run()方法才对。(设计者最早想到了代理的结构,但是他没有做到代理的样子,所以才提供了两种多线程的实现方式)

    • 出来以上的联系之外还有:使用Runnable接口可以比Thread类更好的描述出数据共享这一概念。此时的数据共享指的是多个线程访问统一资源的操作

    总结:
    1. Thread类是Runnable接口的子类,使用Runnable可以避免单继承的局限;
    2. Runnable实现的多线程可以比Thread类实现的多线程更加清晰得 描述数据共享的概念。

    callable接口

    使用Runnable接口实现的多线程可以避免单继承局限,但是Runnable接口中的run()没有返回值;

    callable在java.util.concurrent.Callable内

    @FunctionalInterface
    public interface Callable<V>{
        public V call()  throws Exception;
    }
    call()方法执行完线程的主题功能之后就会返回一个结果,而且返回结果的类型由Callable接口上的泛型来决定。
    

    范例:定义泛型主题类

    class My implements Callable<String>{
        private int tacket = 10;
    
        public String call() throws Exception {
            for(int i=0;i<100;i--){
                if(tacket>=0){
                    System.out.println("买票:ticket= "+this.tacket--);
                }
            }
            return "票买完了";
        }
    }

    java.util.concurrent Class FutureTask<V>//这个类主要是负责callable接口对象操作的,这个接口的定义接口为

    public class FutureTask<V> extends Object implements RunnableFuture<V>

    发现FutureTask实现了RunnableFuture接口,那么可以在查看RunnableFuture可以发现:

    public interface RunnableFuture<V> extends Ruuture<V>

    RunnableFuture又实现了Runnable接口!!!!

    在FutureTask类里面定义有如下的构造方法
    
    FutureTask(Callable<V> callable) 

    FutureTask接受的目的只有一个,那么就是去的call()方法的返回结果。

    
    import java.util.concurrent.Callable;
    import java.util.concurrent.ExecutionException;
    import java.util.concurrent.FutureTask;
    
    class My implements Callable<String>{
        private int tacket = 10;
    
        public String call() throws Exception {
            for(int i=0;i<100;i--){
                if(tacket>=0){
                    System.out.println("买票:ticket= "+this.tacket--);
                }
            }
            return "票买完了";
        }
    }
    public class Main {
        public static void main(String args[]) throws ExecutionException, InterruptedException {
            My my1 = new My();
            My my2 = new My();
            FutureTask<String> task1 = new FutureTask<String>(my1);
            FutureTask<String> task2 = new FutureTask<String>(my2);
            //FutureTask是Runnable接口子类,所以可以使用Thread类的构造来接受task对象
            new Thread(task1).start();
            new Thread(task2).start();
            //多线程结束后我们可以取得内容,根绝FutureTask的父接口Future中的get()方法完成
            System.out.println("A线程的返回结果为:"+task1.get());
            System.out.println("B线程的返回结果为:"+task2.get());
        }
    }
    

    第三种方法最麻烦在问题在于需要接受返回值信息,并且又要于原始的多线程的实现靠拢(想Thread靠拢)

    总结

    1. 对月多线程的实现,终点在于Runnable接口与Thread类启动的配合上;
    2. 对于JDK1.5新特性,了解就可以了,知道他们之间的区别在于返回结果;

    扩展

    线程的命名与取得

    因为每一个线程运行都是不同的结果,需要去抢占属于自己的资源,如果想要区分每一个线程,那么我们就需要为每一个线程进行命名。建议在线程启动之前就命名。
    如果先要对线程进行命名,需要是用到Thread类;
    - 构造方法:Thread(Runnable target, String name)
    - 设置名字:public final void setName(String name)
    - 取得名字: public final String getName()
    在为线程命名的时候由于这些方法是在Thread类里面的,所以我们在使用Runnable方式实现多线程的时候,想要获取线程名字,那么我们可以取得当前执行方法的线程名字。所以在Thread里面有一个这样的方法,
    - 取得当前线程对象:static Thread currentThread()

    
    import java.util.concurrent.ExecutionException;
    
    class MyThread implements Runnable{
        private int tacket = 10;
        public void run() {
            System.out.println(Thread.currentThread().getName());
        }
    }
    public class Main {
        public static void main(String args[]) throws ExecutionException, InterruptedException {
            MyThread mt1 = new MyThread();
            new Thread(mt1).start();
            new Thread(mt1).start();
            new Thread(mt1).start();
        }
    }

    运行结果为:

    Thread-0
    Thread-1
    Thread-2

    通过以上程序我们可以知道,在实例化Thread类对象的时候没有为其命名,那么会自动进行编号命名。

    • 设置名字的时候只需要在构造方法的参数后面加上线程的名字即可。
    new Thread(mt1,"线程1").start();
    • 同时我们观察一下程序会发现
    System.out.println(Thread.currentThread().getName());//不要放在线程里面
    //输出为main

    我们可以知道,其实main是一个主线程,而在main主方法上面创建的线程为main的子线程。
    - 通过以上程序我们可以发现,线程一直存在(主方法就是主线程),可是进程在哪儿呢?

    每当的使用java命令去解释一个程序类的时候,对于操作系统而言都相当与启动了一个新的进程,而main只是新新进程上的子线程。
    - 每一个jvm在启动的时候会启动几个线程呢?
    1. main线程,程序的主要执行以及启动子线程
    2. gc线程,垃圾收集。

    线程的休眠

    线程休眠即:让线程速度变慢一点,休眠方法为:

    public static void sleep(long millis) throws InterruptedException

    实例:

    
    import java.util.concurrent.ExecutionException;
    
    class MyThread implements Runnable{
        public void run() {
            for(int i=0;i<100;i++){
                try {
                    Thread.sleep(1000L);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName()+"x= "+i);
            }
    
        }
    }
    public class Main {
        public static void main(String args[]) throws ExecutionException, InterruptedException {
            MyThread mt1 = new MyThread();
            new Thread(mt1,"线程1").start();
        }
    }
    

    默认情况下在休眠的时候设置了多个线程对象,那么所有的线程对象将一起进入到run()方法,所以会出现资源抢占问题。

    线程优先级

    优先级高的线程有可能先执行

    在Thread里面有两个方法来设置优先级

    设置优先级:public final void setPriority(int newPriority)

    取得优先级:public final int getPriority()

    设置和取得的返回值是int类型,对这个内容有三种取值:

    • 最大优先级:public static final int MAX_PRIORITY(10)
    • 默认优先级:public static final int NORM_PRIORITY(5)
    • 最大优先级:public static final int MIN_PRIORITY(1)

    实例程序:

    import java.util.concurrent.ExecutionException;
    
    class MyThread implements Runnable{
        public void run() {
            for(int i=0;i<100;i++){
                try {
                    Thread.sleep(1000L);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName()+"x= "+i);
            }
    
        }
    }
    public class Main {
        public static void main(String args[]) throws ExecutionException, InterruptedException {
            MyThread mt1 = new MyThread();
            Thread th1 =new Thread(mt1,"线程1");
            Thread th2 =new Thread(mt1,"线程2");
            Thread th3 =new Thread(mt1,"线程3");
    
            th1.setPriority(Thread.MAX_PRIORITY);
    
            th1.start();
            th2.start();
            th3.start();
        }
    }
    

    通过这个代码我们可以大致了解到,线程优先级高的并不一定先执行。

    • 总结

      1. Thread.currentThread可以取得当前线程对象;
      2. Thread.sleep();主要是休眠,感觉是一起休眠,但实际上室友先后顺序的;
      3. 优先级越高的咸亨对象有可能限制性

    线程同步问题

    同步问题即:多个线程访问同一个资源是所需要考虑的问题。

    这里写图片描述
    在java中要实现同步需要使用synchronized关键字。这个关键字有两种使用方法:

    • 同步代码块
    synchronized(锁定对象){
        //需要同步执行的代码;
    }
    • 同步方法
    public synchronized 返回值 函数名(){
        //需要同步的代码;
    }

    同步操作和异步操作相比较:

    • 同步代码执行速度较慢,但是数据安全性高,是相对安全的线程操作;
    • 异步操作执行速度较快,但是数据有可能会因为竞争同一个资源而出现差错。

    死锁

    有时候为了解决数据安全,启用了线程同步,但是启用的线程同步越多,遇到的死锁可能性就越大,所以为了避免这种逻辑错误,需要进行调试和优化。

    生产者和消费者问题

    生成这和消费者指的是两个不同的线程对象,操作统一资源的情况,具体操作流程如下。

    • 生产者负责生产数据
    • 生产者没生产完一组数据之后,消费者就要取走一组数据。

    唤醒与等待

    • 等待public final void wait() throws InterruptedException
    • 唤醒public final void notify()
    • 唤醒正在等待的全部线程:public final void notifyAll()

    生产者和消费者问题代码实例:
    生产者生产一个,只有当消费者消费完之后才可以在生产一个。

    class Info {
        private String name;
        private String age;
        private boolean flag = true;
    
        public synchronized void set(String name, String age) {
    
            if (this.flag == false) {
                try {
                    super.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            this.name = name;
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            this.age = age;
    
            this.flag = false;
            super.notify();
        }
    
        public  synchronized void get(){
            if(this.flag == true){
                try {
                    super.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
    
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(this.name + " -- " + this.age);
    
            this.flag=true;
            super.notify();
        }
    }
    class Productor implements Runnable{
        private Info info ;
        public Productor(Info info){
            this.info=info;
        }
    
        public void run() {
            for(int i=0;i<100;i++){
                if(i%2==0) {
                    this.info.set("ssss", "23333");
                }else{
                    this.info.set("xx","18");
                }
            }
        }
    }
    class Consumer implements Runnable{
        private Info info ;
        public Consumer(Info info){
            this.info=info;
        }
        public void run() {
            for(int i=0;i<100;i++){
                this.info.get();
            }
        }
    }
    public class Main {
        public static void main(String args[]) throws  Exception{
            Info info =new Info();
            Productor pr = new Productor(info);
            Consumer co = new Consumer(info);
    
            new Thread(pr).start();
            new Thread(co).start();
    
        }
    }
    
  • 相关阅读:
    边框的作用之产生相对margin
    css3 实现切换显示隐藏效果
    Vue 获取数据、事件对象、todolist
    Vue 双向数据绑定、事件介绍以及ref获取dom节点
    Vue 目录结构分析 数据绑定 绑定属性 循环渲染数据 数据渲染
    Vue 安装环境创建项目
    进程&线程
    生成Excel
    JQuery input file 上传图片
    0908期 HTML 样式表属性
  • 原文地址:https://www.cnblogs.com/lanaiwanqi/p/10445665.html
Copyright © 2020-2023  润新知