关于java:关于Java多线程看这一篇就够了从创建线程到线程池分析的明明白白

9次阅读

共计 7057 个字符,预计需要花费 18 分钟才能阅读完成。

前言

过程是指一个内存中运行的应用程序,每个过程都有本人独立的一块内存空间,即过程空间或(虚空间)。过程不依赖于线程而独立存在,一个过程中能够启动多个线程。
线程是指过程中的一个执行流程,一个过程中能够运行多个线程。线程总是属于某个过程,线程没有本人的虚拟地址空间,与过程内的其余线程一起共享调配给该过程的所有资源,对立过程内的线程共享一个堆内存,每个线程具备本人的栈内存。“同时”执行是人的感觉,在线程之间实际上轮换执行。

同步与异步

同步:排队执行,效率低然而平安。
异步:同步执行,效率高然而数据不平安。

并发与并行

并发:指两个或多个事件在同一时间段内产生。
并行:指两个或多个事件在同一时刻产生(同时产生)。

线程创立的 3 种形式

1. 继承办法

public  class MyThread extends Thread{
    @Override
    public void run(){}
}
        MyThread m = new MyThread();
        m.start();

2. 接口办法

        // 实现 runnable
        //1 创立一个工作对象
        MyRunnable r = new MyRunnable();
        // 创立一个线程并给他一个工作
        Thread t = new Thread(r);
        // 启动线程
        t.start();

接口的劣势:

