多线程

多线程

Process\Thread

  • 说起进程,就不得不说下程序。程序是指令和数据的有序集合,其本身没有任何运行的含义,是一个静态的概念。
  • 而进程则是执行程序的一次执行过程,它是一个动态的概念。是系统资源分配的单位。
  • 通常在一个进程中可以包含若干个线程,当然一个进程至少有一个线程,不然没有存在的意义。线程是CPU调度和执行的单位。
  • 注意:很多多线程是模拟出来的,真正的多线程是指有多个CPU,即多核,如服务器。如果是模拟出来的多线程,即在一个CPU的情况下,在同一个时间点,CPU只能执行一个代码,因为切换的很多,就有了同时执行的错觉。

核心概念

  • 线程就是独立的执行路径。
  • 在程序运行时,即使没有自己创建的线程,后台也会有多个线程,如主线程,gc线程。
  • main()称之为主线程,为系统的入口,用于执行整个程序。
  • 在一个进程中,如果开辟了多个线程,线程的运行由调度器(cpu)安排调度,调度器是与操作系统紧密相关的,先后顺序是不能人为干预的。
  • 对同一份资源操作时,会存在资源抢夺的问题,需要加入并发控制。
  • 线程会带来额外的开销,如cpu调度时间,并发控制开销。
  • 每个线程在自己的工作内存交互,内存控制不当会造成数据不一致。
//创建线程的方法一:继承Thread类,重写run方法,调度start方法开启线程;
//方法二:实现Runable接口,重写run方法,创建Thread类,引用Runable实现类,再调度start方法。
//注意:线程开启不一定立即执行,由cpu调度执行。
public class TestThread extends Thread {
    //run方法线程主体
    @Override
    public void run() {
        for (int i=0;i<20;i++){
            System.out.println("我在学习多线程-----"+i);
        }
    }
  //main线程,主线程
    public static void main(String[] args) {
        //创建一个线程对象
        TestThread thread = new TestThread();
        //调用start方法,开启子线程。
        thread.start();

        for (int i = 0; i < 200; i++) {
            System.out.println("我在学习----"+i);
        }
        //主线程和子线程交替执行
    }
}

使用多线程同步下载图片

//练习Thread,使用多线程同步下载图片
public class TestThread2 extends Thread {
    private String url;
    private String name;
    //有参构造
    public TestThread2(String url,String name){
        this.url=url;
        this.name=name;
    }

    @Override
    public void run() {
        fileDown file = new fileDown();
        file.downloader(url,name);
        System.out.println("下载了文件名为"+name);
    }

