线程是现代操作系统调度的最小单元。例如启动一个java程序,这时操作系统便会创建一个java进程,在一个进程中可以创建多个线程,每个线程都有自己的计数器、堆栈内存和局部变量等属性,而且可以访问共享的内存变量。而每个java程序其实都是多线程的,例如一个print("hello world")的简单程序,在执行程序时,其实也创建不止线程,所以java天生就是多线程的。
在1)中其实已经说了,线程是进程的子集,一个进程可以有多个线程,每个线程拥有自己的计数器、堆栈内存和局部变量等属性,而且可以访问共享内存。但是不同的进程拥有自己的栈空间。
1)由于现代计算机核数的不断增加,而线程作为大多数操作系统调度的基本单元,一个程序作为一个进程运行时,产生了多个线程,但是在每个时刻,一个处理器上只能有一个线程在执行,如果是单线程,它在运行时,只会用到一个处理器,再多的处理器核心也没什么用,但是如果是多线程,那么就可以将程序的计算分配到多个处理器上,这样就可以大大缩短程序运行时间。例如,一个程序采用单线程在四核CPU上运行时间为8ms,那么如果采用4线程可以将运行时间缩短到小于8ms,注意,这里的运行时间一般不会是成倍数关系,因为多线程在执行时存在上下文切换,并不是线程越多,执行效率越高,相反,如果使用过多线程,反而会造成效率下降。
2)多线程可以让程序有更快的响应时间。举个例子,例如中午你要吃饭,你可以在炒菜之前就将米饭煲在电饭煲中,然后再去炒菜,这样可能在你炒完菜的时候,米饭已经好了,如果是单线程,你可能是先煲米饭或者先炒菜,这样会让这两件事情总时长变得更长。
3)与进程相比,线程的创建和切换开销更小。
在java中创建多线程的方式经常用的有两种,但是总的创建方法至少有四种:详情参考Java实现多线程的四种方法
1)继承Thread类创建线程。定义一个继承Thread类的类A,并且覆盖run()方法,在run()方法中执行线程操作,然后在main方法中创建这个类的对象:A a = new A(),接着用对象实例来调用start()方法,启动线程并执行方法,即a.start()。
2)实现Runnable接口创建线程。定义一个实现Runnable接口的类A,在类中实现run()方法,注意这里这个类不是线程类,在实现run()方法后,接着在主线程中new一个这个类的对象,例如A a = new A(),接着创建线程类,Thread t = new Thread(a),将刚才创建实现run()的类实例传给线程类,然后继续调用start()方法来创建线程,并执行方法。其实第一种方法也是线程类实现了Runnable接口,而重写run()方法相当于实现了Runnable接口的run()方法。
3)使用Callable和FutureTask创建线程。定义一个Callable接口的实现类,创建Callable实现类对象传递给FutureTask构造器,将FutureTask对象传递给Thread构造器,Thread对象调用start方法启动线程,通过FutureTask对象的get方法获取线程运行的结果。
4)使用线程池,例如用Executor框架。使用Executors工具类中的静态工厂方法用于创建线程池,创建线程池使用execute方法启动线程,使用shutdown方法等待提交的任务执行完成并后关闭线程。
1)Vector 和 ArrayList 实现了同一接口 List, 但所有的 Vector 的方法都具有 synchronized 关键修饰,对于单个方法的单独操作是线程安全的,但对于复合操作,Vector 仍然需要进行同步处理。顺带比较一下两者。
Vector和ArrayList的对比:
1)Vector由于是每个方法都是线程安全的,所有所以效率低。
2)两者都需要连续的内存空间。
3)Vector每次扩容为原来的两倍,ArrayList为原来1.5倍,数据过大时Vector容易可能会内存分配失败。
4)两者相对于LinkedList索引数据速度快,队尾增加或者删除速度快,而LinkedList指定位置添加或者删除速度快。
5)ArrayList可以通过List list = Collections.synchronizedList(new ArrayList(...));来实现线程安全。
2)SimpleDateFormat 不是线程安全类,详细请看SimpleDateFormat的线程安全问题与解决方案
所有对基本类型的操作,除了某些对long类型和double类型的操作之外,都是原子级的。目前的JVM(java虚拟机)都是将32位作为原子操作,并非64位。当线程把主存中的 long/double类型的值读到线程内存中时,可能是两次32位值的写操作,显而易见,如果几个线程同时操作,那么就可能会出现高低2个32位值出错的情况发生。要在线程间共享long与double字段是,必须在synchronized中操作,或是声明为volatile。
Vector、Stack、HashTable、Enumeration(接口),除此之外其他集合类均不是线程安全的。
忙循环:用循环让一个线程等待,不会放弃 CPU,传统的方法wait(), sleep() 或 yield() 都会放弃了CPU控制。
目的:为了保留 CPU 缓存,避免重建缓存和减少等待重建时间。在多核系统中,一个等待线程醒来时,可能会在另一个内核运行,这样就会重建缓存。
线程局部变量是局限于线程内部的变量,属于线程自身所有,不在多个线程间共享。Java 提供 ThreadLocal 类来支持线程局部变量,是一种实现线程安全的方式。但是在管理环境下(如 web 服务器)使用线程局部变量的时候要特别小心,在这种情况下,工作线程的生命周期比任何应用变量的生命周期都要长。任何线程局部变量一旦在工作完成后没有释放,Java 应用就存在内存泄露的风险。
10)进程间如何通讯,线程间如何通讯?
这些是在没有涉猎,不懂。。。详细答案还是自行搜索。可以参考 进程间通信和线程间通信的几种方式
Linux进程间的通信方式:管道、有名管道、信号量、消息队列、共享内存、信号、socket
Windows进程间的通信方式:管道、信号量、消息队列、共享内存、socket
Linxu线程间的通信方式:互斥量、条件变量、信号量、信号
Windows线程间的通信方式:互斥量、信号量、事件(Event)、临界区(Critical Section)
java进程通信:,可以用管道或者互斥量、Socket、共享文件,共享内存、连接同一个数据库等详见提问java进程之间怎么通信
java线程通信:通信机制可以分为:共享内存,消息队列。方法分为这四种:共享变量、wait/notify机制、Lock/Condition机制、管道,详情请参考详谈java线程与线程、进程与进程间通信
共享就是一个内存区域的数据被多个处理器访问,伪共享就是不是真的共享,会引起最小的共享区域大小叫做cache line,大小为2的连续整数幂次方,一般为32-256字节;当多个CPU访问一个cache line大小的内存区域时,就会引起冲突,这种情况叫做共享,但是例如两个CPU要访问两个不同变量,而这两个不同变量在一个cache line大小的内存区域中时,从应用逻辑上来说,两个处理器没有共享,但是访问了同一块cache line,造成了事实上的共享,这种情况就叫做伪共享,详见多线程中的volatile和伪共享
同步发送一个请求,等待返回后继续操作,异步无需等待,随时都可以进行操作。最大区别,同步必须等待,异步无需等待,同步举例:TCP的三次握手;异步举例:发短信。详见同步和异步有何异同,什么场景使用,举例说明!
请参考ConcurrentHashMap源码学习笔记(jdk1.8)和HashMap源码学习笔记(jdk1.8)
实现分布式锁的三种方法: