Java多线程使用浅析

多线程是在同一个程序内部并行执行,因此会对相同的内存空间进行并发读写操作。

有一个容易混淆的概念叫做进程。

进程:一个计算机程序的运行实例,包含了需要执行的指令;有自己的独立地址空间,包含程序内容和数据;不同进程的地址空间是互相隔离的;进程拥有各种资源和状态信息,包括打开的文件、子进程和信号处理。

线程:表示程序的执行流程,是CPU调度执行的基本单位;线程有自己的程序计数器、寄存器、堆栈和帧。同一进程中的线程共用相同的地址空间,同时共享进进程锁拥有的内存和其他资源。

一个线程的生命周期

  • 新建状态:

    使用 new 关键字和 Thread 类或其子类建立一个线程对象后,该线程对象就处于新建状态。它保持这个状态直到程序 start() 这个线程。

  • 就绪状态:

    当线程对象调用了start()方法之后,该线程就进入就绪状态。就绪状态的线程处于就绪队列中,要等待JVM里线程调度器的调度。

  • 运行状态:

    如果就绪状态的线程获取 CPU 资源,就可以执行 run(),此时线程便处于运行状态。处于运行状态的线程最为复杂,它可以变为阻塞状态、就绪状态和死亡状态。

  • 阻塞状态:

    如果一个线程执行了sleep(睡眠)、suspend(挂起)等方法,失去所占用资源之后,该线程就从运行状态进入阻塞状态。在睡眠时间已到或获得设备资源后可以重新进入就绪状态。可以分为三种:

    • 等待阻塞:运行状态中的线程执行 wait() 方法,使线程进入到等待阻塞状态。

    • 同步阻塞:线程在获取 synchronized 同步锁失败(因为同步锁被其他线程占用)。

    • 其他阻塞:通过调用线程的 sleep() 或 join() 发出了 I/O 请求时,线程就会进入到阻塞状态。当sleep() 状态超时,join() 等待线程终止或超时,或者 I/O 处理完毕,线程重新转入就绪状态。

  • 死亡状态:

    一个运行状态的线程完成任务或者其他终止条件发生时,该线程就切换到终止状态。

