多线程最简单的使用

几乎每种操作系统都支持进程的概念 ―― 进程就是在某种程度上相互隔离的、独立运行的程序。

线程化是允许多个活动共存于一个进程中的工具。大多数现代的操作系统都支持线程,而且线程的概念以各种形式已存在了好多年。Java 是第一个在语言本身中显式地包含线程的主流编程语言,它没有把线程化看作是底层操作系统的工具。

有时候,线程也称作轻量级进程。就象进程一样,线程在程序中是独立的、并发的执行路径,每个线程有它自己的堆栈、自己的程序计数器和自己的局部变量。但是,与分隔的进程相比,进程中的线程之间的隔离程度要小。它们共享内存、文件句柄和其它每个进程应有的状态。

    进程可以支持多个线程,它们看似同时执行,但互相之间并不同步。一个进程中的多个线程共享相同的内存地址空间,这就意味着它们可以访问相同的变量和对象,而且它们从同一堆中分配对象。尽管这让线程之间共享信息变得更容易,但你必须小心,确保它们不会妨碍同一进程里的其它线程。

为什么使用线程?

使用线程的一些原因是它们可以帮助:

使 UI 响应更快

利用多处理器系统

简化建模

执行异步或后台处理

对于web主要是,异步处理,服务器应用程序从远程来源(如套接字)获取输入。当读取套接字时,如果当前没有可用数据,那么对 SocketInputStream.read() 的调用将会阻塞,直到有可用数据为止。

因为是阻塞IO,所以一个线程只可以处理一个IO套接字,(当然,程序可以轮询套接字,查看是否有可用数据,但通常不会使用这种做法,因为会影响性能。NIO不存在这个问题)

但是,如果创建了一个线程来读取套接字,那么当这个线程等待套接字中的输入时,主线程就可以执行其它任务。通过可以创建多个线程,这样就可以同时读取多个套接字。使用线程等待套接字的代码也比轮询更简单、更不易出错。

当多个线程访问同一数据项(如静态字段、可全局访问对象的实例字段或共享集合)时,需要确保它们协调了对数据的访问,这样它们都可以看到数据的一致视图,而且相互不会干扰另一方的更改。

Java 语言提供了两个关键字:synchronized 和 volatile。

