java---多线程三

目录

单例模式

饿汉模式

懒汉模式

阻塞队列

 基于数组实现阻塞队列

定时器

 实现定时器

线程池

Executor接口

模拟实现线程池

单例模式

java设计模式之一,表示一个类只会存在一个实例

饿汉模式

类加载同时,创建实例

//饿汉模式 只能有一个实例,创建类的时候生成
class Signton {
    //1、创建类对象 使用static修饰,得到唯一的对象
    private static Signton signton = new Signton();

    //2、私有化构造方法 其他类不可以通过new关键字创建类对象
    private Signton() {
    }

    //3、返回实例化对象
    public static Signton getinstance() {
        return signton;
    }
}
public class demo1 {
    public static void main(String[] args) {
        Signton.getinstance();//获取到了这个实例
    }
}

类实例在类加载时期创建 

懒汉模式

类加载时候不创建实例,第一次使用才创建实例

class Signton2 {
    //1、私有化构造方法
    private Signton2() {
    }

    ;
    //2、在使用类的时候创建对象
    private static Signton2 signton2 = null;//static修饰,signton2只有一份

    public static Signton2 getinstance() {
        if (signton2 == null) {
            signton2=new Signton2();
            return signton2;
        }
        return signton2;
    }
}
public class demo2 {
    public static void main(String[] args) {
        //static修饰,signton2只有一份,只会创建一个实例
        System.out.println(Signton2.getinstance());
        System.out.println(Signton2.getinstance());

    }
}

懒汉模式效率更高

观察getinstance()方法,相比较来说饿汉模式只读,是更加线程安全的

饿汉模式,存在问题:if (signton2 == null)时,可能多个线程都满足这个条件,从而创建多个类

解决方法:1、将判断和赋值打包到一起,执行原子操作2、避免指令重排序带来的问题,使用

volatile
class Signton3 {
    private Signton3() {
    }

    private static volatile Signton3 signton3 = null;

    public static Signton3 getinstance() {
        if(signton3==null) {
            //只有在signton3==null的时候,才会涉及到赋值操作,避免每次都加锁
            synchronized (Signton3.class) {
                //将赋值和判断打包到一起,执行原子操作
                if (signton3 == null) {
                    signton3 = new Signton3();
                }
            }
        }
        return signton3;
    }
}
public class demo3 {
    public static void main(String[] args) {
        Signton3.getinstance();
    }
}

阻塞队列

1、先进先出

2、当阻塞队列满了,不能存入元素

3、当阻塞队列为空,不能取元素

用于解决生产者---消费者问题

1、实现低耦合:不采用阻塞队列,生产者直接和消费者联系,当一方产生更改,另外一方也应该产生更改; 采用阻塞队列,生产者和阻塞队列联系,阻塞队列和消费者联系,当生产者更改,消费者不会有影响,当消费者更改,生产者不会有影响

2、阻塞队列相当于缓冲区,当生产者生产数量过多时,阻塞队列存储数据,消费者可以保持原速度从阻塞队列读取数据

 1、BlockingDeque是一个接口

2、包含put()和take()方法,分别用于阻塞式的存入元素和取元素

3、包含add(),offer(),pop()等方法,但是这些方法是不阻塞的

public class demo4 {
    public static void main(String[] args) throws InterruptedException {
        BlockingDeque <String > blockingDeque=new LinkedBlockingDeque<>();
        blockingDeque.put("hello");
        System.out.println(blockingDeque.take());

    }
}

 基于数组实现阻塞队列

