Showing posts with label java-se. Show all posts
Showing posts with label java-se. Show all posts

Saturday, October 10, 2020

Why does removing an element from a HashMap while iterating cause ConcurrentModificationException exception?

I'm reading about Java Concurrency from the book "OCP Oracle Certified Associate Java SE 8 Programmer II Study Guide  Exam 1Z0-809" by Boyarsky Jeanne, Selikoff Scott. It says that the code snippet below throws ConcurrentModificationException exception because it removed an element from the foodData hashmap while looping over its iterator. 

Map<String, Object> foodData = new HashMap<String, Object>();
foodData.put("penguin", 1);
foodData.put("flamingo", 2);

for(String key: foodData.keySet())
   foodData.remove(key);

It doesn't explain in details how this works, but it tells that the solution to this problem is using ConcurrentHashMap instead of HashMap. I found it interesting so I dug deeper.

At first, I wrote a program to run the code snippet above and I got the exception's stack trace below.

Exception in thread "main" java.util.ConcurrentModificationException
        at java.base/java.util.HashMap$HashIterator.nextNode(HashMap.java:1494)
        at java.base/java.util.HashMap$KeyIterator.next(HashMap.java:1517)
        at ConcurrentCollectionTest.main(ConcurrentCollectionTest.java:9)

It shows that the exception was thrown from within the HashIterator.nextNode() method, not the HashMap.remove() method as I thought. And below is the source code.

abstract class HashIterator {

Node<K,V> next;        // next entry to return
Node<K,V> current;     // current entry
int expectedModCount;  // for fast-fail
int index;             // current slot

HashIterator() {
expectedModCount = modCount;
Node<K,V>[] t = table;
current = next = null;
index = 0;
if (t != null && size > 0) { // advance to first entry
do {} while (index < t.length && (next = t[index++]) == null);
}
}

public final boolean hasNext() {
return next != null;
}

final Node<K,V> nextNode() {
Node<K,V>[] t;
Node<K,V> e = next;
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
if (e == null)
throw new NoSuchElementException();
if ((next = (current = e).next) == null && (t = table) != null) {
do {} while (index < t.length && (next = t[index++]) == null);
}
return e;
}
...
}

The exception is thrown if the number of hash map's elements before iterating is different from while iterating (modCount != expectedModCount). This technique is called fail-fast. Java does this to prevent problems such as memory consistency errors. If the element is modified by the current thread while iterating, it should be ok. But, it can be updated by other threads so it can produce unexpected result. 

The collection classes with "Concurrent" prefix, for example ConcurrentHashMap, does not throw the exception when an update (adding or removing element) happens during iteration. This has weaker consistency than fail-fast behavior, but it's thread-safe and provides concurrent read from and write to the collection object (the blocks of code to do update operations are synchronized). The concurrent collection classes should be used when multiple threads modify the collection object outside of synchronized block or method. The concurrent collections is an alternative way to synchronized collections, which require locking on the monitor of the entire collection object, resulting one thread must wait for another even if they're calling different (synchronized) methods. 

The synchronized collections can be created using the methods of Collections class. For example, the synchronized hashmap can be instantiated by invoking the Collections.synchronizedMap() method.

The concurrent collections are thread-safe because all of their methods ensure happens-before relationship between threads as mentioned in this documentation
The methods of all classes in java.util.concurrent and its subpackages extend these guarantees to higher-level synchronization. In particular:
  • Actions in a thread prior to placing an object into any concurrent collection happen-before actions subsequent to the access or removal of that element from the collection in another thread.
  • Actions in a thread prior to the submission of a Runnable to an Executor happen-before its execution begins. Similarly for Callables submitted to an ExecutorService.
  • Actions taken by the asynchronous computation represented by a Future happen-before actions subsequent to the retrieval of the result via Future.get() in another thread.
  • Actions prior to "releasing" synchronizer methods such as Lock.unlockSemaphore.release, and CountDownLatch.countDown happen-before actions subsequent to a successful "acquiring" method such as Lock.lockSemaphore.acquireCondition.await, and CountDownLatch.await on the same synchronizer object in another thread.
  • For each pair of threads that successfully exchange objects via an Exchanger, actions prior to the exchange() in each thread happen-before those subsequent to the corresponding exchange() in another thread.
  • Actions prior to calling CyclicBarrier.await and Phaser.awaitAdvance (as well as its variants) happen-before actions performed by the barrier action, and actions performed by the barrier action happen-before actions subsequent to a successful return from the corresponding await in other threads.

Happens-before relationship means one thread finishes its execution before another thread starts its execution so there is no memory consistency errors (or race condition).

Monday, April 18, 2016

Where is Java home on Mac OS X?

I'm using Mac OS X Lion v10.7 and I have downloaded Java 8 DMG file from Oracle's web site and installed it. After installing it, i can't find Java home directory. According to http://stackoverflow.com/questions/1348842/what-should-i-set-java-home-to-on-osx, there is an executable file that print Java home location to console by default, /usr/libexec/java_home.

> ls -l /usr/libexec/java_home
lrwxr-xr-x  1 root  wheel  79 Apr  7 17:03 /usr/libexec/java_home -> /System/Library/Frameworks/JavaVM.framework/Versions/Current/Commands/java_home
> /usr/libexec/java_home
/Library/Java/JavaVirtualMachines/jdk1.8.0_77.jdk/Contents/Home

And then you can set JAVA_HOME variable in /etc/profile file as following:
> export JAVA_HOME=$(/usr/libexec/java_home)


Tuesday, January 20, 2015

Ivy HelloWorld example


What is Ivy?

