Hashtable 与 hashMap 区别于两点:1.前者是线程同步的 2.是前者不允许出现null键和null值。尽而说到线程同步:
用什么关键字修饰同步方法 ? 用synchronized关键字修饰同步方法
同步有几种实现方法,都是什么?分别是synchronized,wait与notify
wait():使一个线程处于等待状态,并且释放所持有的对象的lock。
sleep():使一个正在运行的线程处于睡眠状态,是一个静态方法,调用此方法要捕捉InterruptedException异常。
notify():唤醒一个处于等待状态的线程,注意的是在调用此方法的时候,并不能确切的唤醒某一个等待状态的线程,而是由JVM确定唤醒哪个线程,而且不是按优先级。
Allnotity():唤醒所有处入等待状态的线程,注意并不是给所有唤醒线程一个对象的锁,而是让它们竞争。
实现同步的方式
同步是多线程中的重要概念。同步的使用可以保证在多线程运行的环境中,程序不会产生设计之外的错误结果。同步的实现方式有两种,同步方法和同步块,这两种方式都要用到synchronized关键字。
给一个方法增加synchronized修饰符之后就可以使它成为同步方法,这个方法可以是静态方法和非静态方法,但是不能是抽象类的抽象方法,也不能是接口中的接口方法。下面代码是一个同步方法的示例:
public synchronized void aMethod() {
// do something
}
public static synchronized void anotherMethod() {
// do something
}
线程在执行同步方法时是具有排它性的。当任意一个线程进入到一个对象的任意一个同步方法时,这个对象的所有同步方法都被锁定了,在此期间,其他任何线程都不能访问这个对象的任意一个同步方法,直到这个线程执行完它所调用的同步方法并从中退出,从而导致它释放了该对象的同步锁之后。在一个对象被某个线程锁定之后,其他线程是可以访问这个对象的所有非同步方法的。
同步块是通过锁定一个指定的对象,来对同步块中包含的代码进行同步;而同步方法是对这个方法块里的代码进行同步,而这种情况下锁定的对象就是同步方法所属的主体对象自身。如果这个方法是静态同步方法呢?那么线程锁定的就不是这个类的对象了,也不是这个类自身,而是这个类对应的java.lang.Class类型的对象。同步方法和同步块之间的相互制约只限于同一个对象之间,所以静态同步方法只受它所属类的其它静态同步方法的制约,而跟这个类的实例(对象)没有关系。
如果一个对象既有同步方法,又有同步块,那么当其中任意一个同步方法或者同步块被某个线程执行时,这个对象就被锁定了,其他线程无法在此时访问这个对象的同步方法,也不能执行同步块。
synchronized 关键字用于保护共享数据。请大家注意“共享数据”,你一定要分清哪些数据是共享数据,请看下面的例子:
public class ThreadTest implements Runnable{
public synchronized void run(){
for(int i=0;i<10;i++) {
System.out.print(" " + i);
}
}
public static void main(String[] args) {
Runnable r1 = new ThreadTest(); //也可写成ThreadTest r1 = new ThreadTest();
Runnable r2 = new ThreadTest();
Thread t1 = new Thread(r1);
Thread t2 = new Thread(r2);
t1.start();
t2.start();
}}
在这个程序中,run()虽然被加上了synchronized 关键字,但保护的不是共享数据。因为这个程序中的t1,t2 是两个对象(r1,r2)的线程。而不同的对象的数据是不同的,r1,r2 有各自的run()方法,所以输出结果无法预知。
synchronized的目的是使同一个对象的多个线程,在某个时刻只有其中的一个线程可以访问这个对象的synchronized 数据。每个对象都有一个“锁标志”,当这个对象的一个线程访问这个对象的某个synchronized 数据时,这个对象的所有被synchronized 修饰的数据将被上锁(因为“锁标志”被当前线程拿走了),只有当前线程访问完它要访问的synchronized 数据时,当前线程才会释放“锁标志”,这样同一个对象的其它线程才有机会访问synchronized 数据。
示例3:
public class ThreadTest implements Runnable{
public synchronized void run(){
for(int i=0;i<10;i++){
System.out.print(" " + i);
}
}
public static void main(String[] args){
Runnable r = new ThreadTest();
Thread t1 = new Thread(r);
Thread t2 = new Thread(r);
t1.start();
t2.start();
}}
如果你运行1000 次这个程序,它的输出结果也一定每次都是:0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9。因为这里的synchronized 保护的是共享数据。t1,t2 是同一个对象(r)的两个线程,当其中的一个线程(例如:t1)开始执行run()方法时,由于run()受synchronized保护,所以同一个对象的其他线程(t2)无法访问synchronized 方法(run 方法)。只有当t1执行完后t2 才有机会执行。
示例4:
public class ThreadTest implements Runnable{
public void run(){
synchronized(this){
for(int i=0;i<10;i++){
System.out.print(" " + i);
}} }
public static void main(String[] args){
Runnable r = new ThreadTest();
Thread t1 = new Thread(r);
Thread t2 = new Thread(r);
t1.start();
t2.start();
}}
这个程序与示例3 的运行结果一样。在可能的情况下,应该把保护范围缩到最小,可以用示例4 的形式,this 代表“这个对象”。没有必要把整个run()保护起来,run()中的代码只有一个for循环,所以只要保护for 循环就可以了。
示例5:
public class ThreadTest implements Runnable{
public void run(){
for(int k=0;k<5;k++){
System.out.println(Thread.currentThread().getName()+ " : for loop : " + k);
}
synchronized(this){
for(int k=0;k<5;k++) {
System.out.println(Thread.currentThread().getName()+ " : synchronized for loop : " + k);
}} }
public static void main(String[] args){
Runnable r = new ThreadTest();
Thread t1 = new Thread(r,"t1_name");
Thread t2 = new Thread(r,"t2_name");
t1.start();
t2.start();
} }
运行结果:
t1_name : for loop : 0
t1_name : for loop : 1
t1_name : for loop : 2
t2_name : for loop : 0
t1_name : for loop : 3
t2_name : for loop : 1
t1_name : for loop : 4
t2_name : for loop : 2
t1_name : synchronized for loop : 0
t2_name : for loop : 3
t1_name : synchronized for loop : 1
t2_name : for loop : 4
t1_name : synchronized for loop : 2
t1_name : synchronized for loop : 3
t1_name : synchronized for loop : 4
t2_name : synchronized for loop : 0
t2_name : synchronized for loop : 1
t2_name : synchronized for loop : 2
t2_name : synchronized for loop : 3
t2_name : synchronized for loop : 4
第一个for 循环没有受synchronized 保护。对于第一个for 循环,t1,t2 可以同时访问。运行结果表明t1 执行到了k=2 时,t2 开始执行了。t1 首先执行完了第一个for 循环,此时t2还没有执行完第一个for 循环(t2 刚执行到k=2)。t1 开始执行第二个for 循环,当t1的第二个for 循环执行到k=1 时,t2 的第一个for 循环执行完了。t2 想开始执行第二个for 循环,但由于t1 首先执行了第二个for 循环,这个对象的锁标志自然在t1 手中(synchronized 方法的执行权也就落到了t1 手中),在t1 没执行完第二个for 循环的时候,它是不会释放锁标志的。所以t2 必须等到t1 执行完第二个for 循环后,它才可以执行第二个for 循环。
分享到:
相关推荐
- HashMap 和 Hashtable 都实现了 Map 接口,HashMap 更快但不是线程安全的,而 Hashtable 是线程安全但较慢。WeakHashMap 则使用弱引用作为键,有助于防止内存泄漏。 - 在选择使用哪种数据结构时,需要考虑性能需求...
- **HashMap**: 非同步设计使得其在单线程环境下的性能优于`Hashtable`。然而,在多线程环境下,可以通过`Collections.synchronizeMap()`方法将`HashMap`转换成线程安全的版本。此外,从Java 5开始,推荐使用`...
HashMap线程不安全的原因主要在于以下几个方面: 1. 多线程环境下并发修改:在多线程环境下,如果多个线程同时对HashMap进行读写操作,可能会导致数据的不一致性和数据丢失。例如,当一个线程正在执行put操作时,另...
为了在多线程环境中安全地使用`HashMap`,开发者需要自己负责同步,例如使用`Collections.synchronizedMap(new HashMap,V>())`创建线程安全的`HashMap`实例。 #### 2. 允许null值 - **HashTable**: 不支持`null`键...
在Java编程语言中,`HashMap`和`HashTable`都是实现键值对存储的数据结构,但它们之间存在一些显著的区别,这些区别主要体现在线程安全性、性能、null值处理以及一些方法特性上。以下是对这两个类的详细分析: 1. ...
- **线程安全性**:`Vector`和`HashTable`是线程安全的,而`ArrayList`和`HashMap`不是。在多线程环境下,`ArrayList`和`HashMap`需要通过同步机制来保证数据一致性。 - **存储方式**:`List`接口的实现(如`...
不过,`HashMap` 的同步问题可以通过 `Collections.synchronizedMap()` 方法得到解决,该方法可以将 `HashMap` 包装成一个线程安全的 `Map` 实例。 #### 三、null 值处理 - **HashMap**:允许使用 `null` 键和 `...
如果多个线程同时访问一个`HashMap`实例,且至少有一个线程修改了该`HashMap`,则必须通过外部同步来保证线程安全。例如,可以通过将`HashMap`对象包装在一个`Collections.synchronizedMap()`返回的对象中来实现这...
- **HashMap**:由于没有同步操作,因此在单线程环境中通常比 `HashTable` 性能更好。 - **HashTable**:虽然提供了线程安全,但这也意味着每次调用方法时都会发生同步操作,这可能会导致性能下降。 4. **初始化...
这是因为它内部的方法都是同步化的(synchronized),这就意味着在多线程环境中使用`Hashtable`时无需额外的同步处理。 - **HashMap**:默认情况下,`HashMap`是非线程安全的。如果需要在一个多线程环境中使用`...
相比之下,`HashMap`是非线程安全的,它没有同步任何方法,因此在多线程环境中使用时,可能需要显式地添加同步控制,例如通过`Collections.synchronizedMap()`方法来创建一个线程安全的`Map`。 #### 3. 允许null...
- **线程安全性**:`HashMap`不是线程安全的,如果多个线程同时访问,必须在外部进行同步控制。 - **null的支持**:`HashMap`允许一个键为`null`,且允许多个值为`null`。 - **初始容量和加载因子**:`HashMap`同样...
HashMap是非同步的,意味着在多线程环境中,如果不进行适当的同步控制,可能会导致数据不一致。而HashTable是同步的,因此它在多线程环境下的安全性更高,但这也牺牲了性能。此外,HashMap允许键和值为null,而...
ConcurrentHashMap 是 Java 中的另一个线程安全的类,它与 HashTable 在线程同步上有什么不同?ConcurrentHashMap 使用了分段锁的机制,每个段都独立锁定,提高了并发性能。 HashMap 和 HashTable 的区别 1. 线程...
- 在单线程环境中,HashMap 比 Hashtable 快,因为没有内置的同步开销。 - 在多线程环境下,如果不需要全局同步,使用 ConcurrentHashMap 可以获得比 Hashtable 更好的性能。 6. 方法名称: - Hashtable 使用了 ...
HashMap 不支持线程的同步,即任一时刻可以有多个线程同时写 HashMap,可能会导致数据的不一致。如果需要同步,可以用 Collections 的 synchronizedMap 方法使 HashMap 具有同步的能力。 LinkedHashMap ...
#### 二、HashMap线程安全问题分析 在多线程环境中,`HashMap`的主要线程安全问题包括但不限于: 1. **链表死循环问题**:在JDK 1.7中,当多个线程同时进行`put`操作时,可能会出现链表死循环的情况,这是一个严重...
- `HashMap`由于不需要线程同步,通常在单线程环境中比`Hashtable`更快。 - 在多线程环境下,`Hashtable`的同步特性保证了数据一致性,但牺牲了效率。对于高并发场景,`HashMap`需要配合`synchronized`关键字或`...
由于同步机制的存在,Hashtable的性能相比HashMap较低,现在在多线程需求下,通常更推荐使用ConcurrentHashMap,它在并发性能上做了优化。此外,Hashtable不支持null键和null值。 TreeMap是一种基于红黑树的Map实现...
与HashMap相比,HashTable的同步特性使得它在多线程环境下更安全,但在单线程环境下,由于同步开销,其性能较低。 5. LinkedList与ArrayList的比较 LinkedList是List接口的另一个实现,它基于双向链表实现,对于在...