class BlackingQuenu {
    private  volatile int[] elem = new int[10];
    private volatile   int front=0;
    private volatile  int tail=0;
   //浪费一个元素空间
     private boolean isfull() {//满
         if (((front - tail + elem.length + 1) % elem.length) == 0) {//读
             return true;
         }
         return false;
     }
     private boolean isEmpty() {
        if (front == tail) {
            return true;
        }
        return false;
    }
     Object locker =new Object();
   public void put(int target) throws InterruptedException {
       synchronized (locker) {
           while (isfull()) {//队列满了 陷入阻塞
               locker.wait();
           }
           elem[tail] = target;
           tail = (tail + 1) % elem.length;
           //队列不空 唤醒take的阻塞
           locker.notify();
       }
   }
      public int take() throws InterruptedException {
          synchronized (locker) {
              while (isEmpty()) {//队列为空 陷入阻塞
                  locker.wait();
              }
              int ret = elem[front];
              front = (front + 1) % elem.length;
              //去掉了一个元素,表示put的阻塞可以唤醒
              locker.notify();
              return ret;
          }
      }
     public void print() {
         synchronized (locker) {
             int size = (tail - front + elem.length) % elem.length;//存放数组元素个数
             int count = 0;
             for (int i = front; count < size; i = (i + 1) % elem.length) {
                 System.out.print(elem[i] + " ");
                 count++;
             }
             System.out.println();
         }
     }
}
public class demo5 {
    public static void main(String[] args) {
        BlackingQuenu queue = new BlackingQuenu();
        Thread producer = new Thread() {//生产者进程

            @Override
            public void run() {
                Random random=new Random();
                while (true) {
                    try {
                      int ret=random.nextInt(100);
                        System.out.println("生产了:  "+ret);
                        queue.put(ret);
                      Thread.  sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        };
        Thread customer = new Thread() {
            @Override
            public void run() {
                while (true) {
                    try {
                        System.out.println("消费了:  "+queue.take());
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        };

        customer.start();
        producer.start();
    }
}

定时器

类似于闹钟,在被唤醒某一段时间之后执行某一种操作

Timer类:

Timer类的schedule方法 

 

1、创建Timer 类的实例化对象

2、调用schedule方法,参数之一是TimerTask类型的对象,TimerTask类是抽象类,重写这个类的run()方法

public class demo3 {
    public static void main(String[] args) {
        Timer timer = new Timer();
        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                System.out.println("定时器执行");
            }
        }, 1000);
    }
}

 实现定时器

1、创建类来描述任务-》MyTask类

2、将多个事务组织起来-》MyTimer类,按照时间优先级排列,并考虑多线程情况,使用

PriorityBlockingQueue实现

3、选择时间到了可以执行的任务执行-》执行最靠前的任务

class MyTask  implements Comparable<MyTask>{
    private Runnable runnable;//要执行的任务
    private long time;//任务要执行的时间 毫秒级


    public MyTask(Runnable runnable, long time) {
        this.runnable = runnable;//传入Runnable 重写run方法
        this.time = System.currentTimeMillis() + time;//任务执行的具体时间:现在时间+等待时间
    }

    public void run() {//调用Runnable接口的run方法,执行run方法里的内容
        runnable.run();
    }
    public long gettime(){
        return this.time;
    }
    @Override
    public int compareTo(MyTask o) {
        return (int) (this.time - o.time);
    }
}

class MyTimer {

    private PriorityBlockingQueue<MyTask> queue = new PriorityBlockingQueue<>();

    public void schedule(Runnable runnable, long delay) {//创建任务
        MyTask task = new MyTask(runnable, delay);
        queue.add(task);//这里要写出MyTask类如何比较

        //每次增加一个新的任务,就要看扫描线程中执行的任务时间到了吗 因为可能新加入的任务要立刻执行 
        synchronized (this) {
            this.notify();
        }
    }

    public MyTimer() {//创建构造方法 创建扫描线程来执行最早的任务
//内部开启一个线程,执行最早的方法
        Thread thread = new Thread() {
            @Override
            public void run() {
                while (true) {
                    try {
                        MyTask task = queue.take();//取出队头元素
                        if (task.gettime() > System.currentTimeMillis()) {
                            queue.put(task);
                            //没到执行时间
                            synchronized (this) {
                                this.wait(task.gettime() - System.currentTimeMillis());
                                //等待这么长时间之后,自动唤醒
                            }
                        } else
                            task.run();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        };
        thread.start();
    }
}
public class demo7 {
    public static void main(String[] args) {
        MyTimer timer = new MyTimer();
        timer.schedule(new Runnable() {
            @Override
            public void run() {
                System.out.println("定时器1");
            }
        }, 1000);

        timer.schedule(new Runnable() {
            @Override
            public void run() {
                System.out.println("定时器2");
            }
        }, 1000);

    }
}

1、MyTask类在队列比较过程中,要知道比较规则

2、Thread创建的线程中,在没有等到时间到来时,这段期间让线程处于等待态,不浪费资源

*使用wait()而不是sleep()的原因:

wait()可以被唤醒,sleep()不可以被唤醒,可以在Thread线程处于等待的时候,插入一个新的任务,这个新插入的任务可能比Thread线程执行的任务更早执行,那么就要中途唤醒扫描线程,让扫描线程执行新插入的任务

线程池

线程比进程来说,更加轻量,但是如果线程频繁的创建,分配,释放资源,产生的消耗也是比较大的,我们就可以使用线程池来进一步优化线程管理

将线程先创建好,放到线程池中;后序需要线程就直接从线程池中取线程;归还线程也是直接还给线程池

创建线程是在内核态实现的,线程池存入线程和取出线程涉及到了用户态,用户态的运行通常比内核态效率更高(用户态由我们控制,内核态不知道其内部的实现方法,效率未知)

Executor接口

 jdk5开始,java.util.concurrent包下增加了Executor接口及其子类,允许使用线程池技术来管理线程并发问题,它有一个子接口ExecutorService,通过这个子接口可以很方便的进行线程池管理

Executors---线程池执行器

调用静态方法创建ExecutorService接口的实例化对象 

public class demo2 {
    public static void main(String[] args) {

//创建一个有固定线程数量的线程池的执行器. 这种线程池可以很好的控制多线程热内,也不会因为响应过多而导致崩溃
//参数指定了线程个数
        ExecutorService exe = Executors.newFixedThreadPool(10);
//创建一个可扩展的线程池的执行器,这个线程池执行器适用于启动许多短期任务的应用程序
//可以根据任务量自动扩容
        ExecutorService exe1=  Executors.newCachedThreadPool();
//在特殊情况下,创建一个只执行一个任务的单个线程
        ExecutorService exe2=      Executors.newSingleThreadExecutor();
//创建一个线程池,可以按照调度命令在给定的延迟之后运行,或定期执行。
// 带有定时功能的线程池       
        ExecutorService exe3=Executors.newScheduledThreadPool(10);
    }
}

通过Executor接口实现线程池管理的主要步骤如下:

1、创建一个Runnable接口或者Callable接口的实现类,同时重写run()方法或者call()方法

2、创建Runnable接口或者Callable接口的实现类对象

3、使用Executors线程执行器创建线程池

4、使用ExecutorService执行器服务类的submit()方法将Runnable接口或者Callable接口的实现类对象提交到线程池进行管理

5、线程执行结束之后,使用shutdown()方法关闭线程池

public class demo3 {
    public static void main(String[] args) {
        //1、使用Executors线程执行器创建线程池
        ExecutorService exe = Executors.newFixedThreadPool(10);
        //2、将Runnable接口的实现类传入ExecutorService
        exe.submit(new Runnable() {
            @Override
            public void run() {
                System.out.println("线程1");
            }
        });
        exe.submit(new Runnable() {
            @Override
            public void run() {
                System.out.println("线程2");
            }
        });
        //3、关闭线程池
        exe.shutdown();
    }
}

模拟实现线程池

1、使用Runnable描述任务

2、组织任务(使用队列实现) 

3、能够描述线程,表示这个线程执行哪些任务

4、组织线程

class MyExecutor {
    //1、描述一个任务
    private Runnable runnable = null;
    //2、组织任务 使用阻塞队列实现
    private BlockingQueue<Runnable> queue = new LinkedBlockingDeque<>();
    //3、描述线程要执行哪些任务    创建MyThread类,重写的run()方法可以获取到所有的任务,并且执行所有任务
     class MyThread extends Thread {
        public void run() {
            Runnable runnable = null;
            try {
                runnable = queue.take();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            runnable.run();
        }
    }
    //4、创建数组,来组织多个线程
    private ArrayList<MyThread> list = new ArrayList<>();
    //5、构造方法 创建出多个线程 这些线程每一个都可以访问到queue队列里的任务
    public  MyExecutor(int n) {
        for (int i = 0; i < n; i++) {
            MyThread thread = new MyThread();
            list.add(thread);//数组里添加线程
            thread.start();//启动线程 执行queue队列里的任务
        }
    }
    //6、创建方法,将任务提交到线程池
    public void submit(Runnable runnable) throws InterruptedException {
        queue.put(runnable);
    }
}

public class demo4 {
    public static void main(String[] args) throws InterruptedException {
        MyExecutor myExecutor = new MyExecutor(10);
        myExecutor.submit(new Runnable() {
            @Override
            public void run() {
                System.out.println("线程1");
            }
        });
        myExecutor.submit(new Runnable() {
            @Override
            public void run() {
                System.out.println("线程2");
            }
        });
    }
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值