多线程(下)

多线程JDK1.5之后的特性:java.util.concurrent.*; java并发包

 

创建线程的三种方式:

       1)继承 extends Thread类,并重写 void run() 方法

       2)实现 implements Runnable接口。并重写 void run() 方法

       3)实现 implements Callable接口。并重写 Object call() 方法

Runnable 和 Callable的区别:

     (1)Runnable没有返回值 void , Callable 有返回值 Object

     (2)   Runable不支持抛出异常,只能 try-catch, 而 Callable 支持抛出异常  throws Exception

              源码:

    /**
     * Computes a result, or throws an exception if unable to do so.
     *
     * @return computed result
     * @throws Exception if unable to compute a result
     */
    public interface Callable<V> {
    
        V call() throws Exception;
    }

             例:创建一个线程

  //Callable代表线程要要执行的代码
    FutureTask task = new FutureTask(new Callable(){
        public Object call(){

              //要执行的代码块...
        }
    
     });

     Thread t = new Thread(task);
 
  //启动线程
      t.start();

  //获取 call() 方法的返回值
 
      Object o = task.get();

 

线程池:创建有限线程资源为更多的任务提供服务的(享元模式)                          好处:实现了对线程的重复使用

                 java中对线程池的抽象:ExecutorService  创建一个固定大小的线程池

//创建一个固定大小的线程池  10 个线程
ExecutorService threadpool = Executors.newFixedThreadPool(10);

for(int i=0; i<10; i++){
     threadpool.submit(() -> {//向线程池提交一个线程
           System.out.println(Thread.concurrent().getName());//获取当前正在运行的线程的名字
           Thread.sleep(1000);//让当前线程休眠1秒
     });
}

//关闭线程池,不再接受新的任务,当所有线程都执行完时,线程池关闭
threadpool.shutdown();

ExecutorService的核心实现类: ThreadPoolExecutor

ThreadPoolExecutor(int corePoolSize,
                                            int maximumPoolSize,
                                                      long keepAliveTime,
                                                                       TimeUnit unit,
                                                                               BlockingQueue<Runnable> workQueue)

参数列表:

        1)int corePoolSize: 线程核心数目(即线程池里最多保留多少个线程)

        2)int maximumPoolSize: 线程池最大线程数  corePoolSize + 救急线程   <= maximum 

                                                   救急线程:当任务过多,连阻塞队列都满了,这时线程池会创建新的线程来救急

        3)long keepAliveTime: 当线程执行完之后,最多存活的时间,针对于 救急线程

        4)TimeUnit  unit:  救急线程的存活时间的单位

        5)BlockingQueue<Runnable> workQueue : 阻塞队列,如果超过了corePoolSize的数量,

                                                                                其他线程就会进入阻塞队列进行排队

        线程池的大小 =  maximumPoolSize  +  阻塞队列的大小  若超过线程池的大小,则会抛出 RejectedExecutionException

 

创建线程池的四种方式:

     1)ExecutorService threadpool  = Executors.newFixedThreadPool( 10 );

         创建 固定大小 的线程池: 核心线程数 = 最大线程数 (没有救急线程)

                      特点:阻塞队列无上限,可存放任意数量的任务

                                 适合执行数量有限,长时间运行的任务

 

     2)ExecutorService threadpool  = Executors.newCachedThreadPool();

           创键 缓冲 的线程池: 核心线程数 = 0 

                     特点:  最大线程数是 Integer 的最大值 ( 2的32次方 - 1),救急线程可无限被创键,生存时间是 60 s

                               适合任务数比较密集,但任务执行时间较短的情况

     3)ExecutorService threadpool = Executors.newSingleThreadPool();

           创键 单线程 线程池 : 核心线程数 = 1

                     特点: 核心线程数为 1,且不能修改

           区别:

                     newFixedThreadPool( 1 ): 虽然线程数固定为 1, 但之后可修改

                     newSingleThreadPool( ): 线程数固定,且不能修改

     4)ExecutorService threadpool = Executors.newScheduledThreadPool( 10 )

             创建带有 日期安排 的线程池

  // 创建一个带有 日程安排的线程
    ScheduledExecutorService service = Executors.newScheduledThreadPool(5);


  // 让任务推迟一段时间执行   ,   参数1.任务对象,                   参数2,3 推迟的时间
    service.schedule(()->{ System.out.println("执行任务..."); } ,  10L, TimeUnit.SECONDS);

  
  // 以一定的频率反复执行任务(任务不会重叠)
    service.scheduleAtFixedRate(()->{
    try {
        Thread.sleep(1200);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }

  //   参数1,任务对象,             参数2,初始推迟时间,    参数3,4 时间间隔和单位
    System.out.println("hello");},      0,             1, TimeUnit.SECONDS);


  // delay表示从上一个任务结束,到下一个任务开始之间的时间  为 1 秒
    service.scheduleWithFixedDelay(()->{ 
                System.out.println("hello");}, 
                          0, 1, TimeUnit.SECONDS);


  // service.shutdown();

 

