Java中的多线程与高并发

进程和线程
进程是系统进行资源调度和分配的基本单元,是操作系统的基础。线程是系统调度的最小单元,是线程的运算单元,一个进程可以包括一个或多个线程。
线程的三种创建方式:
一、继承Thread类

    class MyThread extends Thread {
        @Override
        public void run() {
            Log.i(TAG, "MyThread-->" + Thread.currentThread());
        }
    }

	new MyThread().start();

二、实现Runnabel接口

    class MyRunnable implements Runnable {

        @Override
        public void run() {
            Log.i(TAG, "MyRunnable-->" + Thread.currentThread());
        }
    }

	new Thread(new MyRunnable()).start();

三、实现Callable接口

    class MyCallable implements Callable<String> {

        @Override
        public String call() throws Exception {
        	//带返回值
            return "LT";
        }
    }

        try {
            FutureTask<String> futureTask = new FutureTask<>(new MyCallable());
            new Thread(futureTask).start();
            //futureTask.get() 获取返回值
            Log.i(TAG, "MyCallable-->" + futureTask.get());
        } catch (ExecutionException e) {
            e.printStackTrace();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

当线程创建完成调用start()方法时,此线程进入就绪状态(Runnable)等待CPU调度分配资源,当此线程被调度获得CUP资源开始执行此线程的run()方法时,线程才进入运行状态(Running),所以线程创建完成并调用start()方法并不能保证线程一定开始运行。
当线程加锁,比如线程试图通过synchronized去获取某个锁,但是此锁已经被其他线程占有时,当前线程就处于阻塞状态(BLOCKED)。
当调用sleep()、wait()、join()后,线程也会处于阻塞状态,此时这个线程就等待其他线程采取某些操作,比如notify()、notifyAll()等将他唤醒,被唤醒的线程不会立即执行而是重新处于就绪状态等待调度获得CPU资源进入运行状态。
等待的另外一种状态是计时等待,触发计时等待的条件和触发等待类似,不同的是触发计时等待会传递一个时间参数。
如果线程正常执行完毕或者被强制性的提前终止或出现异常结束,JVM就会销毁此线程,释放CPU资源。

线程池
线程池是一种多线程处理形式,使用线程池有如下几个优点:
1、重用线程池中的线程,减少因创建和销毁线程而带来的性能开销。
2、有效的控制线程池最大并发数,避免无止境的创建线程造成大量线程之间抢占系统资源造成的阻塞现象。
3、简单的线程管理,提供定时执行以及指定间隔时间循环执行等。
可以通过两种方式来创建线程池:使用Executors工具类和ThreadPoolExecutor类。两种方式本质都是一样的,最终都是通过配置ThreadPoolExecutor类的参数来实现。

public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler)

corePoolSize:线程池核心线程数。默认情况下核心线程会在线程池中一直存活,即使他们处于闲置状态,当将ThreadPoolExecutor的allowCoreThreadTimeOut属性设置为true时,闲置核心线程在等待新任务到来时会有超时策略,超时间隔由keepAliveTime参数指定,当等待时间超过keepAliveTime指定的时长后,闲置的核心线程就会被终止。

maximumPoolSize:线程池所能容纳的最大线程数。

keepAliveTime:非核心线程闲置时的超时时长,非核心线程等待新任务到来的时间间隔超过这个时长就会被回收。当ThreadPoolExecutor的allowCoreThreadTimeOut属性设置为true时,同样作用于核心线程。

unit:用于指定keepAliveTime的单位。

workQueue:等待队列,存储Runnable对象。当核心线程都在运行中时,继续添加的任务会进入等待队列中。这个队列一般都是线程安全的,它的大小也可以由开发者自行订制。常用的阻塞队列有:

threadFactory:线程工厂。为线程池提供创建线程的功能。

handler:拒绝策略。当线程池由于任务队列已满或者无法成功执行任务时,ThreadPoolExecutor会调用handler的rejectedExecution方法来通知调用者。ThreadPoolExecutor默认提供了四种拒绝策略:
1、ThreadPoolExecutor.AbortPolicy() :默认的拒绝策略,会丢弃任务并抛出RejectedExecutionException异常。
2、ThreadPoolExecutor.DiscardPolicy() :丢弃任务,但是不抛出异常。
3、ThreadPoolExecutor.DiscardOldestPolicy() :先将任务队列中的最先提交的第一个任务抛弃,然后重新提交被拒绝的当前任务。
4、ThreadPoolExecutor.CallerRunsPolicy() :由调用线程来执行该任务(调用线程调用 run方法)。

工具类Executors提供了创建四种不同类型的线程的方式,它们都直接或者间接的通过配置ThreadPoolExecutor参数来实现:
1、FixedThreadPool:通过Executors的newFixedThreadPool创建。
是一个只有核心线程的线程池(核心线程数等于最大线程数),并且线程池中的线程处于空闲状态时不会被回收,当所有的线程都处于活动状态,有新的任务进来时,新任务会处于等待状态,直到有线程空闲出来。如下所示:FixedThreadPool只有核心线程,并且核心线程没有超时机制,任务队列也没有大小限制。

	public static ExecutorService newFixedThreadPool(int nThreads) {
        return new ThreadPoolExecutor(nThreads, nThreads,
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue<Runnable>());
    }