java中要想实现多线程,有两种手段,一种是继续Thread类,另外一种是实现Runable接口.(其实准确来讲,应该有三种,还有一种是实现Callable接口,并与Future、线程池结合使用

(1)继续Thread类

class myThread extends Thread{
    private int tid;
    //char[] array = {'A','B'};

    public myThread(int tid){
        this.tid = tid;
    }

     @Override
     public void run(){
        try{

            for(int i =0; i< 10; ++i){
               Thread.sleep(1000);
               System.out.println(String.format("%d:%d",tid,i));
                //System.out.println((String.format("%d:%c",tid,array[i])));
            }
        }catch (Exception e){
            e.printStackTrace();
        }
     }
}
(2) 实现 Runable接口
 
class MultiThreadTests implents Runnable {

    public static void myThreadsTest(){
       
          for (int i = 0; i< 2; i++){
        
                  @Override
                  public void run() {
                         try{
                             for (int i =0; i<2; i++){
                                Thread.sleep(1000);
                                System.out.println(String.format("T2:%d",i));
                             }
                         }catch (Exception e){
                             e.printStackTrace();
                         }
                  }
              }
      }
    public static void main(String[] argd){
                myThreadsTest();
    }
}

或者是

public class MultiThreadTests {

    public static void myThreadsTest(){
          for (int i = 0; i< 2; i++){
              new Thread(new Runnable() {
                  @Override
                  public void run() {
                         try{
                             for (int i =0; i<2; i++){
                                Thread.sleep(1000);
                                System.out.println(String.format("T2:%d",i));
                             }
                         }catch (Exception e){
                             e.printStackTrace();
                         }
                  }
              }).start();
          }
    }

    public static void main(String[] argd){
                myThreadsTest();
    }
}

多线程中有许多的方法:

(a)synchronized的使用:可以用于代码块,可以直接用于方法

代码段:

static Object object = new Object();
    //锁测试
    public static void synchronizedTest1(){
         synchronized (object){
             try{
                 for (int i = 0; i<5; i++){
                     Thread.sleep(1000);
                     System.out.println(String.format("T1%d",i));
                 }
             }catch (Exception e){
                 e.printStackTrace();
             }

         }
    }

用于方法:

public class Thread1 implements Runnable {
   public synchronized void run() {  
        ..do something
   }
}

(b)BlockingQueue 同步队列

阻塞队列。该类是java.util.concurrent包下的重要类,通过对Queue的学习可以得知,这个queue是单向队列,可以在队列头添加元素和在队尾删除或取出元素。类似于一个管道,特别适用于先进先出策略的一些应用场景。

BlockingQueue在队列的基础上添加了多线程协作的功能:


BlockingQueue

除了传统的queue功能(表格左边的两列)之外,还提供了阻塞接口put和take,带超时功能的阻塞接口offer和poll。put会在队列满的时候阻塞,直到有空间时被唤醒;take在队 列空的时候阻塞,直到有东西拿的时候才被唤醒。用于生产者-消费者模型尤其好用,堪称神器。

常见的阻塞队列有:

  • ArrayListBlockingQueue
  • LinkedListBlockingQueue
  • DelayQueue
  • SynchronousQueue

ConcurrentHashMap
高效的线程安全哈希map。请对比hashTable , concurrentHashMap, HashMap

class Customer implements Runnable{
    private BlockingQueue<String> q;
    public Customer(BlockingQueue<String> q) {
        this.q = q;
    }

    @Override
     public void run(){
          try{
            while (true){
                System.out.println(Thread.currentThread().getName() + ":" + q.take());
            }
          } catch (Exception e){
              e.printStackTrace();
          }
     }
}

class Producer implements Runnable{

    private BlockingQueue<String> q;
    public Producer(BlockingQueue<String> q ){
        this.q = q;
    }

    @Override
    public void run(){
        try{
            for(int i = 0; i<100;++i){
                Thread.sleep(10);
                q.put(String.valueOf(i));
            }
        }catch (Exception e){
            e.printStackTrace();
        }
    }
}

(c)ThreadLocal:

保存线程的独立变量。对一个线程类(继承自Thread)
当使用ThreadLocal维护变量时,ThreadLocal为每个使用该变量的线程提供独立的变量副本,所以每一个线程都可以独立地改变自己的副本,而不会影响其它线程所对应的副本。常用于用户登录控制,如记录session信息。
实现:每个Thread都持有一个TreadLocalMap类型的变量(该类是一个轻量级的Map,功能与map一样,区别是桶里放的是entry而不是entry的链表。功能还是一个map。)以本身为key,以目标为value。
主要方法是get()和set(),set之后在map里维护一个threadLocal -> a,get时将a返回。ThreadLocal是一个特殊的容器。

 private static ThreadLocal<String> stringThreadLocal = new ThreadLocal<>();
    static String AB = "AB";
    public static void ThreadLocalTest(){
        for (int i=0; i< AB.length()-1; ++i) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    try {
                        for (int i = 0; i < 2; ++i) {
                            stringThreadLocal.set(String.valueOf(AB.charAt(i)));
                            Thread.sleep(1000);
                            System.out.println("TheadLocal:" + stringThreadLocal.get());
                        }

                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
            }).start();
        }
    }

(d)原子类:AtomicInteger:

