There's some other creative ways to do this too.
(1) If the size of the shared space is large and you need fast and immediate access, then dual-ported memory is the (expensive) way to go
(2) if the size is large, but you don't need full speed simultaneous access, then you can use a small CPLD between both CPU's and the single ported memory, as an arbiter. The CPLD logic will make one CPU wait if the other CPU is writing. The amount of granularity is up to you. For example, you can segment 64K of memory into 4 x 16k blocks and arbitrate at that level (so two CPU's can be writing to different blocks at the same time through the CPLD). You can make every read or write access to a block completely exclusive, or you can have multiple read access and single writes.. again to the whole memory array or on block boundaries. The complexity is dependent on your needs..
(3) if your shared memory needs are even smaller still, say just 8k or 16k of shared data, and you have a region of unused memory in the address spaces of both CPUs, then you can simply use the CPLD and its internal RAM as the shared memory, and it can be mapped into the two available address spaces of both CPUs.
(4) if you don't need high speed access, you can even use a shared SPI serial flash, or a shared I2C EEPROM, and use external priority logic for /REQ and /GRANT between the two CPUs to gain access to the shared I2C or SPI device.
* and to extend my #3 with what amyk says, you can just use a RAM chip and /REQ, /GRANT the bus to it