几乎每种操作系统都支持进程的概念 ―― 进程就是在某种程度上相互隔离的、独立运行的程序。
线程化是允许多个活动共存于一个进程中的工具。大多数现代的操作系统都支持线程,而且线程的概念以各种形式已存在了好多年。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方法获取执行结果,该方法会阻塞直到任务返回结果。
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
for(inti=0;i { completionService.submit( newCallable @Override publicObject call() throwsException { returnThread.currentThread().getName(); }}); } for(inti=0;i { try { Future 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/