HashMap对象的key和value均可以为null;
HashTable对象的key和value均不可以为null。
且两者的key值均不能重复,若添加key相同的键值对后,后面的value会自动覆盖前面的value,但是不会报错。
在引入多线程后,由于线程执行的异步性,会给系统造成混乱,特别是在急用临界资源的时候,比如多个线程急用同一台打印机,会使打印结果相互交织在一起,难于区分;当多个线程急用共享变量、表格、链表时,可能会导致数据处理出错,因此线程同步的任务就是使并发执行的各线程之间能够有效的共享资源和相互合作,从而使程序具有可重现性。
当线程并发执行时,由于资源共享和线程协作,会使线程之间存在以下两种制约关系:
间接相互制约:一个系统中的多个线程必然要共享某种系统资源,比如共享CPU、I/O设备等,所谓间接相互制约即源于这种资源共享,打印机就是最好的例子,线程A再使用打印机时,其他线程都要等待。
直接相互制约:这种制约主要是因为线程之间的合作,如有线程A将计算结果提供给线程B作进一步处理,那么线程B在线程A将数据送达之前都将处于阻塞状态。
间接相互制约可以称为互斥,直接相互制约可以称为同步。对于互斥可以这样理解,线程A和线程B互斥的访问某个资源时,则他们之间就一定会产生个顺序问题——要么线程A等待线程B操作完毕,要么线程B等待线程A操作完毕,这其实就是线程的同步了。
总结:
/**
* @author YuZhansheng
* @desc 编程实现:子线程运行10次后主线程再执行5次,交替执行三遍。
* @create 2019-03-08 19:44
*/
public class TestMutiThread {
public static void main(String[] args) {
final Bussiness bussiness = new Bussiness();
//子线程
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 3; i++) {
bussiness.subMethod();
}
}
}).start();
//主线程
for (int i = 0; i < 3; i++) {
bussiness.mainMethod();
}
}
static class Bussiness{
private boolean subFlag = true;
public synchronized void mainMethod(){
while (subFlag){
try {
wait();
}catch (InterruptedException e){
e.printStackTrace();
}
}
for (int i = 0; i < 5; i++) {
System.out.println(Thread.currentThread().getName() + ": main thread running loop count --" + i);
try {
Thread.sleep(1000);
}catch (InterruptedException e){
e.printStackTrace();
}
}
subFlag = true;
notify();
}
public synchronized void subMethod(){
while (!subFlag){
try {
wait();
}catch (InterruptedException e){
e.printStackTrace();
}
}
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName() + ": sub thread running loop count --" + i);
try {
Thread.sleep(1000);
}catch (InterruptedException e){
e.printStackTrace();
}
}
subFlag = false;
notify();
}
}
}
ThreadLocal是一个关于创建局部变量的类,通常情况下,我们创建的变量是可以被任何一个线程访问并修改的,而使用ThreadLocal创建的变量只能被当前线程访问,其他线程则无法访问和修改。ThreadLocal并不是为了解决线程安全问题,而是提供了一种将实例绑定到当前线程的机制,类似于隔离的效果,实际上自己在方法中new出来变量也能达到类似的效果。ThreadLocal和线程安全基本上不搭边,绑定上去的实例也不是多线程公用的,而是每个线程new一份,如果共用了,那就会引发线程安全问题。
既然每个访问 ThreadLocal 变量的线程都有自己的一个“本地”实例副本。一个可能的方案是 ThreadLocal 维护一个 Map,键是 Thread,值是它在该 Thread 内的实例。线程通过该 ThreadLocal 的 get() 方案获取实例时,只需要以线程为键,从 Map 中找出对应的实例即可。每个新线程访问该 ThreadLocal 时,需要向 Map 中添加一个映射,而每个线程结束时,应该清除该映射。
总结
简而言之,进程是程序运行和资源分配的基本单位,一个程序至少有一个进程,一个进程至少有一个线程.进程在执行过程中拥有独立的内存单元,而多个线程共享内存资源,减少切换次数,从而效率更高.线程是进程的一个实体,是cpu调度和分派的基本单位,是比进程更小的能独立运行的基本单位.同一进程中的多个线程之间可以并发执行。
通过实现java.lang.Runnable接口或者通过继承java.lang.Thread类.相比继承Thread类,实现Runnable接口可能更优.原因有二:
Java不支持多继承.因此继承Thread类就代表这个子类不能再继承其他类了.而实现Runnable接口的类还可能扩展另一个类.
类可能只要求可执行即可,因此继承整个Thread类的开销过大。
线程的五种状态:创建、就绪、运行、阻塞和死亡。
第一是创建状态。在生成线程对象,并没有调用该对象的start方法,这是线程处于创建状态。
第二是就绪状态。当调用了线程对象的start方法之后,该线程就进入了就绪状态,但是此时线程调度程序还没有把该线程设置为当前线程,此时处于就绪状态。在线程运行之后,从等待或者睡眠中回来之后,也会处于就绪状态。
第三是运行状态。线程调度程序将处于就绪状态的线程设置为当前线程,此时线程就进入了运行状态,开始运行run函数当中的代码。
第四是阻塞状态。线程正在运行的时候,被暂停,通常是为了等待某个时间的发生(比如说某项资源就绪)之后再继续运行。sleep,suspend,wait等方法都可以导致线程阻塞。
第五是死亡状态。如果一个线程的run方法执行结束或者调用stop方法后,该线程就会死亡。对于已经死亡的线程,无法再使用start方法令其进入就绪。
sleep方法来自Thread类,wait方法来自Object类,调用sleep()方法的过程中,线程不会释放对象锁,而调用wait()方法的时候,线程会释放对象锁。sleep()睡眠后不出让系统资源,wait()方法会让出系统资源,可以让其他线程占用CPU;sleep()里面可以传入一个参数来指定睡眠时间,时间一到就会自动唤醒,而wait()需要配合notify()或者notifyAll()使用。
创建线程要花费昂贵的资源和时间,如果任务来了才创建线程那么响应时间会变长,而且一个进程能创建的线程数有限。为了避免这些问题,在程序启动的时候就创建若干线程来响应处理,它们被称为线程池,里面的线程叫工作线程。从JDK1.5开始,Java API提供了Executor框架让你可以创建不同的线程池。比如单线程池,每次处理一个任务;数目固定的线程池或者是缓存线程池(一个适合很多生存期短的任务的程序的可扩展线程池)。
同步集合与并发集合都为多线程和并发提供了合适的线程安全的集合,不过并发集合的可扩展性更高。在Java1.5之前程序员们只有同步集合来用且在多线程并发的时候会导致争用,阻碍了系统的扩展性。Java5介绍了并发集合像ConcurrentHashMap,不仅提供线程安全还用锁分离和内部分区等现代技术提高了可扩展性。