C (/ ˌ s iː ˌ p l ʌ s ˈ p l ʌ s /) is a general-purpose programming language created by Bjarne Stroustrup as an extension of the C programming language, or 'C with Classes'.The language has expanded significantly over time, and modern C now has object-oriented, generic, and functional features in addition to facilities for low-level memory manipulation. It is almost always. This service will translate the code for you, just start typing the code or upload a file to convert it. Supports converting code from VB.NET to C#, from C# to VB.NET, from C# to TypeScript and from VB.NET to TypeScript and Java to all others.
Douglas C. Schmidtpjain@dre.vanderbilt.edu and schmidt@dre.vanderbilt.edu
Department of Computer Science
Washington University, St Louis
This work is supported in part by a grant from Siemens MedicalEngineering, Erlangen, Germany. The article appeared in the January1997 C++ Report magazine. Java has improvedquite a bit over the years and many of the problems identified in thisarticle have been fixed in later versions of Java.
1. Introduction
Over the past year, the Javaprogramming language has sparked considerable interest among softwaredevelopers. Its popularity stems from its flexibility, portability,and relative simplicity (compared with other object-orientedprogramming languages). Many organizations are currently evaluatinghow to integrate Java effectively into their software developmentprocesses.This article presents our experiences applying the Java language andits run-time features to convert a large C++ communication softwareframework to Java. We describe the key benefits and limitations ofJava we found while converting our C++ framework to Java. Some ofJava's limitations arise from differences between language constructsin C++ and Java. Other limitations are due to weaknesses with Javaitself. The article explains various techniques we adopted toworkaround Java's limitations and to mask the differences between Javaand C++. If you're converting programs written in C++ to Java (ormore likely, if you're converting programmers trained in C++ to Java),you'll invariably face the same issues that we did. Addition insightson this topic are available from Taligent.
1.1. Background
Java is commonly associated with writing applets for World Wide Web(WWW) browsers like NetscapeNavigator and InternetExplorer. However, Java is more than a language for writing WWWapplets. For instance, Java's Abstract Window Toolkit (AWT) is a GUIframework that provides the core functionality for developingplatform-independent client applications. [AWT:96]. In addition, Java's native support formulti-threading and networking makes it attractive for buildingconcurrent and distributed server applications.The Java team at Sun Microsystems intentionally designed the syntax ofJava to resemble C++. Their goal was to make it easy for C++developers to learn Java. Despite some minor differences (e.g., newkeywords for inheritance and RTTI), C++ programmers will be able to``parse' Java syntax easily. It took us about a day to feelcomfortable reading and writing simple Java examples.
Understanding the syntactic constructs of the language, however, isjust one aspect of learning Java. To write non-trivial programs, C++developers must understand Java's semantics (e.g.,copy-by-reference assignment of non-primitive types and type-safeexception handling), language features (e.g.,multi-threading, interfaces, and garbage collection), andstandard library components (e.g., the AWT andsockets). Moreover, to truly master Java requires a thoroughunderstanding of its idioms and patterns [Lea:96], many of which are different from thosefound in C++ [Coplien:92].
1.2. Converting from C++ to Java
We believe the best way to master Java (or any other programminglanguage) is to write substantial applications and reusablecomponents. Therefore, the material in this article is based on ourexperiences converting the ACE framework [Schmidt:94] from C++ toJava.ACE is a freely available object-oriented network programmingframework that makes considerable use of advanced C++ features (liketemplates and pointers-to-methods) and operating systems features(like multi-threading, shared memory, dynamic linking, andinterprocess communication). The following figure illustrates thearchitecture of the C++ version of ACE:Figure 1. The C++ ACE Architecture |
The ACE source code release contains over 85,000 lines of C++. ACEhas been ported to Win32, most versions of UNIX, VxWorks, and MVSOpenEdition. Approximately 9,000 lines of code (i.e., about 10% ofthe total toolkit) are devoted to the OS AdaptationLayer, which shields the higher layers of ACE from OSplatform dependencies.
As part of a project with the Siemens Medical Engineering group inErlangen, Germany, we've converted most of ACE from C++ to Java. Theprimary motivation for converting ACE to Java was to enhance Java'sbuilt-in support for networking and concurrent programmingfunctionality. Many ACE components (such as Task, Timer Queue, ThreadManager, and ServiceConfigurator), along with traditional concurrency primitives (suchas Semaphore,Mutex, and Barrier) wereconverted from C++ to the Java version of ACE. These ACE componentsprovide a rich set of constructs layered atop of Java's existinglibraries. In addition, the Java version of ACE includesimplementation of several distributedservices (such as a Time Service, Naming Service, and LoggingService).
The following figure illustrates the architecture of the Java versionof ACE:
Figure 2. The Java ACE Architecture |
The Java version of ACE shown in Figure 2 is smaller than the C++version of ACE shown in Figure 1. There are several reasons for thereduction in size and scope:
- Portability -- The Java Virtual Machine (JVM)provides cross-platform portability. This eliminates the need for theOS Adaptation Layer that encapsulates the different C language OS APIsin the C++ version of ACE.
- API Parsimony -- The Java run-time interface ismuch ``leaner' than UNIX and Win32 intefaces. For instance, the Javarun-time interface omits many of the interprocess communicationmechanisms (like Named Pipes, STREAM Pipes, TLI, and System V IPC) andsingle-threaded event demultiplexing mechanisms (like
select
,poll
, andWaitForMultipleObjects
) commonly found on UNIX and Win32.In addition, since Java doesn't support shared memory or memory-mappedfiles, the memory management wrappers (such asMem_Map
andShared_Malloc
) were omitted from Java ACE.
1.3. Topics Covered in this Article
The following is a synopsis of the topics covered in this article:- The benefits of Java compared to C++ --
Over the past decade, C++ has been our language of choice for buildinghigh-performance communication frameworks. A major strength of C++ isits efficient run-time performance and multi-paradigm support forobject-oriented programming and generic programming. However, afterboth teaching and applying C++ extensively in practice, we recognizethat it can be a complex language to learn and use. In ourexperience, a major source of C++'s complexity arises from explicitmemory management and lack of portability across compilers and OSplatforms. Not surprisingly, therefore, we found some of the mostimportant benefits of using Java were its garbage collection andstandard libraries. These features enabled us to develop portableJava applications rapidly without expending considerable efforttracking down memory management errors and wrestling with brokencompilers and incompatible development environments.
- Limitations of Java compared to C++ (and workarounds) --
While the syntax of Java resembles C++, certain features, semantics,and patterns are quite different. For instance, Java lacks manyfeatures commonly used in C++ programs. Chief among these aretemplates, pointers-to-methods, explicit parameter passing modes,enumerated types, and operator overloading.
While the omission of these features simplifies the Java language, italso affects the way that developers fluent in C++ can design theirprograms. Converting C++ code to Java, while keeping the samefunctionality, can be tedious due to the absence of some C++ languageconstructs. In particular, we found that using Java oftensignificantly increased the number of classes that we wrote comparedto C++, where we would have employed templates to factor out commoncode.
We recognize that omitting C++ constructs doesn't necessarily makeJava a less powerful language. In fact, we found that rethinking ouroriginal design of ACE using Java often solved the problem aseffectively as by using C++. Many of the limitations we describeoccurred because we were converting a large framework designed toexploit C++ features. Had we designed ACE as a Java framework fromscratch, some of these limitations wouldn't have been problematic.
We expect that many other C++ developers are, or will soon be,programming in Java. We wrote this article to help capture anddocument how we used Java effectively for systems-level programming.Therefore, in addition to outlining Java's limitations, this articledescribes the techniques we used to workaround the lack of C++features like templates and pointers-to-methods.
2. Key Benefits of Java
The Java programming language provides a rich set of language featuresand reusable components. The following are the key benefits of Javawe identified in converting ACE from C++ to Java:2.1. Simplicity
One of the primary goals of the Java design team was to keep thelanguage simple and easy to learn. This was done by making it looksimilar to C++, but by omitting many C++ constructs. For example,several error-prone aspects of C++ (such as pointers and operatoroverloading) have been deliberately omitted from Java to make itsmaller, easier to learn, and easier to use. We found Java to be arelatively concise language that offers the benefits ofobject-oriented programming, plus a useful set of standard librarycomponents.Learning Java and using it to convert ACE from C++ to Java was arelatively smooth process for us. It only took a few days to convertmajor portions of ACE from C++ to Java. Since many Java constructsare similar to C++, converting some of ACE to Java was mostly a matterof mapping the C++ constructs to their Java counterparts. Having donethis, it was relatively easy to build the higher-layer ACEcommunication services and example applications using Java.
For example, it took us less than half a day to reimplement the ACE distributed Time Service using the Java version of ACE. Ofcourse, one reason for our productivity was that we'd already figuredout how to design and implement the distributed Time Service in C++.However, the simplicity of Java also contributed significantly to thisrapid development cycle.
2.2. Portability
One of the most time consuming aspects of implementing the C++ versionof ACE has been porting it to dozens of OS platforms and C++compilers. This is a challenging task since not only must weencapsulate platform-specific system calls, but we must wrestle withmany broken C++ compilers (templates and exception handling areparticularly problematic in our experience).In contrast, Java offers the promise of platform portability. This,of course, is due to the fact that Java is more than just a language-- it defines a Virtual Machine (the JVM) on which programsimplemented in the language will execute. Therefore, unlike C++(which generally treats platform issues outside the scope of thelanguage definition), the Java programming language can make certainassumptions about the environment in which it runs. This is one ofthe key factors that changes the idioms and patterns used by Javaprogrammers.
Java programs now run on many OS platforms (such as Solaris, WindowsNT, Windows '95, and OS/2) without requiring major modifications.However, as new Java development environments are released bydifferent vendors (who add their own bugs and extensions), it may behard to maintain such a high degree of portability. In addition,since Java doesn't support preprocessors or conditional compilationit's unclear how to encapsulate the inevitable platform differencesthat may arise in practice.
2.3. Built-in Threading and Networking Support
Java's built-in support for threading and networking was crucial toour conversion of ACE from C++ to Java. Although the Java environmentalso provides standard support for other common programming mechanisms(such as event-driven user-interfaces), we concentrate on Java'sthreading and socket mechanisms in this article since our focus is onconverting communication software frameworks from C++ toJava.The java.net package and java.lang.Thread class provide useful classes for creatingconcurrent and distributed client/server applications. Using theseclasses simplifies client/server application development because theJava wrappers shield programmers from tedious and error-pronethreading and socket-level details. For example, the following codeshows how a simple ``thread-per-connection' server application can bewritten in Java: Note the relatively few lines of code required to write a simpleconcurrent server. Moreover, note how easily the server can bemulti-threaded by starting each ServiceHandler
in itsown thread of control. Although the C++ version of ACE providesequivalent functionality and parsimony, it's harder to use featureslike exception handling, sockets, and threading portably acrossmultiple OS platforms.
The following code illustrates how a Java client application can bewritten to communicate with the server: The Java wrappers for sockets play a similar role as the C++ socketwrappers in ACE. They both provide a uniform interface thatsimplifies communication software implementations. In our conversionof ACE from C++ to Java, it was trivial to use the Java socketwrappers to provide a communication interface equivalent to the C++version of ACE.
2.4. Standard Libraries
The Java development environment provides several useful collectionsin the form of interfaces and classes. These are contained in thejava.util
package and include Enumeration
,Vector
, Stack
, Hashtable
,BitSet
, and Dictionary
. Providing genericcollections as part of the standard development environment simpliesapplication programming. For example, in the C++ version of ACE,we've implemented reusable components (such as theMap_Manager
and the Unbounded_Set
) tosimplify the development of higher-level ACE components. In contrast to Java, the standard components we used in the C++version of ACE were developed from scratch since they didn't exist asin all our C++ development environments. Although the ANSI/ISO draftstandard is nearing completion, most C++ compilers still don't providestandard libraries that are portable across platforms. Therefore,programmers must develop these libraries, port them from public domainlibraries (such as GNU libg++ and HP's STL), or purchase themseparately from vendors like RogueWave and Object Space.
For instance, to build a portable application in C++ that uses dynamicarrays, developers must either buy, borrow, implement, and/or port adynamic array class (such as the STL vector
). In thecase of Java, developers can simply use the Vector
classprovided in the development environment without concern forportability. The ubiquity of Java libraries is particularly importantfor WWW applets because the standard Java components can bepre-configured into browsers to reduce network traffic. In addition,the unified strategies provided by the Java standard libraries (suchas iteration, streaming,and externalization) provide idioms that Java programmers can easilyrecognize, utilize, and extend.
2.5. Garbage Collection and Range Checking
The Java run-time system performs garbage collection and rangechecking on pointer and array access. This eliminates a common sourceof errors in C and C++ programs. In Java, programmers don't need toexplicitly free memory resources acquired dynamically. Objects arefreed by the garbage collector when they are no longer referenced.This eases common programming traps and pitfalls associated withdangling references or memory leaks. [Editor's note: Robert Martin'sarticle in this issue on ``Java and C++: A Critical Comparison'discusses some drawbacks with Java's garbage collection model.]There are tools (such as Purify, Bounds Checker, and Great Circle)that reduce the effort of writing memory-safe C++ code. However, it'sbeen our experience that even though these tools exist, C/C++programmers typically expend considerable effort avoiding memory leaksand other forms of memory corruption.
2.6. Type-safe Exceptions
Exceptions provide a clean way to handle error conditions withoutcluttering the ``normal' code path. Java provides an elegantexception handling model that includes both checkedexceptions and unchecked exceptions. Checked exceptionsallow the compiler to verify at compile-time that methods handlepotential exceptions. This allows Java to ensure that all checkedexceptions are handled. In addition, Java supports uncheckedexceptions, which allows developers to handle run-time exceptions anderrors.We have consciously avoided the use of exceptions in the C++ versionof ACE due to portability problems with C++ compilers. In contrast,when converting the ACE framework from C++ to Java, we were able tomake extensive use of Java's exception handling mechanisms. Theability to use exception handling significantly improved the clarityof the Java ACE error-handling logic, relative to the C++ version ofACE.
Although Java exceptions are elegant, they also exact a performancepenalty. Depending upon how heavily exception handling is used, theimpact on performance can vary significantly. As mentioned earlier,performance measurements of the Java version of ACE will be covered ina subsequent article.
2.7. Loading Classes Dynamically
The Java run-time system loads classes ``on-demand' as they areneeded. Moreover, Java can load classes both from the file system, aswell as over the network, which is a powerful feature. In addition,Java provides programmers with the hooks to load classes explicitlyvia thejava.lang.ClassLoader
mechanism.The java.lang.ClassLoader
is an abstract class thatdefines the necessary hooks for Java to load classes over the networkor from other sources such as the file system. This class made iteasy to implement the ServiceConfigurator framework in ACE. The ACE Service Configuratorframework provides a flexible architecture that simplifies thedevelopment, configuration, and reconfiguration of communicationservices. It helps to decouple the behavior of these communicationservices from the point in time at which these services are configuredinto an application.
Implementing the Service Configurator framework in the C++ version ofACE is challenging because C++ doesn't define a standard mechanism fordynamic linking/loading of classes and objects. Implementing thisfunctionality across platforms, therefore, requires variousnon-portable mechanisms (such as OS support for explicit dynamiclinking). Moreover, since the draft ISO/ANSI C++ standard doesn'taddress dynamic linking, C++ compilers and run-time systems are notrequired to support dynamic linking. For instance, many operatingsystems will not call the constructors of static C++ objects linked indynamically from shared libraries.
The fact that Java provides standard mechanisms for dynamiclinking/loading of classes significantly simplified the implementationof the ACE Service Configurator framework. This reiterates the factthat Java is more than just a programming language. It defines arun-time environment, and can therefore make certain assumptions aboutthe environment in which it runs. Thus, unlike other languages suchas C and C++, the Java run-time environment can portably supportimportant run-time features such as explicit dynamiclinking/loading.
2.8. Using JavaDoc for Documentation
TheJavaDoc
tool generates API documentation in HTMLformat for the specified package or for individual Java source filesspecified on the command line. Using JavaDoc is straightforward. Thesyntax /** documentation */
indicates a documentationcomment (a.k.a., a ``doc comment') and is used by theJavaDoc
tool to automatically generate HTMLdocumentation. In addition, doc comments may contain special tagsthat begin with the @
character. These tags are used byJavaDoc for additional formatting when generating documentation. Forexample, the tag @param
can be used to specify aparameter of a method. JavaDoc extracts all such entries containingthe @param
tag specified inside a doc comment of a methodand generates HTML documentation specifying the complete parameterlist of that method. The C++ version of ACE also provides automatic generation ofdocumentation using a modified version of the freely available OSEtools. The ACE documentation tools produce UNIX-style man pages (innroff format), as well as JavaDoc-style documentation (in HTMLformat).
3. Key Limitations of Java and Our Workarounds
This section describes key limitations with Java we identified whenconverting ACE from C++ to Java. We've ordered our discussion in theorder of the impact that each limitation had on the conversion of ACEfrom C++ to Java.Note that some of the limitations arose because we were converting anexisting C++ framework to Java. Had we originally developed ACE fromscratch using Java, many problems we encountered, and which wedescribe here as the ``limitations of Java,' would have beennon-issues. However, we suspect that when C++ programmers learn Java,or first convert code written in C++ to Java, they are likely to facethe same issues that we did. Therefore, in addition to outlining thelimitations with Java, this section describes the workarounds weapplied to alleviate the limitations.
3.1. Lack of Templates
The version of Java available to us (version 1.0.2) does not supporttemplates. This was a significant problem when converting ACE fromC++ to Java since ACE uses templates extensively. Templates are usedin ACE for two purposes:- Common code factoring -- A typical use of templates in ACE is to avoid tedious recoding of algorithms and data structures that differ only by their types. For instance, templates factor out common code for programming abstractionssuch as the
ACE_Map_Manager
andACE_Malloc
classes.One workaround for Java's lack of templates is to use
Object
-based containers like the Smalltalk-style collections available from Doug Lea or the Java Generic Library (which is a conversion of the Standard Template Library from C++ to Java) available from Object Space. [Editors note: Graham Glass' STL in Action column in this issue describes the design of the Java Generic Library.]These solutions are not entirely ideal, however, since they require application programmers to insert casts into their code. Although Java casts are strongly-typed (which eliminates a common source of errors in C and C++ programs that use traditional untyped casts), it is hard to optimize away the overhead of run-time type checking.
- Signature-based type conformance -- Another use of templates in ACE is to implement signature-based type conformance. For instance, the
ACE_Acceptor
andACE_Connector
are parameterized with a class that must conform to theACE_Svc_Handler
interface. AnACE_Svc_Handler
is created and initialized when a connection is established either actively or passively. It is an abstract class that applications can subclass to provide a concrete service handler implementation. To parameterize theACE_Acceptor
with anACE_Svc_Handler
, (e.g.,HTTP_Svc_Handler
), we would do the following in C++ ACE:The C++ code parameterizes theACE_Acceptor
staticallywith theHTTP_Svc_Handler
. The advantage of usingtemplates is that there's no run-time function call overhead. Anotheradvantage is the ability to parameterize locking mechanisms. Forinstance, the C++ version of ACE uses templates to select anappropriate synchronization strategy (e.g., mutexes vs. readers/writerlocks), as well as to remove all locking overhead when there is noconcurrency.Although Java lacks templates, it contain some interesting featuresthat enabled us to support signature-based type conformance by using apattern based on its meta-class facilities. For instance, here's howthe Java ACE
Acceptor
is defined: To achieve the C++ ACE behavior in Java ACE, a Class object can becreated using the class name ``HTTPSvcHandler' and this can then bepassed to the constructor ofAcceptor
, as follows: Once theacceptor
object is initialized to listen on awell-known port, theaccept
method will acceptconnections and createHTTPSvcHandler
objects tocommunicate with clients.The Java code uses the
Class
object created using thestring corresponding to the name of theSvcHandler
factory as a parameter to the constructor ofAcceptor
.The Java ACEAcceptor
uses this to create a new instanceof theSvcHandler
when needed. As long asHTTPSvcHandler
is a subclass ofSvcHandler
,a newClass
object can be created and passed to theAcceptor. Therefore, the signature will match that expected by theAcceptor
factory.
3.2. Lack of Enumerated Types
There are three common uses for enumerated types in C++: valueabstraction, type safety, and sequentialdeclarations (for use with arrays and switch statements). In C++,enumerated types are strongly typed. That is, unless you use anexplicit cast, the C++ compiler won't allow you to assign instances ofdifferent enumerated types to each other, nor can you assign integralvalues to instances of an enumerated type. In addition, enumeratedtypes convert automatically to their integral values, which iscommonly used for efficient table-based dispatching ofpointers-to-methods.In Java, there are no enumerated types. This typically isn't aproblem when developing new code, or when writing in an orthodox``object-oriented' style, because subclasses and the JavainstanceOf
typesafe dynamic cast feature can be used inplace of enumerals (as illustrated below).However, there were several situations where lack of enumerated typeswas a problem when converting ACE from C++ to Java:
- Lack of abstraction and type-safety -- Since Java doesn'tprovide enumerated types, programmers need to use primitive types(such as
int
) instead. For example, translating thefollowing C++ ACE code:into Java code is tedious and error-prone. The result looks like this:Not only is this less concise, but it is also more error-prone becauseany value of typeint
can be accidentally passed to theNamingMsgBlock
constructor. Moreover, enumeral valuescan be duplicated accidentally. In contrast, the C++ type-systemensures that onlyNamingMsgType
parameters are passedas arguments to the constructor ofACE_NamingMsgBlock
.One workaround in Java for the lack of enumerated types is to usesubclassing. In this approach, a base class called
NamingMsgType
is defined and a subclass ofNamingMsgType
is created for each type ofNamingMsgType
. The following code illustrates thiscommon Java pattern:Now we can ensure that an argument of typeNamingMsgType
is passed to the constructor ofNamingMsgBlock
. Here's how we can do a ``switch' todetermine the type of the message: - Lack of efficient dispatching -- Although the Javasubclassing pattern solves the problem of type-safety, Java's lack ofenumerated types precludes the common C/C++ pattern of using enums asindices into arrays for efficient method dispatching. For instance,the C++
ACE_Name_Handler
implementation dispatches the appropriate method by using the messagetype to index into a table of pointers to C++ methods. The followingcode demonstrates how a table of pointers to methods can be created todispatch efficiently based on enumNaming_Msg_Type
literals (to simplify the example, some ACE C++ class names have beenchanged): Our dispatch routine is straightforward:Building this type of efficient dispatch mechanism in Java requiresthe use of a primitive type likeint
for the messagetype, which incurs the drawbacks described above. A workaround thisproblem is presented in Section 3.3, alongwith a workaround for Java's lack of pointers to methods.
3.3. Lack of Pointers to Methods
As the preceding example demonstrated, the lack of enums in Javaprecluded us from doing efficient dispatching without using primitiveintegral types. Not only does Java lack enums, however, it also lackspointers to methods. Pointers to methods are widely used in GUIframeworks and other event-based systems that demultiplex individualmethods based on the arrival of messages.A common workaround for Java's lack of pointers to methods is to buildcallback objects via subclassing [Lea:96]. Thiscan be tedious, however, as shown by the following Java rewrite of theC++ ACE_Name_Handler
class presented earlier. Theexample below also illustrates another solution to Java's lack ofenumerated types:
Here is how we can define the NameHandler
class. Thisexample first uses the class NamingMsgType
to emulate thefunctionality of enums, as well as to provide abstraction andtype-safety.The following NameHandler
class uses aVector
to keep instances of callback objects and uses thedispatch
routine to extract the right instance of thecallback object:
Note that if the values of the NamingMsgType
``enumeration' weren't contiguous, we could use the JavaHashTable
rather than a Vector
.
3.4. Lack of ``Conventional' Concurrency Mechanisms
Java is a multi-threaded language with built-in language support forthreads. Thejava.lang.Thread
class contains methods forcreating, controlling, and synchronizing Java threads. The mainsynchronization mechanism in Java is based on Dijkstra-styleMonitors. Monitors are a relatively simple and expressivemodel that allow threads to (1) implicitly serialize their executionat method-call interfaces and (2) to coordinate their activities viaexplicit wait
, notify
, andnotifyAll
operations. While Java's simplicity is often beneficial, we encountered subtletraps and pitfalls with its concurrency model [Cargill:96]. In particular, Java'sMonitor-based concurrency model can be non-intuitive and error-pronefor programmers accustomed to developing applications using threadingmodels (such as POSIX Pthreads, Solaris threads, or Win32 threads)supported by modern operating systems. These threading models providea lower-level, yet often more flexible and efficient, set ofconcurrency primitives such as mutexes, semaphores, conditionvariables, readers/writer locks, and barriers.
In general, Java presents a different paradigm for multi-threadedprogramming. We found that this paradigm was initially non-intuitivesince we were accustomed to conventional lower-level multi-threadedprogramming mechanisms such as mutexes, conditionvariables, and semaphores. As a result, we had to rethink many ofthe concurrency control and optimization patterns used in C++ ACE. Wefound that converting C++ ACE code that used these conventionalsynchronization mechanisms required careful analysis and often changedthe implementation of C++ ACE components. This was due to differencesin Java concurrency mechanisms, compared with conventional POSIXPthreads-like threading mechanisms used in C++ ACE. The followingdiscussion explores some threading challenges we faced when convertingC++ ACE to Java:
- Ambiguous timeout semantics in the
wait
method --To implement Monitors, Java defines thewait
,notify
, andnotifyAll
methods in classObject
. Thus, all classes implicitly inheritthese methods. Thewait
method allows a thread to waitfor a condition to occur on an object, whereas thenotify
andnotifyAll
methods allow a thread to signal otherwaiting threads when the condition associated with an object becomestrue.There are three forms of
wait
: Using the first form ofwait
, a thread performs ablocking wait on an object until it is notified by anotherthread. Using the other two forms ofwait
, an objectblocks until it is notified or the timeout expires. Timeouts are veryuseful when writing robust protocols that won't block indefinitely ifpeers misbehave.Unfortunately, Java doesn't directly inform applications whether the
wait
call returned because the object was notified orbecause a timeout occurred. This differs from the C++ version of ACE,which uses the condition variable mechanism in the underlying OSthreading packages. In C++ ACE, theACE_Condition::wait
method returns a value that explicitly disambiguates between timeoutsand notifications.Java's lack of an explicit return value or exception to differentiatebetween notification and timeout increases the responsibilities ofapplication programmers. For example, when converting the thread-safe
ACE_Message_Queue
to Java, we first tried to implement the methodenqueue
as follows:The solution above won't work in Java since the Javawait
method doesn't distinguish between returns due to timeoutsvs. notifications. In particular, thewait
call mightreturn because of anotifyAll
or because it wasinterrupted, rather than because it timed out.Our first solution to the ambiguity inherent in Java's timed
wait
used an algorithm presented by Doug Lea in [Lea:96]. This algorithm involves explicitlydetermining if the timeout has occurred, as follows:Although this algorithm solved the problem in this case, we wanted togeneralize the solution. Our goal was to avoid replicating commoncode inenqueue
anddequeue
and to create areusableTimedWait
class utility. However, this turnedout to require very careful thought. The problem is that theifFull
condition logic must be factored out of theenqueue
method.As usual, it's ``patterns to the rescue.' We'll start by using theTemplate Method pattern [GoF:95] to implement ageneric
TimedWait
abstraction:We use the Template Method pattern to (1) define the skeleton of thealgorithm that computes the timeout in thetimedWait
method and (2) defer the definition of thecondition
hookmethod to subclasses. Thus, subclasses can redefine thecondition
logic without changing thetimedWait
algorithm.Although our
TimedWait
class works for simple usecases,we can't directly extendMessageQueue
fromTimedWait
sinceTimedWait
only allows us tospecify one condition at a time (i.e., it only has a singlecondition
hook). This is overly restrictive for theMessageQueue
implementation since itsenqueue
anddequeue
methods depend upon twoconditions:!isFull()
and!isEmpty()
,respectively.Once again, it's patterns to the rescue. In this case, we cangeneralize the
TimedWait
solution by applying anotherpattern -- a variant of the Delegated Notification pattern from [Lea:96] that we callBorrowedMonitor
. By using the Borrowed Monitor pattern, thecondition
hook of theTimedWait
classdelegates to the appropriate implementation of theMessageQueue
'sisFull
orisEmpty
methods, as follows:Finally, we can put all the patterns and classes together to create aMessageQueue
that uses the timedNot{Full|Empty}Condition
classes defined above: This solution allows us to perform the timeouts withoutreimplementing the low-level time-out logic in theenqueue
anddequeue
methods.It's important to note that our
TimedWait
scheme onlyprovides approximate timeout granularity. In particular, the timeoutis really a lower bound since theenqueue
anddequeue
methods may not return immediately when a timeoutoccurs if they encounter contention when trying to reacquire themonitor lock. Moreover, in theory, the scheme doesn't even guaranteeliveness because a thread may block indefinitely waiting to reclaimthe monitor lock. This is extremely unlikely to be a problemin practice, however.It is worthwhile to point out that all these classes and patternswould be unnecessary if Java simply differentiated between timeoutsand ``normal' notifications in the first place! Unfortunately, thecurrent semantics of Java's
wait(long timeout)
methodmakes this impossible. - Error-prone nested monitor semantics --If you look closely at the definition of
TimedWait
timedWait
method, you'll notice that it is notadorned with thesynchronized
keyword. This omission isnecessary to avoid ``nested monitor lockout,' which is surprisinglycommon in Java.The nested monitor problem occurs when a thread acquires objectX's monitor lock, e.g.,
TimedWait
, withoutrelinquishing the lock already held on monitor Y, e.g.,MessageQueue
, thereby preventing a second thread fromacquiring the monitor lock for Y. This can lead to a lockoutoccurring since after acquiring monitor X, the first thread maywait for a condition to become true that can only change as a resultof actions by the second thread after it has acquired monitorY. Naturally, this can't happen as long as the firstthread holds X's monitor lock...The following example is based on an example in [Lea:96] and illustrates the nested monitor lockoutproblem: The code above illustrates the canonical form of the the nestedmonitor problem in Java. When a Java thread blocks in the monitor'swait queue, all its locks are held except the lock of theobject placed in the queue [Lea:96]. Considerwhat would happen if a thread T made a call to
Outer.process
and as a result blocked in thewait
call inInner.awaitCondition
. SinceInner
andOuter
classes don't share theirmonitor locks, theawaitCondition
call would release theInner
monitor, while retaining theOuter
monitor. However, no other thread can acquire theOuter
monitor since it's locked by the synchronizedprocess
method. Therefore, no thread can callOuter.set
to setthe condition to be true. As a result, T would continue to beblocked inwait
forever.There are several ways to avoid the nested monitor problems in Java [Lea:96]. In our particular example of
MessageQueue
, the solution to the nested monitor problemis to not declareTimedWait.timedWait
as asynchronized
method. Instead, we borrow the monitor lock(which must already be held) fromTimedWait.object_
.Thus, in the following code:thenotFullCondition_.timedWait
call will end up callingwait
on theMessageQueue
instance (which isstored withinTimedWait
in theobject_
field). Therefore, thewait
operation will automaticallyrelease theMessageQueue
's monitor lock. This is thecorrect behavior since it avoids nestedmonitor lockout.Although this solution works, it is non-intuitive and overly subtleunless you are intimately familiar with both Java's Monitor semanticsand patterns like Template Method, Delegated Notification, andBorrowed Monitor. In our experience, detecting and fixing nestedmonitor lockout in Java is tricky. In fact, this example illustrateshow the simplicity of Java's threading semantics can be a limitation.In general, we found that while implementing simple concurrency modelsin Java is easy, implementing more complex concurrency models oftenrequires heroic efforts, particularly when trying to alleviatedeadlock, race conditions, and synchronization overhead. Therefore,it's crucial to understand Java's threading semantics and designpatterns thoroughly to avoid these kinds of nested monitor problems [Lea:96].
- Non-portable scheduling semantics --The Java Virtual Machine (JVM) makes it easier for programmers torapidly develop portable applications. In particular, it eliminatesthe need to expend considerable effort porting middleware andapplications among various OS platforms and language developmentenvironments. However, writing portable mulit-threaded Javaapplications remains problematic for the following reasons:
- Non-standard scheduling semantics -- The Java specification doesn't dictate the type of run-time threadscheduler implementation (i.e., preemptive vs. non-preemptive).In addition, it doesn't require the scheduler to be ``fair.'Therefore, an unlucky thread can starve forever while waiting toacquire a resource if (1) the thread scheduler is non-preemptiveand/or (2) there is sufficient ongoing competition from other threads.One workaround for this problem is to insert
Thread.yield
calls into the code to give other threads a chance to run. However,this solution can be tedious, error-prone, and inefficient since itrequires the programmer to second-guess the level of concurrency inthe application and the Java run-time system. - Haphazard notification -- There is no way tospecify a particular thread or a group of threads that should benotified.
Several Java books incorrectly state that notify
wakes up the thread that has been waiting thelongest. Our solution in Java ACE was to port theACE_Token
class, which implements the Specific Notification pattern[Cargill:96] in order to wake up waitingthreads in a deterministic order. - Priority inversion -- Thread priority has nobearing on Java's notification semantics. For example, a higherpriority thread is not favored over a lower priority one whenthe scheduler selects which thread blocked in a
wait
shouldbe awakened by anotify
.
- Non-standard scheduling semantics -- The Java specification doesn't dictate the type of run-time threadscheduler implementation (i.e., preemptive vs. non-preemptive).In addition, it doesn't require the scheduler to be ``fair.'Therefore, an unlucky thread can starve forever while waiting toacquire a resource if (1) the thread scheduler is non-preemptiveand/or (2) there is sufficient ongoing competition from other threads.One workaround for this problem is to insert
- Lack of encapsulation for synchronization -- Java'sconcurrency control mechanisms expose the
synchronized
keyword in the public interface of a class. Unfortunately, this canbreak object encapsulation since it's possible for clients to disruptthe locking protocol of any Java object with synchronized methods.For instance, a client can accidentally lock aMessageQueue
, as follows: In C++, this type of problem can be prevented by not exposing thelocking mechanism to clients, as follows: Naturally, the C++ solution is not impervious to malicious programmersor to other classic sources of program corruption in C/C++ (such asscribbling over memory due to stray pointers).
3.5. Lack of Destructors
C++ destructors are commonly used to manage reusable resources (suchas memory, I/O descriptors, locks, window buffers, etc.) that must bereleased before leaving a scope. This leads to common patterns in C++where constructors and destructors collaborate to ensure thatresources are acquired on entry, and released on exit, from a block ofcode.For instance, consider the following simplified version of the C++ ACE_Message_Queue
(for simplicity, most error handling code has been omitted):
Since the destructor is called automatically once the class goes outof scope, we can ensure that the contents in the queue are released.This ``acquire/release' protocol is particularly important if thecontents of the queue are resources (like locks or sockets) that arerelatively scarce, compared with virtual memory.
Java lacks general-purpose destructors. Instead, it providesconstructs like finally
and finalize
tomanage the release of resources. The ACE_Message_Queue
class shown above can be written in Java using the constructfinalize
as follows: Similarly, the Java finally
construct can be used insidea method to ensure that resources are released when the method goesout of scope (even if an exception is thrown). Here's an example fromACE in which the svc
method uses aMessageQueue
to process several messages. To ensure thatthe MessageQueue
is properly closed (and the resourcesreleased) when the method goes out of scope, we use the Javafinally
construct. Unfortunately, Java's finally
and finalize
constructs exhibit the following two problems:
- Manual intervention -- In the case of
finally
in Java ACE, the cleanup code needs to beinserted manually infinally
blocks, which can be tediousand error-prone.In contrast, C++ ACE makes it easier since the compiler ensures that adestructor of theMessageQueue
class is calledautomatically on exit from thesvc
method's scope.Therefore, programmers need not insertfinally
blockscontaining cleanup code into multiple blocks. - Premature resource exhaustion -- In the case of
finalize
, the problem centers around ``lazy deletion.'Since the garbage collector may not get run until the application runslow on memory, this approach is prone to premature resourceexhaustion. In particular, a program that queues many locks or socketdescriptors will run out of these resources long before running out ofmemory. Java does provide hooks to allow programmers to explicitlyforce garbage collection using theRuntime
class'sgc
method. In addition, theRuntime
classalso provides arunFinalization
method that runs anypending class finalizers when invoked. However, doing any of thisrequires manual intervention on the part of the programmers. Sincegarbage collection takes time, running it explicitly multiple timesmay not be desirable in time-critical applications.
3.6. Lack of Explicit Parameters Passing Modes
In contrast to other OO languages like C++ and Ada95, Java doesn'tsupport user-specified parameter passing modes. Instead, Java hasreference semantics for object paramters, but passes these referencesby value. For example, in the following code, even after thesomeMethod
invocation, bar
still points tothe String
object associated with the literal string``original.'We encountered this problem in ACE when we tried to implement theclass ACE_SOCK_Stream
, which encapsulates the OS datatransfer mechanisms for sockets. The recv
andrecv_n
methods of C++ ACE rely on the message beingpassed back to the caller by reference. Since Java only passesreferences ``by value,' this became problematic. To circumvent this limitation when converting ACE from C++ to Java, weidentified the following four solutions:
- Add an extra level of indirection -- We appliedthis common pattern and added Java wrappers around classes we wantedto pass by reference. Then, we passed instances of the wrapper class.Thus, in the example above, we created a wrapper string class called
CString
. We then passed a reference to this wrapperobject that contained the actualString
. Here's what theCString
class looks like: With this change, theString
object can now be changedboth by the caller and the callee. Thus, the firstsomeMethod
example can now be rewritten as follows: - Use one-element array -- The second solution passedthe String object in a one-element array. Using this solution, thefirst
someMethod
example can be rewritten as follows: Although this syntax is somewhat ugly, it is relatively concise.Moreover, we can generalize this approach to pass in multiple objectsby reference via a multiple-element array. - Use mutator operators -- Our third solution usedmutator methods. For instance, in our example above, wecould use
StringBuffer
instead ofString
topass the messages between the caller and the callee. Using thissolution, the above example can be rewritten as follows:Note that this is the solution we used in implementingSOCKStream
in ACE Java. This is because, this solutionrequired the least amount of change to our original design.Unfortunately, the numeric wrappers (e.g., classDouble
andInteger
) lack mutator operations. Therefore, theyaren't useful for returning scalar parameters by reference (despitethe claims made by certain Java books...). This forces programmers towrite many additional (and incompatible) wrapper classes that allowthese types to be passed by reference. - Return objects by value -- The fourth solutionrelies on the callee returning a modified version of the object as thereturn value to the caller. The caller then assigns the return valueto the original object in order to ``update' it. Using this solution,the above example can be rewritten as follows:However, to generalize this approach to return multiple values byreference requires defining even more helper classes.
3.7. Lack of Explicit Inline Functions
The lowest-level of ACE is the OS adaptation layer. This layerconsists of C++ OSwrappers that shield systems programmers from using the tedious,error-prone, and non-portable native OS APIs written in C. In C++,there is essentially no cost for adding this extra level ofabstraction sinceinline
static
functionscan be used. In contrast, adding an extra level of abstraction can increase thecost of method calls in Java since it does not support explicitinlining. There are various patterns for handling this problem usingthe Sun JDK.For instance, the JDK Java compiler does allow the user to specify'-O' flag, which does some optimization, but the user has no directcontrol over this without using convoluted, non-portable techniques.Whenever possible, methods (or entire classes) in Java should bedeclared final
to facilitate inlining.
In general, Java doesn't provide standard hooks (like the C/C++register
keyword) to programmers to manually suggestoptimizations. Instead, Java relies on effective compileroptimizations that, in theory, remove the need for programmers to``hand-optimize' performance. In practice, it remains to be seenwhether Java compilers can perform sufficient optimizations to competewith the performance of C/C++ compilers.
4. Concluding Remarks
This article presents our experience converting the ACE communicationsoftware framework from C++ to Java. Overall, the conversion processhas been both exciting and frustrating. It was exciting because wehad the opportunity to explore Java features that aren't available inC++ (such as meta-class programming), as well as to validatecommunication software designpatterns we'd originally discovered using C++. In addition,converting ACE to Java provided us with many insights on the benefitsand limitations of using Java for industrial-strength frameworkdevelopment, particularly for concurrent network servers.The following are our recommendations to developers who plan to buildsystems in Java, either from scratch or by converting code originallywritten in C++:
- Be prepared for a paradigm shift -- Although Java syntaxlooks like C++, many of its semantics, features, and patterns aredifferent from C++. In addition, the Java run-time system differs inkey respects from conventional OS platforms like UNIX and Win32. Inparticular, Java's threading model is different than the model used bySolaris threads, POSIX Pthreads, and Win32 threads. This is not acriticism of Java, per-se, but it demonstrates that developers with abackground in other languages and operating systems should be preparedfor the ``paradigm shift' required to use Java effectively.
- Recognize and exploit Java's strengths -- An importantlesson from our conversion effort was how certain Java featuressimplify the development of communication software. In particular,built-in support for sockets and threads, automatic memory management,and exception handling made it possible to convert many C++ ACEcomponents to Java with relative easy. Depending on the context, theeffort required to develop applications using Java can be less thandeveloping the application using C++. This is particularly true ifthe application uses dynamic memory heavily and/or must run portablyacross multiple OS platforms.
- Pay close attention to code that uses C++-specific languagefeatures -- Several aspects of our conversion were frustratingbecause translating certain ACE components from C++ to Java wasnon-trivial. Many ACE components use C++-specific language features(such as templates and pointers-to-methods) and programming patterns(such as ``constructors acquire resources, destructors releaseresources'). Translating these components to Java directly wastedious due to the lack of certain language features. For instance,lack of templates and lack of enum abstraction, coupled with thelimitation of passing references by value, increased our programmingeffort compared with the existing C++ solutions.
- Apply workarounds when needed -- We found it was oftentricky to convert C++ code directly to Java while keeping theimplementation as close as possible to the original C++ design.By understanding the differences between C++ and Java, however, wewere able to employ various workarounds to successfully convert theACE framework from C++ to Java. When converting C++-based designs orcode written in C++ to Java, you'll invariably face the same hurdlesthat we did. In some cases, you'll need to rethink your design in``Java terms' to overcome these hurdles. To ease the transition, youmight find it useful to apply the workarounds we presented in thisarticle.
The C++ and Java versions of ACE are freely available via the WWW atURL ACE.html.
5. Acknowledgements
We would like to thankChris Cleeland, David Holmes,Doug Lea, David Levine, and Greg Wilson for comments on earlier drafts of this article.Conversion Of C Program To Java
6. References
[Lea:96]Doug Lea, Concurrent Java: DesignPrinciples and Patterns, Addison-Wesley, 1996.[AWT:96]AWTComponents, Source: http://java.sun.com/tutorial/ui/overview/components.html
Actually, Having A Quick Look At The Source Code No Automatic Translator Will Do A Good Job On The Conversion. The Code Is Based On Too Many Librar...
[Coplien:92]James Coplien.Advanced C++ Programming Styles and Idioms. Reading, MA.:Addison-Wesley, 1992.
[Schmidt:94]Douglas C. Schmidt, ACE: anObject-Oriented Framework for Developing Distributed Applications,Proceedings of the 6th USENIX C++ Technical Conference, Cambridge,Massachusetts, April, 1994.
[Cargill:96]SpecificNotification for Java Thread Synchronization, Proceedings of the3rd Pattern Languages of ProgrammingConference, Allerton Park, Illinois, September, 1996.
C 2b 2b To Java Converter Free
[GoF:95] E. Gamma, R. Helm,R. Johnson, and J. Vlissides, DesignPatterns: Elements of Reusable Object-Oriented Software,Reading, MA: Addison-Wesley, 1995.
This page is maintained by Prashant Jain and Douglas C. Schmidt. If youhave any comments or suggestion for improving this document, pleasesend us email.
Decimal To Binary Conversion Program In C/c /java
Last modified 18:06:18 CST 25 January 2019