原子类操作: jdk1.5之后,利用了现代处理器的特性

                      可以用非阻塞的方式完成原子操作

原子类有:AtomicXXX

                 AtomicBoolean 
                 AtomicInteger 
                 AtomicIntegerArray 
                 AtomicLong 
                 AtomicLongArray 

                 ......

// 创建原子整数类
     private static AtomicInteger i = new AtomicInteger(0);//相当于 int i = 0;

     public static void main(String[] args) throws InterruptedException {
            Thread t1 = new Thread(() -> {
                for (int j = 0; j < 5000; j++) {
                        i.getAndIncrement();  // 获取并且自增  i++
                      //i.incrementAndGet();  // 自增并且获取  ++i
                }
            });
    
            Thread t2 = new Thread(() -> {
                for (int j = 0; j < 5000; j++) {
                        i.getAndDecrement(); // 获取并且自减  i--
                }
            });

            t1.start();
            t2.start();
            t1.join();
            t2.join();
            System.out.println(i);
    }


   线程安全集合类:

             StringBuffered, String, Vector , HashTable, Random... 都是线程安全的

    JDK1.5之后: 新增的线程安全集合类

            1)ConcurrentHashMap: 实现了Map 集合, 线程安全

            原理图:

          2)CopyOnWriteArray: 实现了List,线程安全

                  原理图:

      3) ConcurrentSkipListMap : 实现了Map(可排序),且线程安全

      4) BlockingQueue: 阻塞队列,先进先出

                阻塞队列的应用:

 class Test{
     //创建一个阻塞队列
        private static BlockingQueue<Product>  queue = new ArrayBolckingQueue<>( 5 );
       
        public static void main(String[] args){
              
              //创建生产者线程
              new Thread(() -> {
                    for(int i=0; i<10; i++){
                         Product p = new Product();//生成者生产产品
                         System.out.println(Thread.currentThread().getName()+"生产了"+p);
                         try{
                             queue.put(p);//添加到阻塞队列中
                         }catch(InterruptedException e){
                              e.printStackTrace();                                   
                         }
                    }                  
              }).start();  

              //创建消费者线程
              for(int i=0; i<5; i++){
                 new Thread(()->{
                    for(int j=0; j<2; j++){   //一次消费两个产品
                      try{
                         Product p = queue.take(); // 从阻塞队列中取到产品
                         System.out.println(Thread.currentThread().getName()+"消费了"+p);
                      } catch(InturrptedException e ){
                             e.printStackTrace();
                      }     
                   }                                   
                 }).start(); 
             }               
        }
 }

 

  ThreadLocal:     线程本地变量  ,让每个线程各用个的资源,防止资源的争用                                                       这种变量在多线程环境下访问时能够保证各个线程里变量的独立性                                            可实现不用synchronized就能保证线程安全   

//线程局部变量
private static ThreadLocal<SimpleDateFormat> local = new ThreadLocal() {
        @Override        // 初始值
        protected Object initialValue() {
                return new SimpleDateFormat("yyyy-MM-dd"); // 存入当前线程
        }
};


public static void main(String[] args) {
        for (int i = 0; i < 10; i++) {
                new Thread(()->{
                    try {
                        SimpleDateFormat sdf = local.get(); // 获取本线程自己的局部变量
                        Date date = sdf.parse("1951-10-09"); // 每个线程使用的是自己的
                                                                SimpleDateFormat因此没有争用
                        System.out.println(Thread.currentThread().getName() + " " + date);
                    } catch (ParseException e) {
                        e.printStackTrace();
                    }
                }).start();
        }
}

 

CAS 机制(Compare And Swap): 比较并交换 乐观锁,如果修改失败,没关系,重新尝试

                                                           不使用 synchronized 就实现了线程同步

(乐观锁)CAS 思想:  首先不会给共享资源加锁,而是做一个尝试,  先拿到旧值,查看旧值是否跟共享区域的值相等
                                     如果不等,那么说明别的线程改动了共享区域的值,我的修改失败, 如果修改失败,没关系,重新尝试
                                     如果相等,那么就让我的修改成功


  悲观锁:就像synchronized, 改动之前必须添加锁,防止别人修改

 

1)ReentrantLock:  重入锁

                 .lock():  给当前对象进行上锁,让其在操作时保证元素的原子性

                 .unlock(): 将当前对象的锁解开

      ********  Synchronized 和 ReentrantLock 的区别???

                1)Synchronized 是 虚拟机层面支持的,是关键字

                      而ReentrantLock 是 JDK 层面支持的,是类

                2)性能上早期ReentrantLock要优越,但内存占用高,ReentrantLock灵活性更高,可以加入公平锁、锁条件等功能,                       实现更多的控制。而在更高版本的jdk下synchronized有显著提升,接近ReentrantLock,仅在并发高时不如。

static int i = 0;

