java synchronized what is thread synchronization java
Este tutorial explica a sincronização de threads em Java junto com conceitos relacionados, como Java Lock, Race Condition, Mutex, Java Volatile e Deadlock em Java:
Em um ambiente multithreading onde vários threads estão envolvidos, pode haver conflitos quando mais de um thread tenta obter o mesmo recurso ao mesmo tempo. Esses confrontos resultam em “condição de corrida” e, portanto, o programa produz resultados inesperados.
Por exemplo, um único arquivo está sendo atualizado por dois threads. Se um thread T1 está em processo de atualização deste arquivo, diga alguma variável. Agora, enquanto esta atualização por T1 ainda está em andamento, digamos que o segundo thread T2 também atualiza a mesma variável. Dessa forma, a variável dará resultados errados.
=> Assista à série completa de treinamento Java aqui.
Quando vários threads estão envolvidos, devemos gerenciar esses threads de forma que um recurso possa ser acessado por um único thread por vez. No exemplo acima, o arquivo que é acessado por ambas as threads deve ser gerenciado de forma que T2 não possa acessar o arquivo até que T1 termine de acessá-lo.
Isso é feito em Java usando “ Sincronização de thread ”.
O que você aprenderá:
- Sincronização de thread em Java
- Multi-threading sem sincronização
- Multi-threading com sincronização
- Conclusão
Sincronização de thread em Java
Como Java é uma linguagem multi_threaded, a sincronização de threads tem muita importância em Java, pois vários threads são executados em paralelo em um aplicativo.
Usamos palavras-chave “Sincronizado” e 'volátil' para alcançar a sincronização em Java
Precisamos de sincronização quando o objeto ou recurso compartilhado é mutável. Se o recurso for imutável, os threads apenas lerão o recurso simultaneamente ou individualmente.
Nesse caso, não precisamos sincronizar o recurso. Neste caso, a JVM garante que O código sincronizado Java é executado por um thread de cada vez .
Na maioria das vezes, o acesso simultâneo a recursos compartilhados em Java pode introduzir erros como “inconsistência de memória” e “interferência de thread”. Para evitar esses erros, precisamos ir para a sincronização de recursos compartilhados para que o acesso a esses recursos seja mutuamente exclusivo.
Usamos um conceito chamado Monitores para implementar a sincronização. Um monitor pode ser acessado por apenas um segmento de cada vez. Quando um thread obtém o bloqueio, então, podemos dizer que o thread entrou no monitor.
Quando um monitor está sendo acessado por um determinado segmento, o monitor é bloqueado e todos os outros segmentos que tentam entrar no monitor são suspensos até que o segmento de acesso termine e libere o bloqueio.
No futuro, discutiremos a sincronização em Java em detalhes neste tutorial. Agora, vamos discutir alguns conceitos básicos relacionados à sincronização em Java.
Condição de corrida em Java
Em um ambiente multithread, quando mais de um thread tenta acessar um recurso compartilhado para gravação simultaneamente, vários threads competem entre si para concluir o acesso ao recurso. Isso dá origem à 'condição de corrida'.
Uma coisa a se considerar é que não há problema se vários threads estiverem tentando acessar um recurso compartilhado apenas para leitura. O problema surge quando vários threads acessam o mesmo recurso ao mesmo tempo.
As condições de corrida ocorrem devido à falta de sincronização adequada de threads no programa. Quando sincronizamos corretamente os threads de modo que por vez apenas um thread acessará o recurso, e a condição de corrida deixa de existir.
Então, como detectamos a condição de raça?
A melhor maneira de detectar a condição de corrida é por meio da revisão do código. Como programadores, devemos revisar o código completamente para verificar se há condições de corrida em potencial que possam ocorrer.
Bloqueios / monitores em Java
Já mencionamos que usamos monitores ou bloqueios para implementar a sincronização. O monitor ou bloqueio é uma entidade interna e está associado a todos os objetos. Portanto, sempre que uma thread precisa acessar o objeto, ela deve primeiro adquirir o bloqueio ou monitor de seu objeto, trabalhar no objeto e então liberar o bloqueio.
Os bloqueios em Java terão a seguinte aparência:
public class Lock { private boolean isLocked = false; public synchronized void lock() throws InterruptedException { while(isLocked) { wait(); } isLocked = true; } public synchronized void unlock(){ isLocked = false; notify(); } }
Conforme mostrado acima, temos um método lock () que bloqueia a instância. Todos os threads que chamam o método lock () serão bloqueados até que os conjuntos de métodos unblock () sejam bloqueados com o sinalizador falso e notifique todos os threads em espera.
Algumas dicas para lembrar sobre bloqueios:
- Em Java, cada objeto possui um cadeado ou monitor. Este bloqueio pode ser acessado por um thread.
- Por vez, apenas um thread pode adquirir este monitor ou bloqueio.
- A linguagem de programação Java fornece uma palavra-chave Synchronized ’que nos permite sincronizar os threads criando um bloco ou método como Synchronized.
- Os recursos compartilhados que os threads precisam acessar são mantidos sob este bloco / método Sincronizado.
Mutexes em Java
Já discutimos que em um ambiente multithread, condições de corrida podem ocorrer quando mais de um segmento tenta acessar os recursos compartilhados simultaneamente e as condições de corrida resultam em uma saída inesperada.
A parte do programa que tenta acessar o recurso compartilhado é chamada de 'Seção Crítica' . Para evitar a ocorrência de condições de corrida, é necessário sincronizar o acesso ao trecho crítico. Ao sincronizar esta seção crítica, garantimos que apenas um thread pode acessar a seção crítica por vez.
O tipo mais simples de sincronizador é o “mutex”. Mutex garante que em qualquer instância, apenas um thread pode executar a seção crítica.
O mutex é semelhante ao conceito de monitores ou bloqueios que discutimos acima. Se um thread precisa acessar uma seção crítica, ele precisa adquirir o mutex. Depois que o mutex for adquirido, o thread acessará o código da seção crítica e, quando concluído, liberará o mutex.
Os outros threads que estão esperando para acessar a seção crítica serão bloqueados nesse meio tempo. Assim que o thread que contém o mutex o liberar, outro thread entrará na seção crítica.
software de virtualização grátis para windows 10
Existem várias maneiras de implementar um mutex em Java.
- Usando palavras-chave sincronizadas
- Usando o Semaphore
- Usando ReentrantLock
Neste tutorial, discutiremos a primeira abordagem, ou seja, a sincronização. As outras duas abordagens - Semaphore e ReentrantLock serão discutidas no próximo tutorial em que discutiremos o pacote simultâneo Java.
Palavra-chave sincronizada
Java fornece uma palavra-chave “Sincronizado” que pode ser usada em um programa para marcar uma seção crítica. A seção crítica pode ser um bloco de código ou um método completo. Assim, apenas um thread pode acessar a seção crítica marcada pela palavra-chave Synchronized.
Podemos escrever as partes simultâneas (partes que são executadas simultaneamente) para um aplicativo usando a palavra-chave Synchronized. Também eliminamos as condições de corrida tornando um bloco de código ou um método Sincronizado.
Quando marcamos um bloco ou método sincronizado, protegemos os recursos compartilhados dentro dessas entidades contra acesso simultâneo e, portanto, corrupção.
Tipos de Sincronização
Existem 2 tipos de sincronização, conforme explicado abaixo:
# 1) Sincronização do Processo
A sincronização de processos envolve vários processos ou threads em execução simultaneamente. Por fim, eles alcançam um estado em que esses processos ou threads se comprometem com uma sequência específica de ações.
# 2) Sincronização de Thread
Na sincronização de thread, mais de um thread está tentando acessar um espaço compartilhado. Os threads são sincronizados de forma que o espaço compartilhado seja acessado apenas por um thread por vez.
A sincronização de processos está fora do escopo deste tutorial. Portanto, discutiremos apenas a sincronização de threads aqui.
Em Java, podemos usar a palavra-chave synchronized com:
- Um bloco de código
- Um método
Os tipos acima são os tipos mutuamente exclusivos de sincronização de thread. A exclusão mútua impede que os threads que acessam os dados compartilhados interfiram uns com os outros.
O outro tipo de sincronização de thread é a “comunicação InterThread” que se baseia na cooperação entre threads. A comunicação entre threads está fora do escopo deste tutorial.
Antes de prosseguirmos com a sincronização de blocos e métodos, vamos implementar um programa Java para demonstrar o comportamento dos threads quando não há sincronização.
Multi-threading sem sincronização
O programa Java a seguir possui vários threads que não estão sincronizados.
class PrintCount { //method to print the thread counter public void printcounter() { try { for(int i = 5; i > 0; i--) { System.out.println('Counter ==> ' + i ); } } catch (Exception e) { System.out.println('Thread interrupted.'); } } } //thread class class ThreadCounter extends Thread { private Thread t; private String threadName; PrintCount PD; //class constructor for initialization ThreadCounter( String name, PrintCount pd) { threadName = name; PD = pd; } //run method for thread public void run() { PD.printcounter(); System.out.println('Thread ' + threadName + ' exiting.'); } //start method for thread public void start () { System.out.println('Starting ' + threadName ); if (t == null) { t = new Thread (this, threadName); t.start (); } } } public class Main { public static void main(String args()) { PrintCount PD = new PrintCount(); //create two instances of thread class ThreadCounter T1 = new ThreadCounter( 'ThreadCounter_1 ', PD ); ThreadCounter T2 = new ThreadCounter( 'ThreadCounter_2 ', PD ); //start both the threads T1.start(); T2.start(); // wait for threads to end try { T1.join(); T2.join(); } catch ( Exception e) { System.out.println('Interrupted'); } } }
Resultado
A partir da saída, podemos ver que, como os threads não estão sincronizados, a saída é inconsistente. Os dois threads iniciam e exibem o contador um após o outro. Ambos os fios saem no final.
A partir do programa fornecido, o primeiro encadeamento deve ter saído após exibir os valores do contador e, em seguida, o segundo encadeamento deve ter começado a exibir os valores do contador.
Agora vamos para a sincronização e começar com a sincronização do bloco de código.
Bloco de código sincronizado
Um bloco sincronizado é usado para sincronizar um bloco de código. Este bloco geralmente consiste em algumas linhas. Um bloco sincronizado é usado quando não queremos que um método inteiro seja sincronizado.
Por exemplo, temos um método com, digamos, 75 linhas de código. Fora disso, apenas 10 linhas de código devem ser executadas por um thread de cada vez. Nesse caso, se tornarmos todo o método sincronizado, será um fardo para o sistema. Em tais situações, optamos por blocos sincronizados.
O escopo do método sincronizado é sempre menor que o de um método sincronizado. Um método sincronizado bloqueia um objeto de um recurso compartilhado que deve ser usado por vários threads.
A sintaxe geral de um bloco sincronizado é mostrada abaixo:
synchronized (lock_object){ //synchronized code statements }
Aqui, “lock_object” é uma expressão de referência de objeto no qual o bloqueio deve ser obtido. Portanto, sempre que um thread deseja acessar as instruções sincronizadas dentro do bloco para execução, ele deve adquirir o bloqueio no monitor ‘lock_object’.
Como já discutido, a palavra-chave synchronized garante que apenas um encadeamento possa adquirir um bloqueio por vez e todos os outros encadeamentos tenham que esperar até que o encadeamento que contém o bloqueio termine e libere o bloqueio.
Observação
- Uma “NullPointerException” é lançada se o lock_object usado for Nulo.
- Se um thread dorme enquanto mantém o bloqueio, o bloqueio não é liberado. Os outros encadeamentos não poderão acessar o objeto compartilhado durante este tempo de suspensão.
Agora vamos apresentar o exemplo acima que já foi implementado com pequenas alterações. No programa anterior, não sincronizamos o código. Agora vamos usar o bloco sincronizado e comparar a saída.
Multi-threading com sincronização
No programa Java abaixo, usamos um bloco sincronizado. No método run, sincronizamos o código de linhas que imprimem o contador de cada thread.
class PrintCount { //print thread counter public void printCounter() { try { for(int i = 5; i > 0; i--) { System.out.println('Counter ==> ' + i ); } } catch (Exception e) { System.out.println('Thread interrupted.'); } } } //thread class class ThreadCounter extends Thread { private Thread t; private String threadName; PrintCount PD; //class constructor for initialization ThreadCounter( String name, PrintCount pd) { threadName = name; PD = pd; } //run () method for thread with synchronized block public void run() { synchronized(PD) { PD.printCounter(); } System.out.println('Thread ' + threadName + ' exiting.'); } //start () method for thread public void start () { System.out.println('Starting ' + threadName ); if (t == null) { t = new Thread (this, threadName); t.start (); } } } public class Main { public static void main(String args()) { PrintCount PD = new PrintCount(); //create thread instances ThreadCounter T1 = new ThreadCounter( 'Thread_1 ', PD ); ThreadCounter T2 = new ThreadCounter( 'Thread_2 ', PD ); //start both the threads T1.start(); T2.start(); // wait for threads to end try { T1.join(); T2.join(); } catch ( Exception e) { System.out.println('Interrupted'); } } }
Resultado
Agora, a saída deste programa usando bloco sincronizado é bastante consistente. Como esperado, os dois threads começam a ser executados. O primeiro encadeamento terminou exibindo os valores do contador e sai. Em seguida, o segundo thread exibe os valores do contador e sai.
Método Sincronizado
Vamos discutir o método sincronizado nesta seção. Vimos anteriormente que podemos declarar um pequeno bloco consistindo em menos linhas de código como um bloco sincronizado. Se quisermos que toda a função seja sincronizada, podemos declarar um método como sincronizado.
Quando um método é sincronizado, apenas um thread poderá fazer uma chamada de método por vez.
A sintaxe geral para escrever um método sincronizado é:
synchronized method_name (parameters){ //synchronized code }
Assim como um bloco sincronizado, no caso de um método sincronizado, precisamos de um lock_object que será usado por threads que acessam o método sincronizado.
Para o método sincronizado, o objeto de bloqueio pode ser um dos seguintes:
- Se o método sincronizado for estático, o objeto de bloqueio é fornecido pelo objeto ‘.class’.
- Para um método não estático, o objeto de bloqueio é fornecido pelo objeto atual, ou seja, 'este' objeto.
Uma característica peculiar da palavra-chave sincronizada é que ela é reentrante. Isso significa que um método sincronizado pode chamar outro método sincronizado com o mesmo bloqueio. Portanto, um encadeamento que contém o bloqueio pode acessar outro método sincronizado sem precisar adquirir um bloqueio diferente.
O Método Sincronizado é demonstrado usando o exemplo abaixo.
class NumberClass { //synchronized method to print squares of numbers synchronized void printSquares(int n) throws InterruptedException { //iterate from 1 to given number and print the squares at each iteration for (int i = 1; i <= n; i++) { System.out.println(Thread.currentThread().getName() + ' :: '+ i*i); Thread.sleep(500); } } } public class Main { public static void main(String args()) { final NumberClass number = new NumberClass(); //create thread Runnable thread = new Runnable() { public void run() { try { number.printSquares(3); } catch (InterruptedException e) { e.printStackTrace(); } } }; //start thread instance new Thread(thread, 'Thread One').start(); new Thread(thread, 'Thread Two').start(); } }
Resultado
No programa acima, usamos um método sincronizado para imprimir os quadrados de um número. O limite superior do número é passado para o método como um argumento. Então, a partir de 1, os quadrados de cada número são impressos até que o limite superior seja alcançado.
Na função principal, a instância do thread é criada. Cada instância de thread recebe um número para imprimir quadrados.
Conforme mencionado acima, quando um método a ser sincronizado é estático, o objeto de bloqueio está envolvido na classe e não no objeto. Isso significa que vamos travar na classe e não no objeto. Isso é chamado de sincronização estática.
Outro exemplo é dado abaixo.
class Table{ //synchronized static method to print squares of numbers synchronized static void printTable(int n){ for(int i=1;i<=10;i++){ System.out.print(n*i + ' '); try{ Thread.sleep(400); }catch(Exception e){} } System.out.println(); } } //thread class Thread_One class Thread_One extends Thread{ public void run(){ Table.printTable(2); } } //thread class Thread_Two class Thread_Two extends Thread{ public void run(){ Table.printTable(5); } } public class Main{ public static void main(String t()){ //create instances of Thread_One and Thread_Two Thread_One t1=new Thread_One (); Thread_Two t2=new Thread_Two (); //start each thread instance t1.start(); t2.start(); } }
Resultado
entrada e saída de arquivo c ++
No programa acima, imprimimos tabelas de multiplicação de números. Cada número cuja tabela deve ser impressa é uma instância de thread de classe de thread diferente. Assim, imprimimos tabelas de multiplicação de 2 e 5, então temos thread_one e thread_two de duas classes para imprimir as tabelas 2 e 5, respectivamente.
Para resumir, a palavra-chave sincronizada do Java executa as seguintes funções:
- A palavra-chave synchronized em Java garante acesso mutuamente exclusivo a recursos compartilhados, fornecendo um mecanismo de bloqueio. O bloqueio também evita condições de corrida.
- Usando a palavra-chave synchronized, evitamos erros de programação simultâneos no código.
- Quando um método ou bloco é declarado como sincronizado, um thread precisa de um bloqueio exclusivo para entrar no método ou bloco sincronizado. Depois de realizar as ações necessárias, o encadeamento libera o bloqueio e liberará a operação de gravação. Desta forma eliminará erros de memória relacionados a inconsistências.
Volátil em Java
Uma palavra-chave volátil em Java é usada para tornar as classes thread-safe. Também usamos a palavra-chave volatile para modificar o valor da variável por diferentes threads. Uma palavra-chave volátil pode ser usada para declarar uma variável com tipos primitivos, bem como objetos.
Em certos casos, uma palavra-chave volátil é usada como alternativa para a palavra-chave sincronizada, mas observe que ela não substitui a palavra-chave sincronizada.
Quando uma variável é declarada volátil, seu valor nunca é armazenado em cache, mas sempre lido da memória principal. Uma variável volátil garante ordenação e visibilidade. Embora uma variável possa ser declarada como volátil, não podemos declarar classes ou métodos como voláteis.
Considere o seguinte bloco de código:
class ABC{ static volatile int myvar =10; }
No código acima, a variável myvar é estática e volátil. Uma variável estática é compartilhada entre todos os objetos de classe. A variável volátil sempre reside na memória principal e nunca é armazenada em cache.
Portanto, haverá apenas uma cópia de myvar na memória principal e todas as ações de leitura / gravação serão feitas nesta variável da memória principal. Se myvar não foi declarado como volátil, então cada objeto de thread teria uma cópia diferente que resultaria em inconsistências.
Algumas das diferenças entre palavras-chave voláteis e sincronizadas estão listadas abaixo.
Palavra-chave volátil | Palavra-chave sincronizada |
---|---|
A palavra-chave volatile é usada apenas com variáveis. | A palavra-chave synchronized é usada com blocos de código e métodos. |
Uma palavra-chave volátil não pode bloquear o encadeamento para espera. | A palavra-chave synchronized pode bloquear o thread em espera. |
O desempenho da rosca é aprimorado com o Volatile. | O desempenho do thread degrada um pouco com o sincronizado. |
Variáveis voláteis residem na memória principal. | Construções sincronizadas não residem na memória principal. |
Volatile sincroniza uma variável entre a memória do thread e a memória principal por vez. | A palavra-chave sincronizada sincroniza todas as variáveis de uma vez. |
Deadlock em Java
Vimos que podemos sincronizar várias threads usando a palavra-chave synchronized e tornar os programas thread-safe. Ao sincronizar os threads, garantimos que os vários threads executem simultaneamente em um ambiente multi-threaded.
No entanto, às vezes ocorre uma situação em que os threads não podem mais funcionar simultaneamente. Em vez disso, eles esperam indefinidamente. Isso ocorre quando um encadeamento espera um recurso e esse recurso é bloqueado pelo segundo encadeamento.
O segundo encadeamento, por outro lado, está aguardando o recurso que está bloqueado pelo primeiro encadeamento. Tal situação dá origem a um “impasse” em Java.
O deadlock em Java é descrito usando a imagem abaixo.
Como podemos ver no diagrama acima, o encadeamento A bloqueou o recurso r1 e está aguardando o recurso r2. O thread B, por outro lado, bloqueou o recurso r2 e está aguardando r1.
Portanto, nenhum dos threads pode terminar sua execução a menos que obtenha os recursos pendentes. Essa situação resultou em um conflito em que ambos os encadeamentos estão esperando indefinidamente pelos recursos.
Abaixo está um exemplo de Deadlocks em Java.
public class Main { public static void main(String() args) { //define shared resources final String shared_res1 = 'Java tutorials'; final String shared_res2 = 'Multithreading'; // thread_one => locks shared_res1 then shared_res2 Thread thread_one = new Thread() { public void run() { synchronized (shared_res1) { System.out.println('Thread one: locked shared resource 1'); try { Thread.sleep(100);} catch (Exception e) {} synchronized (shared_res2) { System.out.println('Thread one: locked shared resource 2'); } } } }; // thread_two=> locks shared_res2 then shared_res1 Thread thread_two = new Thread() { public void run() { synchronized (shared_res2) { System.out.println('Thread two: locked shared resource 2'); try { Thread.sleep(100);} catch (Exception e) {} synchronized (shared_res1) { System.out.println('Thread two: locked shared resource 1'); } } } }; //start both the threads thread_one.start(); thread_two.start(); } }
Resultado
No programa acima, temos dois recursos compartilhados e dois threads. Ambos os threads tentam acessar os recursos compartilhados um por um. A saída mostra os dois threads bloqueando um recurso cada enquanto aguardam os outros. Criando assim uma situação de deadlock.
Embora não possamos impedir completamente a ocorrência de situações de impasse, certamente podemos evitá-las tomando algumas medidas.
Listados abaixo estão os meios pelos quais podemos evitar deadlocks em Java.
# 1) Evitando bloqueios aninhados
Ter bloqueios aninhados é o motivo mais importante para haver deadlocks. Os bloqueios aninhados são os bloqueios que são fornecidos a vários threads. Portanto, devemos evitar dar bloqueios a mais de um thread.
# 2) Use o tópico para participar
Devemos usar Thread.join com tempo máximo para que os threads possam usar o tempo máximo de execução. Isso evitará o deadlock que ocorre principalmente quando um thread espera continuamente por outros.
# 3) Evite bloqueio desnecessário
Devemos bloquear apenas o código necessário. Ter bloqueios desnecessários para o código pode levar a bloqueios no programa. Como os deadlocks podem quebrar o código e prejudicar o fluxo do programa, devemos estar inclinados a evitar deadlocks em nossos programas.
perguntas frequentes
P # 1) O que é sincronização e por que ela é importante?
Responda: A sincronização é o processo de controlar o acesso de um recurso compartilhado a vários threads. Sem sincronização, vários threads podem atualizar ou alterar o recurso compartilhado ao mesmo tempo, resultando em inconsistências.
Portanto, devemos garantir que, em um ambiente multi-threaded, os threads sejam sincronizados de forma que a maneira como acessam os recursos compartilhados seja mutuamente exclusiva e consistente.
P # 2) O que é sincronização e não sincronização em Java?
Responda: A sincronização significa que uma construção é um thread-safe. Isso significa que vários threads não podem acessar a construção (bloco de código, método, etc.) de uma vez.
Construções não sincronizadas não são thread-safe. Vários threads podem acessar os métodos ou blocos não sincronizados a qualquer momento. Uma classe popular não sincronizada em Java é StringBuilder.
P # 3) Por que a sincronização é necessária?
Responda: Quando os processos precisam ser executados simultaneamente, precisamos de sincronização. Isso ocorre porque precisamos de recursos que podem ser compartilhados entre vários processos.
A fim de evitar conflitos entre processos ou threads para acessar recursos compartilhados, precisamos sincronizar esses recursos para que todos os threads tenham acesso aos recursos e o aplicativo também funcione sem problemas.
Q # 4) Como você consegue uma Synchronized ArrayList?
Responda: Podemos usar o método de lista Collections.synchronized com ArrayList como um argumento para converter ArrayList em uma lista sincronizada.
P # 5) O HashMap está sincronizado?
para que é usada a programação c ++
Responda: Não, o HashMap não está sincronizado, mas o HashTable está sincronizado.
Conclusão
Neste tutorial, discutimos a sincronização de threads em detalhes. Junto com ele, também aprendemos sobre a palavra-chave volatile e deadlocks em Java. A sincronização consiste na sincronização de processos e threads.
Em um ambiente multithreading, estamos mais preocupados com a sincronização de threads. Vimos a abordagem de palavra-chave sincronizada da sincronização de threads aqui.
Deadlock é uma situação em que vários threads aguardam continuamente por recursos. Vimos o exemplo de deadlocks em Java junto com os métodos para evitar deadlocks em Java.
=> Visite aqui para aprender Java do zero.
Leitura recomendada
- Thread.Sleep () - Método Thread Sleep () em Java com exemplos
- Threads Java com métodos e ciclo de vida
- Java Basics: Java Syntax, Java Class e Core Java Concepts
- Multithreading em Java - Tutorial com exemplos
- Multithreading em C ++ com exemplos
- Tutorial de JAVA para iniciantes: mais de 100 tutoriais práticos em vídeo Java
- Componentes Java: plataforma Java, JDK, JRE e máquina virtual Java
- Tutorial de Java String | Métodos Java String com exemplos