Most micro controllers these days have part or manufacturer specific methods to protect the embedded firmware code. This is generally done by locking out the circuits that normally allow the code memory to be read out. (You'll have to look for part specific details in the data sheet or at the manufacturers web site in applicable application notes).
Once locked it is not possible to read out the code memory using normal techniques. This provides a reasonable level of protection to keep most hackers from viewing the machine code for your embedded application.
Many MCU devices these days have on-board FLASH memory to strore the program code. A previously stored and protected program stored in FLASH can usually be replaced with new code but it takes a full chip FLASH erase operation to unlock the protection mechanism. Once erased the part will operate like it did before the original protection lock. If a new program is loaded it is generally possible to re-lock the part in order to protect the newly loaded machine code.
Any discussion of code protection in microcontrollers would not be complete without mention that there is usually no guarantee that any protection scheme offered by the part manufacturer is fool proof. Manufacturers will even state that the protection systems are not 100% fool proof. One of the reasons for this is that there is a whole black market industry going on where, for a fee, diligent hackers will read out code from a protected part for anyone that wants to pay. They have devised various schemes that permit the code to be read out of the ROMs or FLASHes on protected micro controllers. Some of these schemes are incredibly clever but work to better success on some part families than on others. So be aware of this fact then you try to protect your program from prying eyes.
Once someone has their hands on the binary image of the machine code that has been read out of a microcontroller, whether that was a protected microcontroller or not, they can process the machine code through a tool called a disassembler. This will turn the binary data back into assembly language code that can be studied to try to learn how the algorithms of your program work. Doing accurate disassembly of machine code is a painstaking job that can take huge amounts of work. In the end the process can lead to the assembler code like I described. If your program was written in some high level language such as C, C++ or Basic the assembly code will only represent the compiled and linked result of your program. It is generally not possible to reverse engineer stolen code all the way back to the high level language level. Experienced hackers can come close given enough time and experience.
What this means is there is actually a benefit to writing your embedded application firmware in a high level language. It provides another layer that makes it harder for your program to be fully reverse engineered. Even greater benefit is to be had by using the highest state of the art in optimizing compilers to compile the embedded application because the highest performance optimizers can literally turn the program into a huge spaghetti bowl full of dozens of calls to short subroutines that are very hard to decipher in a disassembler.
Most experienced embedded developers will tell you to go ahead and use any protection scheme that is offered on the MCU in your application....but not to depend upon it to the end of road for your product. They will tell you that the best way to stay ahead of the competition is to constantly upgrade your product so that the old versions are out of date and uninteresting by the time that hackers may have cloned your code. Change the code around, add new features, spin your PC boards from time to time to swap all your I/Os around and any other things that you may think of. This way you can win the race every time.