java多线程(初阶)

1.单例模式

通过静态方法来new一个实例化对象,且构造方法被private修饰,是java中的一种设计模式,分为懒汉模式,饿汉模式。

(1)饿汉模式:

class Singleton {
    private static Singleton instance = new Singleton();
    private static Singleton getInstance() {
        return instance;
    }
     private Singleton() {

    }
}

在类加载的时候对象就已经被new出来了,只需要通过静态方法来获取就行了。

(2)懒汉模式:

class SingletonLazy {
    private static SingletonLazy instance =  null;
    private static SingletonLazy getInstance() {
        if(instance == null) {
            instance = new SingletonLazy();
        }
        return instance;
    }
    private SingletonLazy() {

    }
}

类加载的时候对象并没有创建出来,调用静态方法以后才能够new出对象,并且返回。

(3)注意事项:

懒汉模式(多个线程修改同一个变量)比饿汉模式(多个线程读取同一个变量)更不安全。原因:

上述情况导致对象new了两次,违背了单例的要求。创建对象不一定是轻量的,背后可能是重量的。故对上述懒汉模式中的代码进行加锁。

此时创建对象的这个操作就是原子的,当有一个线程获取到这把锁,在它没有释放锁之前,其他线程只能等待这把锁被释放,从而导致阻塞。这个等待时间不知道有多长,可能会阻塞很久,所以又做了如下优化:

第一个if作用:用来判断是否要进行加锁,第二个if作用:用来判断是否要进行new对象。如果一个对象已经被new好了,另一个线程调用该方法的时候,在第一个if就进不去,就不会进行加锁,直接返回对象。

但在这个过程中可能会出现一个问题:指令重排序。

new对象的这个操作分三步:

a.内存分配地址

b.在内存空间上构造对象

c.将地址赋予instance引用

正常来说应该abc的执行顺序,而现在有可能的执行顺序时acb,如果按照这个执行顺序,有可能线程t1在还ac执行完过后,instance是一个不为空的非法对象,此时线程t2来调用这个方法了,直接就返回了instance对象(非法的),所以为了避免上述的情况,就让volatile修饰变量。

2.阻塞队列

是一种特殊的队列,线程安全且具有阻塞特性。

阻塞特性:

a.在队列满的时候,往队列中插入元素会阻塞等待;

b.在队列为空的时候,删除元素也会阻塞等待;

(1)生产者消费者模型:(典型的使用阻塞队列完成的模型)

生产者把生产出来的内容放到阻塞队列中,消费者从队列中获取内容。

(2)优点:

a.解耦合

添加了阻塞队列:

通过上述方式耦合程度大大降低。

b.削峰填谷

(3)JAVA标准库中的阻塞队列(BlockingQueue)

是一个接口,有三种实现方式:基于链表(适合:数据不要求比较,且数据量大),基于顺序表,基于优先级队列(适合:数据要求比较)

ps:阻塞队列是继承于Queue(队列)的,尽量不要使用Queue里面的方法,因为可能会造成线程不安全且没有阻塞特性。

3.定时器

客户端发起请求之后,正常情况下来说,服务器是会接收并且处理请求,然后返回响应。但是也有可能服务器挂了,客户端等待了很久都没有接收到响应。

对于客户端来说不能无限的等待下去,到达这个最大期限以后会再次发送请求,如果还是没有接收到响应,就会直接断开于服务器的连接或者做其他的事。

上述这个最大期限就是定时器所需要完成的事情。

(1)JAVA标准库中的定时器(Timer)

在java.util包下。

eg:

TimeTask是一个抽象类,实现了Runnable接口;delay(延时)单位:ms

启动程序观察,程序并没有停止。

原因;Timer中的其他线程没有执行完毕。(Timer中不仅有扫描线程还有一些其他的线程,这些线程没有结束是导致进程没有结束的主要原因)扫描线程就是Timer中执行TimeTask任务的一个线程。

(2)自定义定时器

a.Timer中需要一个线程,来扫描是否要执行任务。

b.需要一个数据结构把任务保存起来

c.还需要一个类,来描述当前的任务(任务内容和时间)

MyTimerTask类:

public class MyTimerTask{
    private Runnable runnable;
    private long time;
    public MyTimerTask(Runnable runnable, long delay) {
        this.runnable = runnable;
        this.time = System.currentTimeMillis() + delay;
    }
    public Runnable getRunnable() {
        return this.runnable;
    }
    public long getTime() {
        return this.time;
    }
}

MyTimer类:

public class MyTimer {
    //有一个数据结构来存储任务,其中任务执行顺序是按照时间来执行的
    private PriorityQueue<MyTimerTask> queue = new PriorityQueue<>(new Comparator<MyTimerTask>() {
        @Override
        public int compare(MyTimerTask o1, MyTimerTask o2) {
            return (int)(o1.getTime()-o2.getTime());
        }
    });
    //创建锁对象
    private Object locker = new Object();
    //schedule方法用来放任务
    public void schedule(Runnable runnable, long time) {
        synchronized (locker) {
            queue.offer(new MyTimerTask(runnable,time));
            locker.notify();
        }
    }