    public static void main(String[] args) {
        TestThread2 t1 = new TestThread2("http://cdn.xiaxiang.tech/image/blogs/threeJS/case/camera/%E7%94%B5%E5%BD%B1%E7%9B%B8%E6%9C%BA%E7%84%A6%E7%82%B91.png","真难看.jpg");
        TestThread2 t2 = new TestThread2("https://p0.ssl.qhimgs1.com/dmfd/296_196_/t01e84107a259d53f20.jpg", "一般.jpg");

        t1.start();
        t2.start();

    }
    
class fileDown{
    //下载图片的方法
      public void  downloader(String url,String name){
            try {
                FileUtils.copyURLToFile(new URL(url),new File(name));
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

Lamda表达式

  • lambda表达式只能有一行代码的情况下才能简化为一行,如果有多行,那么必须用代码块包裹。
  • 使用Lambda的前提时接口为函数式接口。
  • 多个参数也可以去掉参数类型,要么都去掉,要么都加上参数类型,必须加上括号。
为什么要使用Lambda表达式
  • 避免匿名内部类定义过多
  • 可以让你的代码看起来更简洁
  • 去掉了一个没有意义的代码,只留下核心的逻辑
函数式接口
  • 任何接口,如果只包含唯一一个抽象方法,那么它就是一个函数式接口。
  • 对于函数式接口,我们可以通过Lambda表达式创建该接口的对象。

线程状态

线程状态之间的关系

线程停止
  • 线程run()、main() 方法执行结束,或者因异常退出了run()方法,则该线程结束生命周期。死亡的线程不可再次复生。
public class TestStop implements Runnable {

    private boolean flag=true;

    public void run() {
        int i=0;
        while(flag){
                System.out.println("run在执行----"+i++);
        }
    }
   //利用flag为false,停止线程
    public void toFalse(){
        this.flag=false;
    }
    public static void main(String[] args) {
        TestStop stop = new TestStop();
        Thread thread = new Thread(stop);
        thread.start();
        for (int i = 0; i < 1000; i++) {
            System.out.println("main方法-----"+i);
            if (i==900){
                stop.toFalse();
                System.out.println("run方法停止执行");
            }
        }
    }
}
线程休眠 sleep()
  • sleep(时间)指定当前线程阻塞的毫秒数,存在InterruptedException异常。sleep时间达到后线程就绪状态。

  • 每一个对象都有一个锁,sleep不会释放锁。

  • 放大问题的发生性。

public class TestSleep implements  Runnable {
    public void run() {
        for (int i = 0; i < 10; i++) {
            System.out.println(i);
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
    public static void main(String[] args) throws InterruptedException {
        //线程延迟
      /*  TestSleep s = new TestSleep();
        Thread thread = new Thread(s);
        thread.start();*/
        //获取当前时间延迟
        Date date = new Date();
        SimpleDateFormat f = new SimpleDateFormat("yyyy-HH-mm hh:mm:ss");
        while (true){
            Thread.sleep(1000);
            System.out.println(f.format(date));
             date = new Date();
        }
    }
}
线程礼让 yield()
  • 礼让线程,让当前正在执行的线程暂停,但不阻塞。
  • 将线程从运行状态转为就绪状态。
  • 让cpu重新调度,礼让不一定成功!看cpu心情。
public class TestYield implements Runnable {
    public void run() {
        System.out.println(Thread.currentThread().getName()+"在执行线程");
        Thread.yield();
        System.out.println(Thread.currentThread().getName()+"结束线程了");
    }

    public static void main(String[] args) {
        TestYield y = new TestYield();
     new Thread(y,"小明").start();
     new Thread(y,"小红").start();

    }
}
线程强制执行 join()
  • join合并线程,待此线程执行完成后,再执行其他线程。在此线程执行过程中,其他线程阻塞。(可以想象成插队)
public class TestJoin implements  Runnable {
    public void run() {
        for (int i = 0; i < 10; i++) {
            System.out.println("我在插队"+i);
        }
    }

    public static void main(String[] args) throws InterruptedException {
        TestJoin join = new TestJoin();
        Thread thread = new Thread(join);
       thread.start();
        for (int i = 0; i < 100; i++) {
            System.out.println("main在执行"+i);
            if (i==59){
                thread.join();
            }
        }
    }
}
线程状态观测 getState()
  • 线程状态。线程可以处于以下状态之一:
    • NEW
      尚未启动的线程处于此状态。
    • RUNNABLE
      在Java虚拟机中执行的线程处于此状态。
    • BLOCKED
      被阻塞等待监视器锁定的线程处于此状态。
    • WAITING
      正在等待另一个线程执行特定动作的线程处于此状态。
    • TIMED_WAITING
      正在等待另一个线程执行动作达到指定等待时间的线程处于此状态。
    • TERMINATED
      已退出的线程处于此状态。
  public static void main(String[] args) throws InterruptedException {
        TestStatus testStatus = new TestStatus();
        Thread thread = new Thread(testStatus);
        //new线程对象时,线程的状态
        Thread.State state = thread.getState();
        System.out.println(state);
        //start时,线程状态
        thread.start();
        state=thread.getState();
        System.out.println(state);

        while(state!=Thread.State.TERMINATED){  //只要线程不结束,就一直输出线程状态
            Thread.sleep(1000);
            state =thread.getState();
            System.out.println(state);
        }

    }

    public void run() {
        for (int i = 0; i < 5; i++) {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println("循环完了----");
    }
线程优先级
  • Java提供一个线程调度器来监控程序中启动后进入就绪状态的所有线程,线程调度器按照优先级决定应该调度哪个线程来执行。(优先级低只是意味着获得调度的概率低,并不是优先级低就不会被调用了)
  • 线程默认优先级为5,优先级的范围为0~10.
  • 使用以下方式获取和改变优先级:getPriority(),setPriority()
   public void run() {
        System.out.println(Thread.currentThread().getName()+"优先级为"+Thread.currentThread().getPriority());
    }

    public static void main(String[] args) {
        System.out.println(Thread.currentThread().getName()+"优先级为"+Thread.currentThread().getPriority());

        TestPriority priority = new TestPriority();
        Thread one = new Thread(priority, "one");
        Thread two = new Thread(priority, "two");
        Thread three = new Thread(priority, "three");
        Thread four = new Thread(priority, "four");
        //注意:一般都是先设置优先级,再启动线程
        one.start();
        two.setPriority(1);
        two.start();
        three.setPriority(10);
        three.start();
        four.setPriority(5);
        four.start();
    }
守护线程
  • 线程分为用户线程和守护线程。
  • 虚拟机必须确保用户线程执行完毕。
  • 虚拟机不用等待守护线程执行完毕。
  • 如后台记录操作日志,监控内存、垃圾回收等。
  • 设置为守护线程的方法: setDaemon()(默认为false表示为用户线程,true为守护线程)

线程同步

多个线程操作同一资源(并发)

  • 现实生活中,我们会遇到“同一个资源,多人都想使用”的问题,比如,食堂排队打饭,每个人都想吃饭,最天然的解决方法就是排队,一个一个来。
  • 处理多线程问题时,多个线程访问同一个对象,并且某些线程还想修改这个对象,这个时候我们就需要线程同步。线程同步其实就是一种等待机制,多个需要同时访问此对象的线程进入这个对象的等待池形成队列,等待前面线程使用完毕,下一个线程再使用。
  • 由于同一进程的多个线程共享同一块存储空间,在带来方便的的同时,也带来了访问冲突问题,为了保证数据在方法中被访问的正确性,在访问时加入了锁机制synchronized,当一个线程获得对象的排它锁,独占资源时,其他线程必须等待,使用后释放锁即可。但同时也存在以下问题:
    • 一个线程持有锁会导致其他所有需要此锁的线程挂起。
    • 在多线程竞争下,加锁,释放锁会导致比较多的上下文切换和调度延迟,引起性能问题。
    • 如果一个优先级高的线程等待一个优先级低的线程释放锁 ,会导致优先级倒置,引起性能问题。
//线程不安全案例,会导致多人买到同一张票
public class BuyTicket {
    public static void main(String[] args) {
        MyTicket myTicket = new MyTicket();
      new Thread(myTicket,"你").start();
      new Thread(myTicket,"我").start();
      new Thread(myTicket,"他").start();
    }
    static class MyTicket implements Runnable {
        private int nums = 10;
        private Boolean flag = true;
        public void run() {
            while (flag) {
                if (nums<=0){
                    flag=false;
                }
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + "拿到了第" + nums-- + "票");
            }
        }
    }
}
//执行结果如下:
  /*  我拿到了第8票
      他拿到了第9票
      你拿到了第10票
      我拿到了第6票
      你拿到了第5票
      他拿到了第7票
      他拿到了第4票
      我拿到了第3票
      你拿到了第4票
      我拿到了第2票
      他拿到了第1票
      你拿到了第1票
我拿到了第0票*/
同步方法和同步块 synchronized
public class BuyTicket {

    public static void main(String[] args) {
        MyTicket myTicket = new MyTicket();
        new Thread(myTicket,"你").start();
      new Thread(myTicket,"我").start();
      new Thread(myTicket,"他").start();
    }
}
class MyTicket implements Runnable {
    private int nums = 10;
    private Boolean flag = true;
    public  void run() {
        while (flag) {
            buy();
        }
    }
    public synchronized void buy(){
        if (nums<=0){
            flag=false;
            return;
        }
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + "拿到了第" + nums-- + "票");
    }
}
//加入synchronized修饰方法后,执行结果为:
/*
你拿到了第10票
他拿到了第9票
我拿到了第8票
他拿到了第7票
他拿到了第6票
你拿到了第5票
你拿到了第4票
他拿到了第3票
我拿到了第2票
我拿到了第1票*/

同步块:synchronized(obj){}

  • obj称之为 同步监视器
    • obj可以时任何对象,但是推荐使用共享资源作为同步监视器
    • 同步方法中无需指定同步监视器,因为同步方法的同步监视器就是this,就是这个对象的本身,或者是class
public class UnsafeList{

   public static void main(String[] args) {
       listtwo listtwo = new listtwo();
       for (int i = 0; i < 1000; i++) {
           new Thread(listtwo).start();
       }
    }
}
class listtwo implements Runnable{
    List<String> list=new ArrayList<String>();
    public void run() {
        synchronized (list){
            list.add(Thread.currentThread().getName());
            System.out.println(list.size());
        }

    }
}
死锁

概念:多个线程各自占有一些共享资源,并且互相等待其他线程占有的资源才能运行,而导致两个或多个线程都在等待对方释放资源,都停止执行的情形。某一个同步块同时拥有“两个以上对象的锁”时,就可能会发生死锁的问题。

  • 产生死锁的四个必要条件:
    • 互斥条件:一个资源每次只能被一个进程使用。
    • 请求与保持条件:一个进程因请求资源而阻塞时,对已获得资源保持不放。
    • 不剥夺条件:进程已获得的资源,在未使用完之前,不能强行剥夺。
    • 循环等待条件:若干个进程之间形成一种头尾相接的循环等待资源关系。
//造成死锁的案例
public class deadLock {
    public static void main(String[] args) {
        hua hua = new hua(0, "小红");
        hua hua1 = new hua(1, "小花");
        new Thread(hua).start();
        new Thread(hua1).start();
    }
}
//口红
class kouHong{ }
//镜子
class jiZhi{}
//化妆
class hua implements Runnable {
    static kouHong kouHong = new kouHong();
    static jiZhi jiZhi = new jiZhi();

    private int choose;
    private String name;

    public hua(int choose, String name) {
        this.choose = choose;
        this.name = name;
    }

    public void run() {
        if (choose == 0) {
            synchronized (kouHong) {
                System.out.println(this.name + "在使用口红");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (jiZhi) {
                    System.out.println(this.name + "在使用镜子");
                }
            }
        } else {
            synchronized (jiZhi) {
                System.out.println(this.name + "在使用镜子");

                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (kouHong) {
                    System.out.println(this.name + "在使用口红");
                }
            }
        }
    }
}
Lock锁
  • Lock时显式锁(手动开启和关闭锁,lock()、unlock()方法)synchronized式隐式锁,出了作用域自动释放。
  • Lock只有代码块锁,synchrinized有代码块和方法锁。
  • 使用Lock锁,JVM将花费较少的时间来调度线程,性能更好。并且具有更好的扩展性(提供更多的子类)

线程协作、通信

应用场景:生产者和消费者问题

  • 假设仓库中只能放一件产品,生产者将生产出来的产品放入仓库,消费者将仓库中产品取走消费。
  • 如果仓库中没有产品,则生产者将产品放入仓库,否则停止生产并等待,直到仓库中的产品被消费着取走为止
  • 如果仓库中放有产品,则消费者可以将产品取走消费,否则停止消费并等待,指导仓库中再次放入产品为止。

分析

这是一个线程同步的问题,生产者和消费者共享同一个资源,并且生产者和消费者之间相互依赖,互为条件。

  • 对于生产者,没有生产产品之间,要通知消费者等待,而生产之后,又需要马上通知消费者消费。
  • 对于消费者,在消费之后,要通知生产者已经结束消费,需要生产新的产品,以供消费。
  • 在生产者消费者问题中,仅有synchronized是不够的
    • synchronized可阻止并发更新同一个共享资源,实现了同步。
    • synchronized不能用来实现不同线程之间的消息传递(通信)。
方法名 作用
wait() 表示线程一直等待,直到其他线程通知,与sleep不同,wait会释放锁
wait(long timeout) 指定等待的毫秒数
notify() 唤醒一个处于等待状态的线程
notifyAll() 唤醒同一个对象所有调用wait方法的线程,优先级别高的线程优先调度
//管程法实现线程通信
public class TestPC {
    public static void main(String[] args) {
        SynContainer synContainer = new SynContainer();
        Protucer protucer = new Protucer(synContainer);
        Consumer consumer = new Consumer(synContainer);
        new Thread(protucer).start();
        new Thread(consumer).start();
    }
}
//生产者
class Protucer implements Runnable{
    SynContainer synContainer;
    public Protucer(SynContainer synContainer){
        this.synContainer=synContainer;
    }
    public void run() {
        for (int i = 0; i < 100; i++) {
            synContainer.push(new Chicken(i));
            System.out.println("生产了"+i+"只鸡");
        }

    }
}
//消费者
class Consumer implements  Runnable{
    SynContainer synContainer;
    public Consumer(SynContainer synContainer){
        this.synContainer=synContainer;
    }
    public void run() {
        try {
            //开始等待一段时间,等待生产者产到10只鸡
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        for (int i = 0; i < 100; i++) {
            System.out.println("消费了第"+synContainer.pop().id+"鸡");
        }
    }
}

//鸡
class Chicken{
    int id;

    public Chicken(int id) {
        this.id = id;
    }
}

//缓冲区
class SynContainer{
    //需要一个容器大小
    Chicken[] chickens=new Chicken[10];
    //定义鸡的个数
    int count=0;
    public synchronized void push(Chicken chicken){
        //如果容器满了,就需要通知消费者来消费
        if (count==chickens.length){
            //通知消费者消费,生产等待
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        chickens[count]=chicken;
        count++;
        //可以通知消费者消费了
        this.notifyAll();
    }
    public synchronized Chicken pop(){

        if(count==0){
            //通知生产者生产,消费者等待
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        count--;
        Chicken chicken=chickens[count];
        this.notifyAll();
        return  chicken;

    }
}
//信号灯法
public class TestTV {
    public static void main(String[] args) {
        Tv tv = new Tv();
        player player = new player(tv);
        watcher watcher = new watcher(tv);
        new Thread(player).start();
        new Thread(watcher).start();
    }
}
//演员
class player implements Runnable{
    Tv tv;
    public player(Tv tv){
        this.tv=tv;
    }
    public void run() {
        for (int i = 0; i < 20; i++) {
            if(i%2==0){
               this.tv.play("快乐大本营");
            }else {
                this.tv.play("还珠格格");
            }
        }
    }
}
//观众
class watcher implements Runnable{
    Tv tv;
    public watcher(Tv tv){
        this.tv=tv;
    }
    public void run() {
        for (int i = 0; i < 20; i++) {
            tv.watch();
        }
    }
}
//电视台
class Tv{
    String voice;
    Boolean flag=true;
    //演员表演节目
    public synchronized void play(String voice){
        while(!flag){
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        this.voice=voice;
        System.out.println("演员表演了"+this.voice);
        this.notifyAll();
        this.flag=!this.flag;
    }
    //观看节目
    public synchronized void watch(){
        while (flag){
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println("观众观看了"+this.voice);
        this.notifyAll();
        this.flag=!this.flag;
    }
}

线程池

  • 背景:经常创建和销毁,使用量特别大,比如并发情况下的线程,对性能影响很大。
  • 思路:提前创建好多个线程,放入线程池中,使用时直接获取,使用完放回池中,可以避免频繁创建销毁、实现了重复利用。
  • 好处:
    • 提高响应速度(减少了创建新线程的时间)
    • 降低了资源消耗(重复利用线程池中的线程,不需要每次都创建)
    • 便于管理:
      • corePoolSize: 核心池的大小
      • maxiumPoolSize 最大线程数
      • keepAliveTime 线程没有任务时最多保持多长时间后会终止。
//线程池的使用
public class Execute {
    public static void main(String[] args) {
        ExecutorService service= Executors.newFixedThreadPool(10);//线程池的大小
        MyExecute myExecute = new MyExecute();
        service.execute(new Thread(myExecute));
        service.execute(new Thread(myExecute));
        service.execute(new Thread(myExecute));

        //线程池关闭
        service.shutdown();
    }
}
class MyExecute implements Runnable{
    public void run() {
        System.out.println(Thread.currentThread().getName());
    }
}
posted @ 2020-10-09 23:09  小胖学java  阅读(117)  评论(0编辑  收藏  举报