public static void main(String[] args) throws InterruptedException {
    ReentrantLock rl = new ReentrantLock(); // 创建 重入锁 对象

    Thread t1 = new Thread(() -> {
        for (int j = 0; j < 5000; j++) {
            try {
                rl.lock(); // 加锁
                i++;
            } finally {
                rl.unlock(); // 保证解锁一定被执行
            }
        }
    });

    Thread t2 = new Thread(() -> {
        for (int j = 0; j < 5000; j++) {
            try {
                rl.lock(); // 加锁
                i--;
            } finally {
                rl.unlock(); // 保证解锁一定被执行
            }
        }
    });

    t1.start();
    t2.start();
    t1.join();
    t2.join();
    System.out.println(i);
}

 

2)CountDownLatch:  闭锁,倒计时锁,目的是 只有当多个线程都执行完毕后,

                                        才能继续向下执行

       

public static void main(String[] args) throws InterruptedException {

    // 构造方法需要指定倒计时的数字
    CountDownLatch cdl = new CountDownLatch(3);
 
    new Thread(()->{
        System.out.println("线程1开始运行"+new Date());
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("线程1准备完成"+new Date());
        cdl.countDown();
    }).start();

    new Thread(()->{
        System.out.println("线程2开始运行"+new Date());
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("线程2准备完成"+new Date());
        cdl.countDown();
    }).start();

    new Thread(()->{
        System.out.println("线程3开始运行"+new Date());
        try {
            Thread.sleep(1500);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("线程3准备完成"+new Date());
        cdl.countDown();
    }).start();

    // 主线程等待,直到倒计时为0
    System.out.println("主线程等待");
    cdl.await();
    System.out.println("ready go....");
}

 

3)CyclicBarrier: 可循环的屏障(栅栏),只有线程数达到指定数时,才执行,

                              否则继续等待到满足为止

      *********CyclicBarrier 与 CountDownLatch 的区别???

            CyclicBarrier : (循环栅栏) 允许一组线程相互等待,直到到达栅栏屏障点,栅栏可以重用

            CountDownLatch:(倒计时锁)   是让一个线程等待其它任务(线程)执行完毕,结束倒计时后不能重用

// CyclicBarrier   可循环的 屏障(栅栏)
// 当满足CyclicBarrier设置的线程个数时,继续执行,没有满足则等待
CyclicBarrier cb = new CyclicBarrier(2); // 个数为2时才会继续执行

new Thread(()->{
    System.out.println("线程1开始.."+new Date());
        try {
            cb.await(); // 当个数不足时,等待
         } catch (InterruptedException e) {
            e.printStackTrace();
         } catch (BrokenBarrierException e) {
            e.printStackTrace();
         }
    System.out.println("线程1继续向下运行..."+new Date());
}).start();

new Thread(()->{
    System.out.println("线程2开始.."+new Date());
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        try {
            cb.await(); // 2 秒后,线程个数够2,继续运行
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (BrokenBarrierException e) {
            e.printStackTrace();
        }
    System.out.println("线程2继续向下运行..."+new Date());
}).start();

4)Semapore: 信号量: 限制能同时运行的线程,当达到限制数时,线程同时执行,

                         继续等到又达到限制数时,才继续执行

 

// 限制了能同时运行的线程上限
Semaphore s = new Semaphore(3); 

for (int i = 0; i < 10; i++) {
    new Thread(() -> {
        try {
            s.acquire(); // 获得此信号量
            System.out.println("我是线程" + Thread.currentThread().getName());
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            s.release(); // 释放信号量
        }

    }).start();
}

 

 

 

   

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
多线程下注入 Spring Bean,我们需要注意一些问题。首先,Spring Bean 的依赖注入是线程安全的,因为 Spring 容器会保证 Bean 的实例唯一,并且在多线程环境中不会出现竞态条件。然而,如果我们在多线程环境下手动创建 Bean 实例并注入,就需要注意线程安全性。 为了在多线程下注入 Spring Bean,我们需要确保 Bean 的作用域是线程安全的。通常情况下,我们可以将 Bean 的作用域设置为 prototype,使得每个线程都拥有自己的 Bean 实例,避免线程间的竞态条件。 另外,我们需要注意在多线程环境下对 Bean 的操作是否会造成线程安全问题。比如在单例 Bean 中使用了非线程安全的对象或方法,就可能会导致线程安全问题。在这种情况下,我们需要使用同步机制来保证线程安全,或者考虑将 Bean 的作用域设置为 prototype。 在注入 Bean 的时候,我们还需要考虑是否需要进行依赖注入或者手动创建 Bean 实例。如果需要在多线程下注入 Bean,最好使用 Spring 容器进行依赖注入,这样可以保证线程安全性并且简化代码逻辑。 总的来说,在多线程下注入 Spring Bean,我们需要确保 Bean 的作用域是线程安全的,并且在操作 Bean 的过程中注意线程安全性,避免出现竞态条件。同时尽量使用 Spring 容器进行依赖注入,避免手动创建 Bean 实例造成线程安全问题。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值