实现 Runnable 与继承 Thread 相比有如下劣势
1. 通过创立工作,而后给线程分配任务的形式实现多线程,更适宜多个线程同时执行工作的状况
2,能够防止单继承所带来的局限性
3,工作与线程是拆散的,进步了程序的健壮性
4,前期学习的线程池技术,承受 Runnable 类型的工作,不承受 Thread 类型的线程

    public static void main(String[] args) {new Thread(){
            @Override
            public void run() {for (int i = 0; i < 10; i++) {System.out.println("12345"+i);
                }
            }
        }.start();

然而继承 Thread 有个很简略的实现形式,通过匿名外部类重写 run()不必从新创立一个类而简略的实现了多线程,每个线程都有本人的栈空间,而共用一个堆内存。
由一个线程调用的办法,办法指挥执行在此线程中。

3.Callable 实现线程的状态的返回(实现了 Callalble 接口)

Callalble 接口反对返回执行后果,须要调用 FutureTask.get()失去,此办法会阻塞主过程的持续往下执
行,如果不调用不会阻塞。

    Callable<Integer> callable= new MyCallable();
    FutureTask<Integer> future = new FutureTask<>(callable); 
    new Thread(future).start();
    Integer j=task.get();
    System.out.println("return"+j);
  1. 编写类实现 Callable 接口 , 实现 call 办法
class Mycallable implements Callable<T> { 
    @Override 
    public <T> call() throws Exception {return T;} 
} 
  1. 创立 FutureTask 对象 , 并传入第一步编写的 Callable 类对象 FutureTask future = new FutureTask<>(callable);
  2. 通过 Thread, 启动线程 new Thread(future).start();

如果调用 get 办法,则主线程期待其执行实现之后再执行,如果不调用则对主线程无影响可并行。

FutureTask()办法

其父类 Future()办法

守护线程与用户线程

线程分为守护线程和用户线程
用户线程:当一个过程不蕴含任何的存活的用户线程时,进行完结
守护线程: 守护用户线程的,当最初一个用户线程完结时,所有守护线程主动死亡。
java 默认线程为用户线程,通过线程调用 setDaemon(true)设置守护线程。

        Thread t1 = new Thread(new MyRunnable());
        // 设置守护线程
        t1.setDaemon(true);
        t1.start();

线程同步的三种办法

1. 同步代码块,指定锁比方同一个 object

格局:synchronized(锁对象){}

public class Demo8 {public static void main(String[] args) {Object o = new Object();
        Runnable run = new Ticket();
        new Thread(run).start();
        new Thread(run).start();
        new Thread(run).start();}

    static class Ticket implements Runnable{
        // 总票数
        private int count = 10;
        private Object o = new Object();
        @Override
        public void run() {while (true) {synchronized (o) {if (count > 0) {
                         // 卖票
                            System.out.println("正在筹备卖票");
                            try {Thread.sleep(1000);
                            } catch (InterruptedException e) {e.printStackTrace();
                            }
                            count--;
                            System.out.println(Thread.currentThread().getName()+"卖票完结, 余票:" + count);
                        }else {break;}

                }
            }
        }
    }
}

2. 同步办法

run()外面调用同步办法,如果是一般办法应用办法外部的 this(此对象)作为锁,如果是静态方法是类名.class(字节码文件对象)

        public synchronized boolean test(){System.out.println("测试");
            }

3. 显示锁

同步代码块和同步办法都是隐式锁。
显式锁更加直观,体现面向对象,本人生成锁,本人加锁解锁。

    static class Test implements Runnable{
        // 总票数
        // 参数为 true 示意偏心锁    默认是 false 不是偏心锁
        private Lock l = new ReentrantLock(true);
        @Override
        public void run() {l.lock();
            System.out.println("测试");
            l.unlock();}
    }

偏心锁与不偏心锁

偏心锁:先来先到,一起排队
不偏心锁:大家一起抢
java 默认都是不偏心锁,能够通过显式锁中的构造方法实现偏心锁。

// 设置偏心锁
private Lock l= new ReentrantLock(true);

不传参数默认 false 不偏心,传入参数则偏心

死锁的防止

在任何有可能导致锁产生的办法里,不要调用另外一个有可能产生锁的办法让另外一个锁产生。
上面是死锁产生的例子:

public class Demo11 {public static void main(String[] args) {
        // 线程死锁
        Culprit c = new Culprit();
        Police p = new Police();
        new MyThread(c,p).start();
        c.say(p);
    }

    static class MyThread extends Thread{
        private Culprit c;
        private Police p;
        MyThread(Culprit c,Police p){
            this.c = c;
            this.p = p;
        }

        @Override
        public void run() {p.say(c);
        }
    }
    static class Culprit{public synchronized void say(Police p){// 两办法专用 this(此对象)锁,第一个办法不执行完无奈执行第二个办法
            System.out.println("罪犯:你放了我,我放了人质");
            p.fun();}
        public synchronized void fun(){System.out.println("罪犯被放了,罪犯也放了人质");
        }
    }
    static class Police{public synchronized void say(Culprit c){System.out.println("警察:你放了人质,我放了你");
            c.fun();}
        public synchronized void fun(){System.out.println("警察救了人质,然而罪犯跑了");
        }
    }
}

此时为死锁

若第一个线程执行(假如)p.fun()时,第二个线程 p 还没执行 p.say()办法,此时没锁到,两办法都能执行,但呈现谬误。

生产者与消费者

1. 一个类,能够批改能够读取,如果不应用线程同步,在批改时产生了另一线程的读取操作,会产生读取脏数据的状况。
2. 通过退出 synchronized 线程同步办法,批改时无奈读取能够解决读取脏数据的问题,然而无奈实现两线程之间的同步,会产生一个线程的抢占工夫片景象无奈同步。
3. 如果想要读取和批改能够相互期待同步,还须要退出 wait 和 notify 操作,一个执行完 notifyall 劳动,置判断标识,另一个被唤醒后读取判断标识执行而后劳动,在唤醒另一个。退出 flag 标记判断线程执行先后。

package com.java.demo;

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class Demo4  {

    /**
     * 多线程通信问题, 生产者与消费者问题
     * @param args
     */
    public static void main(String[] args) {Food f = new Food();
        new Cook(f).start();
        new Waiter(f).start();}

    // 厨师
    static class Cook extends Thread{
        private Food f;
        public Cook(Food f) {this.f = f;}

        @Override
        public void run() {for(int i=0;i<100;i++){if(i%2==0){f.setNameAndSaste("老干妈小米粥","香辣味");
                }else{f.setNameAndSaste("煎饼果子","甜辣味");
                }
            }
        }
    }
    // 服务生
    static class Waiter extends Thread{
        private Food f;
        public Waiter(Food f) {this.f = f;}
        @Override
        public void run() {for(int i=0;i<100;i++){
                try {Thread.sleep(100);
                } catch (InterruptedException e) {e.printStackTrace();
                }
                f.get();}
        }
    }
    // 食物
    static class Food{
        private String name;
        private String taste;

        //true 示意能够生产
        private boolean flag = true;

        public synchronized void setNameAndSaste(String name,String taste){if(flag) {
                this.name = name;
                try {Thread.sleep(100);
                } catch (InterruptedException e) {e.printStackTrace();
                }
                this.taste = taste;
                flag = false;
                this.notifyAll();
                try {this.wait();
                } catch (InterruptedException e) {e.printStackTrace();
                }
            }
        }
        public synchronized void get(){if(!flag) {System.out.println("服务员端走的菜的名称是:" + name + ", 滋味:" + taste);
                flag = true;
                this.notifyAll();
                try {this.wait();
                } catch (InterruptedException e) {e.printStackTrace();
                }
            }
        }
    }
}

线程的状态

线程状态。线程能够处于以下状态之一:
NEW
尚未启动的线程处于此状态。
RUNNABLE
在 Java 虚拟机中执行的线程处于此状态。
BLOCKED
被阻塞期待监视器锁定的线程处于此状态。
WAITING
无限期期待另一个线程执行特定操作的线程处于此状态。
TIMED_WAITING
正在期待另一个线程执行最多指定等待时间的操作的线程处于此状态。
TERMINATED
已退出的线程处于此状态。
线程在给定工夫点只能处于一种状态。这些状态是虚拟机状态,不反映任何操作系统线程状态。

线程池

1. 缓存线程池

无限度长度
工作退出后的执行流程:
1. 判断线程池是否存在闲暇线程
2. 存在则应用
3. 不存在则创立线程并应用


        ExecutorService service = Executors.newCachedThreadPool();
        // 指挥线程池执行新的工作
        service.execute(new Runnable() {
            @Override
            public void run() {System.out.println(Thread.currentThread().getName()+"测试");
            }

        });

2. 定长线程池

长度是指定的线程池
退出工作后的执行流程:
1 判断线程池是否存在闲暇线程
2 存在则应用
3 不存在闲暇线程 且线程池未满的状况下 则创立线程 并放入线程池中 而后应用
4 不存在闲暇线程 且线程池已满的状况下 则期待线程池的闲暇线程

        // 设置定长线程池
        ExecutorService service = Executors.newFixedThreadPool(2);
        service.execute(new Runnable() {
            @Override
            public void run() {System.out.println(Thread.currentThread().getName()+"测试");
                try {Thread.sleep(3000);
                } catch (InterruptedException e) {e.printStackTrace();
                }

            }
        });

3. 单线程线程池

成果与长度为 1 的定长线程池一样。
执行流程
1 判断线程池的那个线程是否闲暇
2 闲暇则应用
3 不闲暇则期待它闲暇后再应用

        ExecutorService service = Executors.newSingleThreadExecutor();
        service.execute(new Runnable() {
            @Override
            public void run() {System.out.println(Thread.currentThread().getName()+"测试");
            }
        });

4. 周期定长线程池

执行流程
    1 判断线程池是否存在闲暇线程
    2 存在则应用
    3 不存在闲暇线程  且线程池未满的状况下  则创立线程  并放入线程池中  而后应用
    4 不存在闲暇线程  且线程池已满的状况下  则期待线程池的闲暇线程

周期性工作执行时:
定时执行 当某个工作触发时 主动执行某工作

执行一次:

        ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(2);
        // 定时执行一次
        // 参数 1:定时执行的工作
        // 参数 2:时长数字
        // 参数 3:2 的工夫单位    Timeunit 的常量指定
           scheduledExecutorService.schedule(new Runnable() {
            @Override
            public void run() {System.out.println(Thread.currentThread().getName()+"测试");
            }
        },5, TimeUnit.SECONDS);      // 5 秒钟后执行 */

周期执行

        周期性执行工作
            参数 1:工作
            参数 2:提早时长数字(第一次在执行下面工夫当前)参数 3:周期时长数字(没隔多久执行一次)参数 4:时长数字的单位
        * **/
        scheduledExecutorService.scheduleAtFixedRate(new Runnable() {
            @Override
            public void run() {System.out.println(Thread.currentThread().getName()+"测试");
            }
        },5,1,TimeUnit.SECONDS);

Lambda 表达式

再应用匿名外部类作为参数时,能够应用 lambda 写法来极大的简化代码。

        // 冗余的 Runnable 编写形式
       Thread t = new Thread(new Runnable() {
            @Override
            public void run() {System.out.println("测试");
            }
        });
        t.start();

保留传递的参数,保留要重写的办法体,两头用 -> 连贯

        Thread t = new Thread(() -> System.out.println("测试"));

最初

感激你看到这里,看完有什么的不懂的能够在评论区问我,感觉文章对你有帮忙的话记得给我点个赞,每天都会分享 java 相干技术文章或行业资讯,欢送大家关注和转发文章!

正文完
 0