2、CachedThreadPool:通过Executors的newCachedThreadPool创建。
线程数量不定的线程池,它只有非核心线程,并且最大线程数为Integer.MAX_VALUE,相当于最大线程数可以任意大,当所有线程都处于活动状态时,有新任务进来就会创建一个新的线程来执行这个新任务,反之则使用空闲线程来执行新任务,空闲线程设置有超时机制,为60秒,超过这个时间空闲线程就会被回收。CachedThreadPool使用的任务队列是SynchronousQueue,是一种特殊的不能存储元素的队列,当有新任务时会立即执行。CachedThreadPool的这种特性使之适合执行大量的耗时少的任务,当线程池中的所有线程都处于空闲状态时由于有超时机制所有线程都会被回收,这个时候CachedThreadPool中实际上是没有任何线程的,不占用任何系统资源。

    public static ExecutorService newCachedThreadPool() {
        return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                      60L, TimeUnit.SECONDS,
                                      new SynchronousQueue<Runnable>());
    }

3、ScheduledThreadPool:通过Executors的newScheduledThreadPool创建。
是一种核心线程数固定而非核心线程无限制的线程池,并且当非核心线程闲置时会被立即回收。ScheduledThreadPool主要用于执行定时任务和具有固件周期的重复任务。

   public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
        return new ScheduledThreadPoolExecutor(corePoolSize);
    }
   public ScheduledThreadPoolExecutor(int corePoolSize) {
      super(corePoolSize, Integer.MAX_VALUE,
            DEFAULT_KEEPALIVE_MILLIS, MILLISECONDS,
            new DelayedWorkQueue());
  }

4、SingleThreadExecutor:通过Executors的newSingleThreadExecutor创建。
SingleThreadExecutor内部只有一个线程,并且是核心线程,它确保所有的任务都在一个线程中按顺序执行。SingleThreadExecutor的意义在于统一所有的外部任务到一个线程中,这样使得这些任务不用处理线程之间的数据同步问题。

    public static ExecutorService newSingleThreadExecutor() {
        return new FinalizableDelegatedExecutorService
            (new ThreadPoolExecutor(1, 1,
                                    0L, TimeUnit.MILLISECONDS,
                                    new LinkedBlockingQueue<Runnable>()));
    }

线程安全
当多个线程同时操作某一个资源数据时,由于CPU时间调度的问题,数据可能会被多次覆盖修改。
当线程创建时,JVM会为其创建一个工作内存,是线程的私有区域,Java内存模型(JMM)规定所以变量都存储在主内存中,主内存是共享内存,而线程对变量的操作只能在工作内存中,当线程需要操作变量时,首先需要把变量从主内存拷贝到工作内存,操作完成后再写回主内存。不同线程间不能访问对方的工作内存,线程间的通信只能通过主内存来完成。

线程安全的三个特性:
1、原子性:操作过程中不能被分割,对共享内存的操作在其他线程看来要么是执行成功、要么是失败。
2、可见性:一个线程对某个共享变量的修改能及时的同步到共享内存中,以保证其他线程可以对结果及时可见。
3、有序性:禁止指令重排序

线程同步
volatile
一种轻量级的线程同步机制。
volatile保证数据可见性,但是不保证原子性,并且禁止指令重排序。

Atomic
Java1.5提供的包,其中包括了一系列的原子操作类。这些类可以保证在多线程环境下,当某个线程在执行Atomic的方法时不会被其他线程打断。Atomic核心操作是CAS。
CAS(Compare And Swap)比较并交换,CAS是一种乐观锁思想的应用。所谓乐观锁就是每次不加锁而是假设没有冲突而去执行某个操作,如果因为冲突失败就重试,直到成功为止。与乐观锁相对应的就是悲观锁,比如synchronized,悲观锁会导致其他所有需要锁的线程挂起,等待持有锁的线程释放锁。
CAS操作有三个核心参数:
1:主内存(也就是共享内存)的值V
2:线程工作内存(私有内存)中的值A,A是主内存中V值的拷贝
3:线程工作内存中A值改变后的值B,B是需要写入到工作内存中的值。
CAS核心操作主要在将B值写入到V值的过程中先比较A值与V值是否相等,如果相等则把B值赋给A值,如果不相等证明V值被其他线程修改过,则把V值重新赋值给A,并且重新计算得到新的B值,之后再次进行前面的操作。
CAS虽然可以避免线程的阻塞,但是也带来了如下问题:
ABA问题:CAS比较的是值,如果当主内存中的值是A,当进行赋值操作比较的时候发现值还是A,这个时候CAS操作并不能确定A值没有被其他线程修改过,有可能A值先改变为了B之后又修改回了A。解决ABA问题其中一个办法是加版本号,每次A值变化时版本号都加1,比较的时候不仅比较值同时也检查版本号是否一致。其次atomic里面提供了一个类AtomicStampedReference来解决ABA问题,这个类中的compareAndSet方法会首先检查当前引用是否等于预期引用,并且当前标志是否等于预制标志,只有都相等才会把该引用和该标志的值设定为给定的新值。
循环时间开销大:CAS操作如果不成功会一直自旋 直到成功,造成CPU开销大。
只能保证一个变量的原子操作:当对多个共享变量操作时,CAS就不能保证原子性。这个时候可以使用锁或者使用atomic包下的AtomicReference类,AtomicReference类保证引用对象的原子性,可以把多个共享变量放到一个类中再进行CAS操作。

Synchronized
实现方法内部或者代码块内部数据的互斥访问。同一时间最多只有一个线程可以获取到锁对象,当线程A尝试获取线程B持有的锁时,线程A只有等待或者阻塞,直到线程B释放 锁对象。
可见性:任何线程在获取到锁对象的第一时间会先去主内存读取共享变量的最新值到工作内存中,在释放锁的第一时间会先把工作内存中的值写回主内存。
原子性:同一时间最多只有一个线程可以访问,因此synchronized加锁的方法或者代码块要么不执行,要么全部执行完成。
有序性:

Lock
可实现线程安全的可见性、原子性以及有序性。与synchronized相比,Lock可以手动获取锁和释放锁,比synchronized灵活。Lock定义了一系列的锁操作方法,需要手动释放锁,否则会出现死锁现象。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值