vamos la
CGI é uma antiga ( porem ainda em uso ) tecnologia onde um request HTTP vai executar um processo no sistema operacional diferente, então vc tem um belo isolamento entre os requests ( se um processo explodir, o outro não vai notar ). Por outro lado vc ter um processo por request vai ser ineficiente em um determinado limite pois vc tem o custo de iniciar o processo, vc tem o context switch entre processos no sistema operacional, vc aloca um X de memoria para cada request, etc.
A Servlet ja é um conceito mais esperto: vc tem uma instancia distribuida entre threads. Cada request vai ser atendido em uma thread. Por isso que Servlets não devem guardar estado, elas devem ser stateless.
Entenda: entre threads vc tem as variaveis locais salvas de interferencia. assim se vc esta fazendo um loop de 1 a 100 vc nao tem uma thread vendo o valor desse contador da outra thread. o codigo é copiado e executado em threads diferentes ( vc tem menos overhead que um processo, mas ainda assim tem custos ). O bixo pega se vc tiver atributos na servlet. Imagina que vc tem um atributo de instancia “login” onde vc armazena o login usado no request: entre threads diferentes este atributo sera atualizado e vc pode misturar as coisas.
faça um teste vc mesmo acessando uma servlet de abas diferentes do seu browser e tente armazenar alguma coisa na instancia.
mas ai é claro que vc pode proteger a sua servlet usando “lock/sincronização”. o problema é que isso é relativamente caro e vc cria um gargalo pois apenas uma thread vai poder entrar naquela porção de codigo.
Se a Servlet é stateless, então vc tem uma arquitetura relativamente facil de escalar: um servidor vai conseguir atender a mais requests simultaneos.
mas ai vc vai dizer “ué mas eu tenho estado: quando o cara esta fazendo uma compra na minha loja virtual ele tem um carrinho e ele pode adicionar ou remover coisas e isso é um estado”
Ai entra uma questão mais fundamental: o protocolo HTTP é stateless. para vc ter esse carrinho de compras vc precisa amarrar de alguma forma: por exemplo com um Cookie. O cookie vai dizer: esse cara esta usando o carrinho 1234 e vc salvou o carrinho 1234 em algum lugar ( num storage compartilhado como o Redis, num banco de dados, etc ), vc pode ter salvo até na Servlet com um Map<String, Carrinho> mas perceba que OU vc jogou o estado para outros componentes OU vc esta usando um estado ( o mapa de carrinhos ) que tem um pedaço por cookie e uma pessoa não acessa o cookie da outra.
a questão da servlet ser stateless é uma questão de design. Perceba que se vc adiciona coisas na servlet, pode fica mais complexo de lidar com diferentes fluxos. mas a Servlet tem que ter alguma coisa, como uma instancia de um DAO ou outro objeto. não é escrito em pedra. vc só tem que evitar os efeitos colaterais de ter um estado quando vc tem multiplas threads executando ao mesmo tempo.