线程是操作系统能够进行运算调度的最小单位,它被包含在进程之中,是进程中的实际运作单位。不同进程使用不同的内存空间,而所有的线程共享一片相同的内存空间。别把它和栈内存搞混,每个线程都拥有单独的栈内存用来存储本地数据。如果一个线程完成一个任务要100毫秒,那么用十个线程完成该任务只需10毫秒。
start()方法被用来启动新创建的线程,其内部调用了run()方法。run()称为线程体,通过调用Thread类的start()方法来启动一个线程。
可见性(即一个线程修改了某个变量的值,这个值对其他线程是立即可见的)。
禁止进行指令重排序(指令重排序:处理器为了提高程序运行效率,可能会对输入代码进行优化,它不保证程序中各个语句的执行先后顺序同代码中的顺序一致,但是它会保证程序最终执行结果和代码顺序执行的结果是一致的)。
缺点:不支持原子性。而atomic具有原子性。
每个线程都有自己的栈内存,用于存储本地变量、方法参数和栈调用,一个线程中存储的变量对其它线程是不可见的。而堆是所有线程共享的一片公用内存区域。对象都在堆里创建,为了提升效率线程会从堆中弄一个缓存到自己的栈,如果多个线程使用该变量就可能引发问题,这时volatile变量就可以发挥作用了,它要求线程从主存中读取变量的值。
避免死锁最简单的方法就是将系统中所有的资源设置标志位、排序,规定所有的进程申请资源必须以一定的顺序(升序或降序)做操作来避免死锁。
如果任务来了才创建线程,那么响应时间会变长,并且一个进程能创建的线程数有限。为了避免这些问题,在程序启动时就创建若干线程来响应处理,它们被称为线程池,里面的线程叫工作线程。
在java.lang.Thread中有一个方法叫holdsLock(),它返回true就是拥有锁。
活锁的进程状态可以改变但是却不能继续执行。比如两个人在狭小的走廊碰到,两个人都试着避让对方好让彼此通过,但是因为避让的方向都一样导致最后谁都不能通过走廊。
在等待时wait会释放锁,而sleep一直持有锁。wait通常被用于线程间交互,sleep通常被用于暂停执行。
ThreadPoolExecutor的submit()方法将会抛出一个RejectedExecutionException异常。
多个线程同时运行一个代码,如果每次运行结果和单线程运行的结果一样,就是线程安全的。
只有一个线程在等待状态时notify()才有用武之地。notifyAll()唤醒所有线程并允许他们争夺锁,确保了至少有一个线程能继续运行。
每个对象都有锁,通过线程获得。由于wait、notify、notifyAll都是锁级别的操作,所以把他们定义在Object类中因为锁属于对象。
ThreadLocal解决多线程中因并发产生数据不一致问题。ThreadLocal为每个线程中并发访问的数据提供一个副本,通过访问副本来运行业务,这样的结果是耗费了内存,但大大减少了线程同步所带来性能消耗,也减少了线程并发控制的复杂度。
ThreadLocal和Synchonized都用于解决多线程并发访问。但是他们有本质区别。synchronized是利用锁的机制,使变量或代码块在某一时该只能被一个线程访问。而ThreadLocal为每一个线程都提供了变量的副本,使得每个线程在某一时间访问到的并不是同一个对象,这样就隔离了多个线程数据共享。而Synchronized却正好相反,它用于在多个线程间通信时能够获得数据共享。
synchronized有一些缺点。比如你不能扩展锁之外的方法或者块边界,尝试获取锁时不能中途取消等。ReentrantLock类拥有与synchronized相同的并发性和内存语义且它还具有可扩展性。
读写锁分为读锁和写锁,上读锁时允许多个用户进行读操作,上写锁时,只允许一个用户进行写操作。读写锁最适用于对数据结构的读操作次数多于写操作的场合。
阻塞式方法是指程序会一直等待该方法完成期间不做其他事情。
在多线程中有多种方法让线程按特定顺序执行,你可以用线程类的join()方法在一个线程中启动另一个线程,另外一个线程完成该线程继续执行。为了确保三个线程的顺序你应该先启动最后一个(T3调用T2,T2调用T1),这样T1就会先完成而T3最后完成。