Dependency injection frameworks/IOC containers (pick your stupid name of choice) avoid at least one of the problems of singletons: if you decide that you don't want it to be a singleton any more, you can trivially change the lifecycle to 'instance'.
By contrast, using singletons in plain old Java, you'd have to go through and find every usage of SingletonClass.instance().everyMethodCallOnThisClass() and push it up to the constructor. Since you now have a new dependency you have to pass all the way down your callstack, it can get painful quickly.
You still have the issue of global state, but I think that's altogether more complicated. Sometimes state really is global to your program and exposing it everything makes a certain amount of sense. It may well be ok for managing system resources (thread pools, network connections, IO in general). I think often you can get away with a boolean flag or two shared across a whole app. If it's read-only it's not a problem at all.
You need to be aware that every bit of shared state you make available or continue to use is a potential source of bugs and maintenance. Every bit of this that you expose needs serious thought. Don't let it snowball. Even if it means you have to make a few extra types of object or pass a stupid number of arguments all the way down the callstack, that's usually preferable because at least then you can reason about what's going on.
And with structured programming, it's only one extra argument per function call, StuffThisFunctionNeeds (plus a structure definition for each different flavor of StuffThisFunctionNeeds)
By contrast, using singletons in plain old Java, you'd have to go through and find every usage of SingletonClass.instance().everyMethodCallOnThisClass() and push it up to the constructor. Since you now have a new dependency you have to pass all the way down your callstack, it can get painful quickly.
You still have the issue of global state, but I think that's altogether more complicated. Sometimes state really is global to your program and exposing it everything makes a certain amount of sense. It may well be ok for managing system resources (thread pools, network connections, IO in general). I think often you can get away with a boolean flag or two shared across a whole app. If it's read-only it's not a problem at all.
You need to be aware that every bit of shared state you make available or continue to use is a potential source of bugs and maintenance. Every bit of this that you expose needs serious thought. Don't let it snowball. Even if it means you have to make a few extra types of object or pass a stupid number of arguments all the way down the callstack, that's usually preferable because at least then you can reason about what's going on.