多线程拓展

守护线程

守护线程又叫做兜底线程,每当程序运行,都会默认开启一个守护线程,用于监听我们正常的程序。

守护线程就是在当前线程执行完毕,守护线程就会跟着结束执行,注意完成垃圾回收等功能。

使用setDaemon()方法把某个线程设置为守护线程
但是必须在启动线程之前设置守护线程,否则会报错

如:

public class Thread_01_Daemon {
    public static void main(String[] args) {
        Thread thread1 = new Processor_01();
        thread1.setName("Thread->1->");
        //设置守护线程
        thread1.setDaemon(true);
        thread1.start();
        for (int i = 0; i < 10; i++) {
            System.out.println("main-->" + i);
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

class Processor_01 extends Thread {
    @Override
    public void run() {
        int i = 0;
        while (true) {
            System.out.println(Thread.currentThread().getName() + " = " + i++);
            try {
                sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

在main线程执行完毕后,thread1线程会立即结束运行。

Timer定时器

在JDK类库中Timer类主要负责计划任务的功能,也就是在指定的时间开始执行某一个任务。此类也常用来做一下周期性同步工作。

使用schedule()方法设置定时器
方法参数为:

  1. 需要执行的任务类
  2. 执行任务的起始时间
  3. 执行任务的时间间隔

如:

public class Thread_02_Timer {
    public static void main(String[] args) {
        //创建定时器
        Timer timer = new Timer();
        //执行任务对象
        LogTimerTask logTimerTask = new LogTimerTask();
        //指定起始时间
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss SSS");
        //任务  起始时间   间隔时间(毫秒)
        try {
            timer.schedule(logTimerTask, sdf.parse("2021-01-30 21:24:58 150"), 1000 * 3);
        } catch (ParseException e) {
            e.printStackTrace();
        }
        //System.out.println(sdf.format(new Date()));
    }
}

class LogTimerTask extends TimerTask {
    @Override
    public void run() {
        //格式化时间对象
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss SSS");
        //获取当前系统时间,为毫秒数
        Date date = new Date();
        //格式化输出时间
        System.out.println(sdf.format(date));
    }
}

死锁

锁的相关知识

如果访问了一个对象中加锁的成员方法,那么该对象中所有的加锁的成员方法全部锁定,都不能被其他线程访问。和静态方法无关

如果访问了一个类加锁的静态方法,那么该类中所有的加锁的静态方法都被锁定,不能访问。和成员方法无关。

如测试代码:

public class Thread_03_Synchronized {
    public static void main(String[] args) {
        MyClass_01 myClass_01 = new MyClass_01();
        Thread thread1 = new Thread(new Processor_03(myClass_01));
        Thread thread2 = new Thread(new Processor_03(myClass_01));
        Thread thread3 = new Thread(new Processor_03(myClass_01));
        thread1.setName("t1");
        thread2.setName("t2");
        thread3.setName("t3");
        thread1.start();
        //睡一下会儿,保证thread1先执行,先进入加锁m1方法,并睡眠5秒
        try {
            Thread.sleep(500);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        thread2.start();
        thread3.start();
    }
}

class Processor_03 implements Runnable{
    MyClass_01 myClass_01;

    public Processor_03(MyClass_01 myClass_01) {
        this.myClass_01 = myClass_01;
    }

    @Override
    public void run() {
        if ("t1".equals(Thread.currentThread().getName())){
            myClass_01.m1();
        }else if ("t2".equals(Thread.currentThread().getName())){
            myClass_01.m2();
        }else if ("t3".equals(Thread.currentThread().getName())){
            myClass_01.m3();
        }
    }
}

//业务类
class MyClass_01 {
    public synchronized void m1() {
        System.out.println(Thread.currentThread().getName()+"****"+"加锁的成员方法m1,即将进入睡眠");
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public synchronized void m2() {
        System.out.println(Thread.currentThread().getName()+"****"+"加锁的成员方法m2");
    }

    public void m3() {
        System.out.println(Thread.currentThread().getName()+"****"+"未加锁的成员方法m3");
    }
}

代码块锁

语法格式:
synchronized(xxx){} 代码块锁,可以锁类,也可以锁对象。

如果锁对象,括号中应填写对象名,当防卫该代码块锁的时候,该对象中所有的代码块锁和加锁的成员方法都被锁定。同理访问对象中加锁的成员方法的时候,代码快锁也会被锁定。

如果锁类,当访问该代码块的时候,该类中所有的代码块锁和加锁的静态方法都被锁定,同理,访问类中加锁的静态方法的时候,代码块锁也会被锁定。

死锁

两个线程分别占用对方需要的同步资源,等在等待对象释放资源。出现死锁后,不会出现异常,不会出现提示,所有的线程都处于阻塞状态,程序无法继续执行。

原理:

  1. 某个线程的执行完成,需要先后,嵌套,锁定执行两个对象,并且先执行对象1
  2. 另一个线程的执行完成,需要先后,嵌套,锁定执行两个对象,并且先执行对象2
  3. 在第一个线程中,要去访问对象2的时候,发现被锁定了,只能等待
  4. 在第二个线程中,要去访问对象1的时候,发现被锁定,只能等待

实现死锁的思路:

  1. 两个线程
  2. 两个对象
  3. 两个线程中保存的对象时相同的
  4. 线程1先访问对象1再嵌套访问对象2
  5. 线程2先访问对象2再嵌套访问对象1

如:

public class Thread_04_DeadLock 
    public static void main(String[] args) {
        Object o1 = new Object();
        Object o2 = new Object();
        Thread t1 = new T1(o1, o2);
        Thread t2 = new T2(o1, o2);
        t1.start();
        t2.start();
    }
}

class T1 extends Thread {
    Object o1;
    Object o2;

    public T1(Object o1, Object o2) {
        this.o1 = o1;
        this.o2 = o2;
    }

    @Override
    public void run() {
        synchronized (o1) {
            System.out.println("*************");
            //加入睡眠,保证一定会死锁,因为到这里先确保让t2把o2锁定
            try {
                sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            synchronized (o2) {
                System.out.println("T1执行完成");
            }
        }
    }
}

class T2 extends Thread {
    Object o1;
    Object o2;

    public T2(Object o1, Object o2) {
        this.o1 = o1;
        this.o2 = o2;
    }

    @Override
    public void run() {
        synchronized (o2) {
            System.out.println("-------------------");
            //加入睡眠,保证一定会死锁,因为到这里先确保让t1把o1锁定
            try {
                sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            synchronized (o1) {
                System.out.println("T2执行完成");
            }
        }
    }
}

线程通信

使用Object中方法实现

  1. wait():该线程进入等待状态,功能还在,只是没有运行,当被唤醒之后进入就绪状态,当执行的时候接着当前等待的状态继续执行。
    必须用在成员方法中,因为是对象相关,并且该成员方法必须加上synchronized。
    无参或者传入0,说明不会自动唤醒,只能被notifyAll()/notify唤醒。
    也可以传入long类型的值,单位是毫秒,过了指定时间之后自动醒。
  2. notify():唤醒该对象上等待中的某一个线程(优先级)
  3. notifyAll():唤醒该对象上所有等待的线程

实现生产者和消费者问题:
实现思路:

  1. 有一个业务类,SunStack 其中有一个成员变量cnt用来保存生产的个数,还需要一个容器来存储生产的数据char[]。
  2. 业务类需要有对应的生产方法和消费方法
    ① 生产push:想数组中添加元素,需要判断数组是否满了,如果满了就不生产,唤醒消费者
    ② 消费pop:同上,判断使用没有数据,如果没有就唤醒生产者
    ③ 两个线程分别调用生产和消费

如实现:

public class Thread_06_ProducerConsumer {
    public static void main(String[] args) {
        SynStack synStack = new SynStack();
        Thread thread1 = new Thread(new Consumer(synStack));
        Thread thread2 = new Thread(new Producer(synStack));
        thread1.start();
        thread2.start();
    }
}

//消费者
class Consumer implements Runnable {
    SynStack synStack;

    public Consumer(SynStack synStack) {
        this.synStack = synStack;
    }

    @Override
    public void run() {
        for (int i = 0; i < 20; i++) {
            synStack.pop();
            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

//生产者
class Producer implements Runnable {
    SynStack synStack;

    public Producer(SynStack synStack) {
        this.synStack = synStack;
    }

    @Override
    public void run() {
        for (int i = 0; i < 20; i++) {
            synStack.push((char) ('a' + i));
        }
    }
}

class SynStack {
    //保存生产的个数,同时也是下一个元素的下标
    int cnt = 0;
    char[] data = new char[6];

    public synchronized void push(char c) {
        //生产个数如果和容器大小一致,说明生产满了,就不再生产
        if (cnt == data.length) {
            try {
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

        //唤醒消费者
        notifyAll();
        //把元素保存到数组中
        data[cnt] = c;
        //容器中个数+1
        cnt++;
        System.out.println("生产了 : " + c + " , 容器中剩余 " + cnt + " 个字符");
    }

    public synchronized char pop() {
        //容器中个数为0,就不进行消费
        if (cnt == 0) {
            try {
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        //唤醒生产者
        notifyAll();
        //cnt先进行--操作,因为cnt保存的是下一个元素的小标
        cnt--;
        char c = data[cnt];
        System.out.println("消费了 : " + c + " , 容器中剩余 " + cnt + " 个字符");
        return c;
    }
}

线程单例模式的使用

原因:
线程启动后,如果同时调用单例模式创建对象的方法,将会都在if中判断为null,多个线程会创建不同的单例模式的对象,为了避免这个问题,将getInstance()方法同步。

如果在方法声明上加入synchronized可以实现,但是一旦跳过第一次,后续不管多少个并发/并行singLethon_01都不会等于null,就不会再创建。而我们使用synchronized,效率会降低太多,除了第一次,以后使用这个方法,都需要排队等待。降低了程序的运行效率。
所以需要在方法中使用同步代码块来实现。并且在同步代码块中需要再次判断对象是否为null,否则第一次执行时,虽然都在排队,当第一个线程创建完对象,第二个线程会进入同步代码块继续创建一个对象。

如:

ublic class SingLethon_01 {
    //私有化构造方法
    private SingLethon_01() {

    }

    //创建私有化静态变量,类名为当前类类名
    private static SingLethon_01 singLethon_01 = null;

    //加锁可以实现
    //但是一旦跳过第一次,后续不管多少个并发/并行singLethon_01都不会等于null,就不会再创建
    //而我们使用synchronized,效率会降低太多,除了第一次,以后使用这个方法,都需要排队等待
    //而程序中,只需要第一次排队等待即可
    /*public synchronized static SingLethon_01 getInstance() {
        if (singLethon_01 == null) {
            singLethon_01 = new SingLethon_01();
        }

        return singLethon_01;
    }*/

    public static SingLethon_01 getInstance() {
        if (singLethon_01 == null) {
            //添加第二层判断是因为如果有线程都执行到了这一行,在排队等待
            //第一个线程进入创建完对象后
            //第二个线程进入,没有if判断会再次创建对象
            synchronized (SingLethon_01.class) {
                if (singLethon_01 == null) {
                    singLethon_01 = new SingLethon_01();
                }
            }
        }
        return singLethon_01;
    }
}

这样创建就可以保证创建出来的都是同一个对象

线程池的使用

概述

背景:经常创建和销毁、使用量特别大的资源,比如并发情况下的线程, 对性能影响很大。

思路:提前创建好多个线程,放入线程池中,使用时直接获取,使用完 放回池中。可以避免频繁创建销毁、实现重复利用。类似生活中的公共交 通工具。

使用线程池的好处:

  1. 提高响应速度(减少了创建新线程的时间)
  2. 降低资源消耗(重复利用线程池中线程,不需要每次都创建)
  3. 便于线程管理.

JDK 5.0起提供了线程池相关API:ExecutorService 和 Executors。

  1. ExecutorService:真正的线程池接口。常见子类ThreadPoolExecutor。
  2. Executors:工具类、线程池的工厂类,用于创建并返回不同类型的线程池
  • Executors.newCachedThreadPool():创建一个可根据需要创建新线程的线程池
  • Executors.newFixedThreadPool(n); 创建一个可重用固定线程数的线程池
  • Executors.newSingleThreadExecutor() :创建一个只有一个线程的线程池
  • Executors.newScheduledThreadPool(n):创建一个线程池,它可安排在给定延迟后运行命令或者定期地执行。

创建一个可根据需要创建新线程的线程newCachedThreadPool

创建一个可缓存的线程池,如果线程池长度超过处理需要,可以灵活回收空闲线程。
若没有可以回收的,则新建线程,线程池规模不存在限制,数量不固定

如:

public class _01_NewCachedThreadPoolTest {
    public static void main(String[] args) {
        //创建线程池
        ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
        for (int i = 0;i<1000;i++){
            //执行任务,传入Runnable的匿名内部类,也可以是实现类对象
            cachedThreadPool.execute(new Runnable() {
                @Override
                public void run() {
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName());
                }
            });
        }
        System.out.println("======================");
        cachedThreadPool.shutdown();
    }
}

创建一个固定长度线程池newFixedThreadPool

可控制线程最大并发数,超出此数量的线程,会在队列中等待。

如:

public class _02_NewFixedThreadPoolTest {
    /*
     * 创建一个固定长度线程池,可控制线程最大并发数
     * 超出此数量的线程,会在队列中等待
     *
     * */
    public static void main(String[] args) {
        ExecutorService fixedThreadPool = Executors.newFixedThreadPool(3);

        for (int i = 0; i < 100; i++) {
            fixedThreadPool.execute(new Runnable() {
                @Override
                public void run() {
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName());
                }
            });
        }
        System.out.println("=============");
        fixedThreadPool.shutdown();
    }
}

创建定时及周期性执行任务newScheduledThreadPool

有两种实现方法:

  1. 延迟执行
  2. 延迟并间隔执行

需传入执行的任务,延迟时间,延迟时间单位
如:

public class _03_NewScheduledThreadPool {
    public static void main(String[] args) {
        // 创建池子,并指定数量5
        ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(5);
        //执行
        //执行的任务,延迟时间,延迟时间的单位
        scheduledThreadPool.schedule(new Runnable() {
            @Override
            public void run() {
                System.out.println("延迟3秒再执行");
            }
        }, 3, TimeUnit.SECONDS);

        //关闭池子
        scheduledThreadPool.shutdown();
    }
}

需传入执行的任务,延迟时间,时间间隔,延迟时间单位。
注意:不可以关闭池子,否则无法运行。
如:

public class _03_NewScheduledThreadPool {
    public static void main(String[] args) {
        // 创建池子,并指定数量5
        ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(5);
        //执行
        //执行的任务,延迟时间,时间间隔,延迟时间的单位
        scheduledThreadPool.scheduleAtFixedRate(new Runnable() {
            @Override
            public void run() {
                System.out.println("延迟3秒再执行,并且每2秒执行一次");
            }
        }, 3, 2, TimeUnit.SECONDS);
        //不可以关闭池子,关闭池子该方法无法运行
        //关闭池子
        //scheduledThreadPool.shutdown();
    }
}

单线程池newSingleThreadExecutor

只创建一个线程,如果这个线程因为异常或正常结束,则会有一个新的线程来替代它。
执行顺序按照先来后到顺序执行,谁先来谁先执行。
适用于一个一个任务执行的情况

如:

public class _04_NewSingletThreadExecutor {
    public static void main(String[] args) {
        ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor();

        //一次性创建十个请求
        for (int i = 0;i<10;i++){
            final int index = i;
            System.out.println("创建 "+i+" 次");
            //只能依次执行
            singleThreadExecutor.execute(new Runnable() {
                @Override
                public void run() {
                    try {
                        Thread.sleep(1000);
                        System.out.println(Thread.currentThread().getName()+" : "+index);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            });
        }
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值