atomicInteger,或者使用自己保证原子的操作,则等同于synchronized

    //原子型变量与基本类型变量的在多线程执行操作时的区别

    private static int counter  = 0;
    private static AtomicInteger atomicInteger = new AtomicInteger(0);

    //基本类型变量在多线程中是线程不安全的,就是两个线程同时访问一个变量的时候只能一个线程实现操作
    private  static  void testWithoutAtomic(){          //静态方法不能访问非静态的方法,但是非静态的方法是能够访问静态的变量的
        for (int i = 0; i <10; i++){
            new Thread(new Runnable() {
                @Override
                public void run() {
                    try{
                        Thread.sleep(3000);
                        for (int j = 0; j<10; j++){
                            counter ++;
                            System.out.println(counter);
                        }

                    }catch (Exception e){
                        e.printStackTrace();
                    }
                }
            }).start();
        }

    }

    //原子型数据
    private static void testWithAtomic(){
        for (int i = 0; i<10; ++i){
            new Thread(new Runnable() {
                @Override
                public void run() {
                    try{
                        for (int j = 0; j<10; ++j){
                            System.out.println(atomicInteger.incrementAndGet());  //原子型的数据自增,线程安全的
                        }
                    }catch (Exception e){
                        e.printStackTrace();
                    }
                }
            }).start();
        }
    }

(e)ThreadPoolExecutor:

Executor 可以创建3种类型的ThreadPoolExecutor线程池:

FixedThreadPool:创建固定长度的线程池,每次提交任务创建一个线程,直到达到线程池的最大数量,线程池的大小不再变化。这个线程池可以创建固定线程数的线程池。特点就是可以重用固定数量线程的线程池。
SingleThreadExecutor:SingleThreadExecutor是使用单个worker线程的Executor。特点是使用单个工作线程执行任务。
CachedThreadPool:CachedThreadPool是一个”无限“容量的线程池,它会根据需要创建新线程。特点是可以根据需要来创建新的线程执行任务,没有特定的corePool

 //线程池
    public static void ExecutorTest(){
        //ExecutorService service = Executors.newSingleThreadExecutor();
        ExecutorService service = Executors.newFixedThreadPool(2);

        service.submit(new Runnable(){

            @Override
            public void run() {
                 try{
                for(int i = 0; i<10; ++i){
                    Thread.sleep(1000);
                    System.out.println("Executor1:" + i);
                }
            }catch (Exception e){
                     e.printStackTrace();
                 }
            }
        });


        service.submit(new Runnable() {
            @Override
            public void run() {
                try{
                    for (int i = 0; i<10; ++i){
                        Thread.sleep(1000);
                        System.out.println("Executor2:" + i);
                    }
                }catch (Exception e){
                    e.printStackTrace();
                }
            }
        });

        service.shutdown();  //任务执行完之后关闭

        //轮询当前线程是否结束
        while(!service.isTerminated()){
            try{
                Thread.sleep(1000);
                System.out.println("Waited for termination");
            }catch (Exception e){
                e.printStackTrace();
            }
        }}

ExecutorService类内部是通过ThreadPoolExecutor实现的,掌握该类有助于理解线程池的管理,本质上,他们都是ThreadPoolExecutor类的各种实现版本。

(f)Future:  用于等待异步的结果阻塞的等待结果或者是捕获当前线程中的异常

过去,需要异步线程的任务执行结果,要求主线程和任务执行线程之间进行同步和数据传递。

Future简化了任务的异步执行,作为异步操作的一个抽象。调用get()方法可以获取异步的执行结果,如果任务没有执行完,会等待,直到任务完成或被取消,cancel()可以取消。

//Future  等待异步的结果 阻塞的等待结果或者是捕获线程中的异常
    private static void testFuture(){
        ExecutorService service = Executors.newSingleThreadExecutor();
        Future<Integer> future = service.submit(new Callable<Integer>() {
            @Override
            public Integer call() throws Exception{
                Thread.sleep(1000);
                //return  1;
                throw new IllegalArgumentException("异常");

            }
        });

        service.shutdown();
        try{
             System.out.println(future.get());         //等待线程执行完成数据的操作之后再获取这个值
            //System.out.println(future.get(100,TimeUnit.MILLISECONDS));    //要求100ms执行完要不然报错
        }catch (Exception e){
            e.printStackTrace();
        }
    }

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值