Ivy is additional set of Ant task created by Apache. It's packaged as JAR file and needs to be put in $ANT_HOME/lib, just like other third-party tasks. (To create your own task, you need to create a Java class that extends org.apache.tools.ant.Task)

Ivy contains a set of tasks (resolve, cachepath, retrieve, install, publish) which works like Apache Maven phases. For example, the resolve task downloads all dependencies from Maven's public repository and stores them in ivy cache (~/.ivy2/cache directory).

Why Ivy?

One of the reasons is you are familiar with Ant and you don't want to spend more time to learn Maven.




HelloWorld Example


We are going to show how to use Ivy to compile Java HelloWorld program.

1. Downloading Ivy
Suppose you already installed Ant and then you can download Ivy from http://ant.apache.org/ivy/download.cgi

2. Installing Ivy
cp apache-ivy-2.2.0/ivy-2.2.0.jar $ANT_HOME/lib
3. Creating HelloWorld.java

Suppose we are currently in $WORKSPACE directory then create the file in the directory $WORKSPACE/src/com/vathanakmao/ivytest/ as following:
package com.vathanakmao.ivytest;

import org.apache.commons.lang.StringUtils;

public class HelloWorld {

         public static void main(String[] args) {

                 String string = StringUtils.upperCase("Hello World!");

                 System.out.println(string);

         }

}

4. Creating build.xml file
<project name="test ivy" default="resolve"
                          xmlns:ivy="antlib:org.apache.ivy.ant">

        <target name="resolve" description="resolve dependencies with ivy">
                <ivy:resolve />
                <ivy:cachepath pathid="default.classpath"/>
        </target>

        <target name="compile" depends="resolve" description="Compile">
                <mkdir dir="build/classes" />
                <javac srcdir="src" destdir="build/classes">
                        <classpath refid="default.classpath" />
                </javac>
        </target>
</project>
The resolve task () will fetch the artifacts from Maven's public repository http://repo1.maven.org/maven2 and put them in cache (~/.ivy2/cache).

The cachepath task () creates an Ant path with the given ID default.classpath, pointing to ivy cache directory consisting of the resolved artifacts. Then, the javac task will look for the artifacts in the cache because we declare it as the classpath with refid.

5. Creating ivy.xml file
<?xml version="1.0" encoding="ISO-8859-1"?>
<ivy-module version="2.0"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:noNamespaceSchemaLocation="http://ant.apache.org/ivy/schemas/ivy.xsd">

    <info organisation="com/vathanakmao/ivytest" module="helloworld" status="integration"></info>

    <dependencies>
         <dependency org="commons-lang" name="commons-lang" rev="2.6" />
    </dependencies>
</ivy-module>

6. Compiling
ant compile



Saturday, January 17, 2015

Parsing ISO 8601-compliant String to java.util.Date

To parse a string representation of date in ISO 8601 format, for example, 2015-01-18T01:01:01Z, you can use javax.xml.bind.DatatypeConverter class (included in JDK since version 1.6 update 4). The class is part of JAXB API. Please see JAXB for more details.

Please see the code snippet below on how to use it.
System.out.println(DatatypeConverter.parseDateTime("2014-10-15T01:01:01Z").getTime());
The parseDateTime() method returns java.util.Calendar object.



Why not use java.text.SimpleDateFormat class?

The example below works the same.
SimpleDateFormat formatter = new SimplateDateFormat("yyyy-MM-dd'T'HH:mm:ssZ");
System.out.println(formatter.parse("2014-10-15T01:01:01Z").getTime());
But the ParseException will be thrown if the date string is in another ISO 8601 format, for example, "2014-10-15T01:01:01+07:00". DatatypeConverter still works for this format and other ISO 8601 formats.





Reference
https://jaxb.java.net/tutorial/section_1_1-Introduction.html#About JAXB
https://jaxb.java.net/guide/Which_JAXB_RI_is_included_in_which_JDK_.html

Monday, January 5, 2015

Extract text from a string using Java regular expression

In the example below, you can extract the URL path from a complete URL:

String regex = "\\A(http://|https://)(.[^/]*)(.+)";
String url = "http://example.com/portal/194/174120/products/1413876472.8028_14_o.jpg";
Matcher matcher = Pattern.compile(regex).matcher(url);
if (matcher.find()) {
 String contextPath = matcher.group(3);
 return contextPath;
}

and the result is:

/portal/194/174120/products/1413876472.8028_14_o.jpg



Reference:
http://www.vogella.com/tutorials/JavaRegularExpressions/article.html

Guessing file content type

You can get file content type from a URL as following:

String fileUrl = "http://example.com/myimage.jpg";
String contentType = URLConnection.guessContentTypeFromName(fileUrl);

and the result would be:

image/jpeg

There is another method to guess file content type from input stream instead, URLConnection.guessContentTypeFromStream(InputStream) but if the server does not return content type of the file in the response header when you access it, the method would return null. Guessing content type from file name still work in this case.


Saturday, February 16, 2013

Compiling and running Java program

Suppose we have Hello.java file in the folder D:/myprogram/com/example/ and the package of the Hello class is com.example.



Compiling

D:> javac myprogram\com\example\Hello.java
and then the Hello.class file will be created in the same folder as Hello.java.

If your program is using third-party libraries and they are in D:\myprogram\libs, then:
D:> javac -cp "D:\myprogram\libs" myprogram\com\example\Hello.java



Running

D:> java -cp "D:\myprogram" com.example.Hello

If there are third-party libraries in D:\myprogram\libs folder, then:
D:> java -cp "D:\myprogram\libs\*;D:\myprogram" com.example.Hello