    public MyTimer() {
        //扫描线程,不停地进行扫描当前的任务是否要执行。
        Thread t = new Thread(()-> {
           while (true) {
               try {
                   synchronized (locker) {
                       while(queue.isEmpty()) {
                           locker.wait();
                       }
                       MyTimerTask task = queue.peek();
                       if(System.currentTimeMillis() >= task.getTime()) {
                           //执行任务
                           task.getRunnable().run();
                           queue.poll();
                       }else {
                           locker.wait(task.getTime() - System.currentTimeMillis());
                       }
                   }
               } catch (InterruptedException e) {
                   throw new RuntimeException(e);
               }

           }
        });
        t.start();
    }
}

test类:

public class test {
    public static void main(String[] args) {
        MyTimer timer = new MyTimer();
        timer.schedule(new Runnable() {
            @Override
            public void run() {
                System.out.println("1000");
            }
        }, 1000);

        timer.schedule(new Runnable() {
            @Override
            public void run() {
                System.out.println("2000");
            }
        }, 2000);

        timer.schedule(new Runnable() {
            @Override
            public void run() {
                System.out.println("3000");
            }
        },3000);

        System.out.println("程序启动!!");
    }
}

程序启动:

也不会结束,由于while循环(模拟了Timer里面程序的结束结果)。

ps:notify:

4.线程池

线程是轻量级线程,但是频繁地创建线程这个开销也是不容忽视的。

有两种解决方式:

a.协程:比线程还轻量,但是在JAVA不常用

b.线程池:在创建线程1的时候,就把其他线程2,3,4,5.....给创建好了,要用的时候直接去池子中取

从池子中取要比直接创建的速率要快?

是的。因为从池子中取这个操作是一个纯用户态的操作,这个过程是可控的,而通过new来创建对像,是内核态+用户态的操作,这个过程是不可控的。(创建线程会调用相关api,并到系统内核中去执行)

(1)ThreadPoolExecutor(线程池)

java中提供了一些线程池,但本质都是对ThreadPoolExecutor的封装

a.构造方法

主要理解最后一个:

 

分别为核心线程数和最大线程数。

线程池中线程的数目是不确定的,在核心线程数和最大线程数范围之间。

举个例子来理解:

一个公司中有正式员工和实习生,所有的正式员工就相当于核心线程数,公司所有正式员工+实习生就是最大线程数。实习生不允许摸鱼,而正式员工允许,在任务较多的,做不过来就会多找几个实习生来帮助完成任务,当任务不多的时候,这时候就看哪个实习生在摸鱼的,就将他开除。

线程池中类似这样做来满足效率的需求和避免过多的系统开销。

线程活跃时间,就类似于上述允许实习生摸鱼的时间。

阻塞队列(消息队列),用来存放线程池中的任务。

这个一个工厂类,由该类来负责创建对象(通过该类中的静态方法来创建并返回),并且返回。(工厂模式的体现)

拒绝策略:当线程池中任务已经放满了过后,此时再来一个新的任务,对于不同的拒绝策略,有着不同的效果:

 关于线程池中线程的数目:

是一个不确定的值(如果答出某个确切的值都是错误的)

原因:线程中的代码一般分两种:cpu密集型和io密集型

cpu密集型:代码主要进行算术运算和逻辑运算

io密集型:代码主要涉及io操作

假设一个cpu上最多允许跑的线程数目是N个:

假设一个代码中所有都为cpu密集型,线程池中的线程数目不应该超过N个,超过的话就会把cpu吃满,影响cpu的执行速率,反而更多的线程还会增加调度的开销。

假设一个代码中所有都为io密集型,由于是对硬盘进行操作,不吃cpu,所以线程池中的线程数目可以超过N个。

所以线程池中的数目是不确定的。不同的代码,线程池中的线程数目是不同的(无法知道一个代码中是cpu密集型还是io密集型)。

(2)自定义线程池:

a.搞一个数据结构来存储任务。

b.ThreadPoolExecutor中需要创建出n个线程来执行任务(构造方法中实现)

c.需要一个submit方法来提交任务。

MyThreadPoolExecutor类:

public class MyThreadPollExecution {
    //阻塞队列
    private BlockingQueue<Runnable> queue = new ArrayBlockingQueue(100);

    //提交任务
    public void submit(Runnable runnable) throws InterruptedException {
        //此处应该有拒绝策略,这里省略
        synchronized (locket) {
            queue.put(runnable);
            locket.notify();
        }
    }

    private Object locket = new Object();
    public MyThreadPollExecution(int n) {
        //创建n个线程来执行任务
        for(int i = 0; i < n; i++) {
            Thread t = new Thread(()-> {
                try {
                    synchronized (locket) {
                        while (queue.isEmpty()) {
                            locket.wait();
                        }
                        Runnable runnable = queue.take();
                        runnable.run();
                    }
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            });
            t.start();
        }
    }
}

test类:

public class test {
    public static void main(String[] args) throws InterruptedException {
        MyThreadPollExecution myThreadPollExecution = new MyThreadPollExecution(4);
        for(int i = 0; i < 1000; i++) {
            int id = i;
            myThreadPollExecution.submit(new Runnable() {
                @Override
                public void run() {
                    System.out.println("执行任务" + id);
                }
            });
        }
    }
    
}

执行结果:

  • 16
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值