synchronized关键字用法(原理http://www.importnew.com/29031.html)

一 原子性(互斥性):实现多线程的同步机制,使得锁内代码的运行必需先获得对应的锁,运行完后自动释放对应的锁。

二 内存可见性:在同一锁情况下,synchronized锁内代码保证变量的可见性。

三 可重入性:当一个线程获取一个对象的锁,再次请求该对象的锁时是可以再次获取该对象的锁的。

如果在synchronized锁内发生异常,锁会被释放。

总结:

(1)synchronized方法 与 synchronized(this) 代码块 锁定的都是当前对象,不同的只是同步代码的范围

(2)synchronized (非this对象x) 将对象x本身作为“对象监视器”:

a、多个线程同时执行 synchronized(x) 代码块,呈现同步效果。

b、当其他线程同时执行对象x里面的 synchronized方法时,呈现同步效果。

c、当其他线程同时执行对象x里面的 synchronized(this)方法时,呈现同步效果。

(3)静态synchronized方法 与 synchronized(calss)代码块 锁定的都是Class锁。Class 锁与 对象锁 不是同一个锁,两者同时使用情况可能呈异步效果。

(4)尽量不使用 synchronized(string),是因为string的实际锁为string的常量池对象,多个值相同的string对象可能持有同一个锁。

volatile关键字用法

一 内存可见性:保证变量的可见性,线程在每次使用变量的时候,都会读取变量修改后的最的值。

二 不保证原子性。

java线程简介(https://www.ibm.com/developerworks/cn/education/java/j-threads/j-threads.html)

每个 Java 程序都至少有一个线程 ― 主线程。当一个 Java 程序启动时,JVM 会创建主线程,并在该线程中调用程序的 main()方法。

JVM 还创建了其它线程,通常都看不到它们 ― 例如,与垃圾收集、对象终止和其它 JVM 内务处理任务相关的线程。其它工具也创建线程,如 AWT(抽象窗口工具箱(Abstract Windowing Toolkit))或 Swing UI 工具箱、servlet 容器、应用程序服务器和 RMI(远程方法调用(Remote Method Invocation))。

创建线程

Thread t = new Thread(() -> System.out.println("Thread lambda")); 

t.start();

使用一个线程最简单的方法,传入一个Runnable。(如开头上)

或者是继承thread类。


但是从Java5之后就基本上使用线程池来代替直接创建线程。

利用Executors类提供了4种不同的线程池:newCachedThreadPool, newFixedThreadPool, newScheduledThreadPool, newSingleThreadExecutor。

newCachedThreadPool

创建一个可缓存的无界线程池,该方法无参数。当线程池中的线程空闲时间超过60s则会自动回收该线程,当任务超过线程池的线程数则创建新线程。线程池的大小上限为Integer.MAX_VALUE,可看做是无限大。(SynchronousQueue实现队列)

newFixedThreadPool

创建一个固定大小的线程池,该方法可指定线程池的固定大小,对于超出的线程会在LinkedBlockingQueue队列中等待。(LinkedBlockingQueue实现队列)

newSingleThreadExecutor

创建一个只有线程的线程池,该方法无参数,所有任务都保存队列LinkedBlockingQueue中,等待唯一的单线程来执行任务,并保证所有任务按照指定顺序(FIFO或优先级)执行。

这个线程池可以在线程死后(或发生异常时)重新启动一个线程来替代原来的线程继续执行下去(LinkedBlockingQueue实现队列)

newScheduledThreadPool

创建一个可定时执行或周期执行任务的线程池,该方法可指定线程池的核心线程个数。

concurrent包下常用类

Doug Lea主刀

AQS是concurrent包的基石(AbstractQueuedSynchronizer)

https://www.cnblogs.com/waterystone/p/4920797.html

Callable接口和Future

 Future就是对于具体的Runnable或者Callable任务的执行结果进行取消、查询是否完成、获取结果。必要时可以通过get方法获取执行结果,该方法会阻塞直到任务返回结果。

Future submit(Callable task) :

CompletionService

如果想Executor提交了一组计算任务,并且希望在计算完成后获得结果,那么可以保留与每个任务关联的Future,然后反复使用get方法,同事将参数timeout指定为0,从而通过轮询来判断任务是否完成。这种方法虽然可行,但却有些繁琐。幸运的是,还有一种更好的方法:CompletionService。CompletionService将Executor和BlockingQueue的功能融合在一起。你可以将Callable任务提交给它来执行,然后使用类似于队列操作的take和poll等方法来获得已完成的结果,而这些结果会在完成时被封装为Future。ExecutorCompletionService实现了CompletionService,并将计算部分委托到一个Executor。代码示例如下:


intcoreNum = Runtime.getRuntime().availableProcessors();

ExecutorService executor = Executors.newFixedThreadPool(coreNum);

CompletionService completionService = newExecutorCompletionService(executor);

for(inti=0;i

{

    completionService.submit( newCallable(){

        @Override

        publicObject call() throwsException

        {

            returnThread.currentThread().getName();

        }});

}

for(inti=0;i

{

    try

    {

        Future future = completionService.take();

        System.out.println(future.get());

    }

    catch(InterruptedException | ExecutionException e)

    {

        e.printStackTrace();

    }

}

Atomic系列-原子变量类


主要使用cas(compareAndSet)+volatile变量实现


直接赋值的原子操作

需要类似于实现和之前的值进行比较(例如自增)


内部是一个循环,使用unsafe中的本地方法,实现cas操作,不适用于竞争十分激烈的场景。


(CAS彻底讲解https://www.cnblogs.com/Mainz/p/3546347.html

主要使用java中的unsafe实现CAS)

ConcurrentHashMap (https://www.ibm.com/developerworks/cn/java/java-lo-concurrenthashmap/index.html)

可以高并发的map,使用分段锁,不同于hashtable。

自己使用CAS实现增长。

或者使用guava提供的AtomicLongMap。

对访问的url计数,非线程安全,具体urlhttp://www.importnew.com/26035.html

CopyOnWriteArrayList

一看名字就知道写时复制新数组,读的时候直接使用,使用于写少读多的情况。

CountDownLatch

强调的是一个线程等待多个线程完成某件事,只能用一次,无法重置;

比如有一个任务A,它要等待其他4个任务执行完毕之后才能执行

CyclicBarrier

强调的是多个线程互相等待完成,才去做某个事情,可以重置。

CyclicBarrier,让一组线程到达一个同步点后再一起继续运行,在其中任意一个线程未达到同步点,其他到达的线程均会被阻塞。

CountDownLatch 是计数器, 线程完成一个就记一个, 就像 报数一样, 只不过是递减的.

而CyclicBarrier更像一个水闸, 线程执行就想水流, 在水闸处都会堵住, 等到水满(线程到齐)了, 才开始泄流.

CyclicBarrier和CountDownLatch区别

http://scau-fly.iteye.com/blog/1955165

线程池https://fangjian0423.github.io/2016/03/22/java-threadpool-analysis/

你可能感兴趣的:(多线程最简单的使用)