0% acharam este documento útil (0 voto)
12 visualizações10 páginas

Programação Avançada (Prova)

Este documento discute conceitos relacionados a threads em Java, incluindo a relação entre a classe Thread, objetos Thread e threads reais de execução. Também aborda técnicas como sincronização e memória consistente para programação concorrente.

Enviado por

leonardoalves
Direitos autorais
© © All Rights Reserved
Levamos muito a sério os direitos de conteúdo. Se você suspeita que este conteúdo é seu, reivindique-o aqui.
Formatos disponíveis
Baixe no formato PDF, TXT ou leia on-line no Scribd
0% acharam este documento útil (0 voto)
12 visualizações10 páginas

Programação Avançada (Prova)

Este documento discute conceitos relacionados a threads em Java, incluindo a relação entre a classe Thread, objetos Thread e threads reais de execução. Também aborda técnicas como sincronização e memória consistente para programação concorrente.

Enviado por

leonardoalves
Direitos autorais
© © All Rights Reserved
Levamos muito a sério os direitos de conteúdo. Se você suspeita que este conteúdo é seu, reivindique-o aqui.
Formatos disponíveis
Baixe no formato PDF, TXT ou leia on-line no Scribd
Você está na página 1/ 10

Questão 1)

A) Relação entre a classe Java Thread, objetos Thread criados a partir dela e threads
reais de execução.

A classe Java Thread é uma classe abstrata que define uma estrutura básica para a criação
e gerenciamento de threads. Objetos Thread são instâncias da classe Java Thread que
representam threads individuais de execução. Threads reais de execução são unidades de
processamento independentes que podem executar código simultaneamente.

A relação entre esses três conceitos pode ser resumida da seguinte forma:

● A classe Java Thread fornece um modelo para a criação e gerenciamento de


threads.
● Objetos Thread são instâncias da classe Java Thread que representam threads
individuais de execução.
● Threads reais de execução são unidades de processamento independentes que
podem executar código simultaneamente.

Em outras palavras, a classe Java Thread define a estrutura para a criação de threads,
enquanto objetos Thread representam essas threads individuais e threads reais de
execução são as unidades de processamento reais que executam o código.

B) Identificação das três threads de execução e suas funções.

No programa fornecido, existem três threads de execução:

● Thread principal: Esta é a thread que inicia a execução do programa. Ela é


responsável por criar as outras duas threads e iniciar suas execuções.
● Thread Thread-0: Esta é a thread que executa a classe TwoThreads com um
atraso de 500 milissegundos. Ela é responsável por imprimir as mensagens
"Thread-8 count" na tela.
● Thread Thread-1: Esta é a thread que executa a classe TwoThreads com um
atraso de 1000 milissegundos. Ela é responsável por imprimir as mensagens
"Thread-1 count" na tela.

C) Explicação da ordem da saída das threads.

A saída das threads é produzida na seguinte ordem:

1. Thread-8 count
2. Thread-8 count
3. Thread-8 count
4. Thread-1 count
5. Thread-8 count
6. Thread-1 count
7. Thread-1 count
8. Thread-1 count
9. Thread-1 count
A razão para essa ordem é que a thread Thread-0 termina sua execução antes da thread
Thread-1. Isso ocorre porque a thread Thread-0 tem um atraso menor (500
milissegundos) do que a thread Thread-1 (1000 milissegundos). Como resultado, a thread
Thread-0 é capaz de imprimir suas mensagens na tela antes que a thread Thread-1
tenha a chance de começar a imprimir suas mensagens.

D) Efeito da inserção da instrução ti.interrupt(); no final do programa.

Se a instrução ti.interrupt(); for inserida no final do programa, isso fará com que a
thread Thread-1 seja interrompida. Isso significa que a thread Thread-1 parará de
executar seu código e lançará uma exceção InterruptedException.

Como resultado, a thread Thread-1 não imprimirá mais nenhuma mensagem na tela. A
saída do programa será a seguinte:

1. Thread-8 count
2. Thread-8 count
3. Thread-8 count
4. Thread-1 count
5. Thread-8 count

Explicação do efeito da interrupção da Thread-1:

Quando uma thread é interrompida, ela lança uma exceção InterruptedException.


Essa exceção precisa ser tratada pelo código da thread ou a thread será encerrada
abruptamente. No programa fornecido, o código da thread Thread-1 não trata a exceção
InterruptedException. Como resultado, a thread Thread-1 é encerrada abruptamente
quando é interrompida.

Isso significa que a thread Thread-1 não é mais capaz de executar seu código, incluindo a
impressão de mensagens na tela.

Questão 2)

A) Sincronização do método increment():

O uso da palavra-chave synchronized no método increment() garante a sincronização


do acesso à variável batch por várias threads. Isso significa que apenas uma thread pode
executar o método increment() por vez, evitando problemas de concorrência e
garantindo a integridade dos dados.

Se a palavra-chave synchronized não estivesse presente, e várias threads chamassem o


método increment()simultaneamente, o valor da variável batch poderia ser corrompido.
Isso ocorre porque as operações de leitura e escrita em variáveis ​compartilhadas por
threads não são atômicas. Ou seja, elas podem ser interrompidas por outras threads,
levando a resultados imprevisíveis.
B) Sincronização do método value():

A sincronização do método value(), que apenas lê o valor atual de batch, pode parecer
desnecessária à primeira vista. No entanto, existem duas razões pelas quais essa
sincronização pode ser útil:

● Visibilidade da memória: Em sistemas multithread, a memória não é sempre


atualizada instantaneamente para todas as threads. A sincronização garante que a
thread que chama o método value() obtenha o valor mais recente da variável
batch, mesmo que outra thread tenha atualizado o valor logo antes.
● Ordem de execução: Em alguns casos, a ordem de execução dos métodos
increment() e value() pode ser importante. A sincronização garante que o
método value() não retorne um valor antigo logo após o método increment() ter
sido executado por outra thread.

C) Alternativa usando a palavra-chave volatile:

A palavra-chave volatile pode ser usada como uma alternativa à sincronização para
garantir a visibilidade da memória em variáveis ​compartilhadas. Ao declarar uma variável
como volatile, o compilador garante que todas as threads vejam o valor mais recente da
variável, mesmo que não haja sincronização explícita.

No caso do método value(), o uso da palavra-chave volatile seria suficiente para


garantir que a thread que chama o método obtenha o valor mais recente da variável batch.
No entanto, a sincronização ainda pode ser necessária se a ordem de execução dos
métodos increment() e value() for importante.

D) Memória consistente em multithreading:

A memória consistente em multithreading refere-se à propriedade de que todas as threads


em um programa devem ver as mesmas modificações na memória na mesma ordem. Isso
significa que as operações de leitura e escrita em variáveis ​compartilhadas devem ser
atômicas e visíveis a todas as threads na mesma ordem em que foram executadas.

A memória consistente é um desafio para os desenvolvedores de software porque os


sistemas multithread podem apresentar diferentes modelos de memória, cada um com suas
próprias características e implicações. A não compreensão das nuances da memória
consistente pode levar a erros de programação difíceis de detectar e depurar.

Existem várias técnicas para garantir a memória consistente em multithreading, como


sincronização explícita, uso da palavra-chave volatile e barreiras de memória. A escolha
da técnica adequada depende das características específicas do programa e do modelo de
memória do sistema.
Conclusão:

A palavra-chave synchronized e a palavra-chave volatile são ferramentas úteis para


garantir a memória consistente e proteger variáveis ​compartilhadas do acesso simultâneo
por várias threads.

Questão 3)

A) Distinguindo Programas Concorrentes e Paralelos

● Programas concorrentes: Permitem que múltiplos fluxos de execução (threads ou


processos) sejam executados simultaneamente, mas não necessariamente em
paralelo. Isso significa que os threads podem compartilhar recursos, como a CPU,
mas podem ter que esperar um pelo outro para acessar recursos críticos.
● Programas paralelos: Exploram o poder de processamento de vários CPUs ou
núcleos de CPU para executar tarefas simultaneamente. Cada thread ou processo é
executado em sua própria CPU ou núcleo, o que permite que eles trabalhem de
forma independente sem ter que esperar um pelo outro.

B) Avaliação da Paralelização do Código

O código fornecido no problema pode ser adequado para paralelização, pois apresenta as
seguintes características:

● Tarefas independentes: O método blur realiza o embaçamento de linhas de pixels


na imagem de forma independente. Cada linha de pixels pode ser processada sem
depender de outras linhas.
● Granularidade de tarefas: As tarefas de embaçamento são granulares, ou seja,
cada linha de pixels representa uma pequena unidade de trabalho. Isso torna
possível distribuir as tarefas entre vários threads de forma eficiente.
● Potencial de aceleração: A paralelização pode acelerar significativamente o
processo de embaçamento, especialmente em imagens grandes ou em sistemas
com múltiplos CPUs ou núcleos.

No entanto, existem alguns pontos importantes a serem considerados:

● Condições de corrida: É preciso ter cuidado para evitar condições de corrida ao


acessar e modificar os arrays srce dest. Isso pode ser feito usando mecanismos
de sincronização, como bloqueios ou semáforos.
● Sobrecarga: A paralelização pode introduzir sobrecarga adicional, especialmente
em sistemas com poucos CPUs ou núcleos. Isso ocorre porque a criação e
gerenciamento de threads podem consumir recursos de processamento que
poderiam ser usados para realizar o trabalho real.
C) Paralelização usando RecursiveAction

A classe RecursiveAction do Java pode ser usada para paralelizar o código de


embaçamento da seguinte forma:

1. Criar uma subclasse de RecursiveAction: Essa subclasse deve implementar o


método compute() para realizar o embaçamento de uma linha de pixels.
2. Dividir o trabalho: O método blur deve dividir o trabalho em tarefas menores,
cada uma representando o embaçamento de uma linha de pixels.
3. Submeter tarefas ao fork/join framework: O método blur deve usar o fork/join
framework para submeter as tarefas de embaçamento aos threads de trabalho.
4. Gerenciamento de dados: Os dados necessários para as tarefas de embaçamento,
como os arrays src e dest, devem ser passados para as tarefas como argumentos.

Exemplo de implementação:

public class BlurTask extends RecursiveAction {

private final int[] src;

private final int[] dest;

private final int start;

private final int length;

public BlurTask(int[] src, int[] dest, int start, int length) {

this.src = src;

this.dest = dest;

this.start = start;

this.length = length;

@Override

protected void compute() {

// Embaçar a linha de pixels especificada


for (int i = start; i < start + length; i++) {

// ... (código de embaçamento)

public class Util {

public static void blurParallel(int[] src, int[] dest) {

// Dividir o trabalho em tarefas

BlurTask[] tasks = new BlurTask[src.length];

for (int i = 0; i < src.length; i++) {

tasks[i] = new BlurTask(src, dest, i, 1);

// Submeter tarefas ao fork/join framework

ForkJoinPool pool = ForkJoinPool.commonPool();

pool.invokeAll(tasks);

// ... (método blur original)

}
Questão 4)

A) Diferença entre servidores single-threaded e multi-threaded:

Servidor single-threaded:

● Processa solicitações dos clientes uma de cada vez.


● Cada solicitação é executada em um único thread.
● O thread atual espera a solicitação ser concluída antes de processar a
próxima.

Vantagens:

● Simples de implementar e gerenciar.


● Adequado para cargas de trabalho leves.
● Menos propenso a travamentos e deadlocks.

Desvantagens:

● Pode ser lento sob carga pesada.


● Não utiliza recursos de hardware multi-core de forma eficiente.
● Pode ser bloqueado por operações I/O lentas.

Servidor multi-threaded:

● Processa solicitações dos clientes simultaneamente.


● Cada solicitação é executada em um thread separado.
● Os threads podem ser executados em paralelo em CPUs multi-core.

Vantagens:

● Pode ser muito rápido sob carga pesada.


● Utiliza recursos de hardware multi-core de forma eficiente.
● Não é bloqueado por operações I/O lentas.

Desvantagens:

● Mais complexo de implementar e gerenciar.


● Mais propenso a travamentos e deadlocks.
● Pode consumir mais recursos de memória e CPU.
Viabilidade de cada design:

A escolha entre um servidor single-threaded e multi-threaded depende de vários


fatores, como:

● Tipo de carga de trabalho: Se a carga de trabalho for leve e as solicitações


dos clientes forem curtas, um servidor single-threaded pode ser suficiente. Se
a carga de trabalho for pesada ou as solicitações dos clientes forem longas,
um servidor multi-threaded é necessário.
● Recursos de hardware: Se o servidor tiver apenas um único núcleo de CPU,
um servidor single-threaded é a única opção. Se o servidor tiver vários
núcleos de CPU, um servidor multi-threaded pode utilizar esses recursos de
forma mais eficiente.
● Experiência de desenvolvimento: Se os desenvolvedores não tiverem
experiência com programação multi-thread, um servidor single-threaded pode
ser mais fácil de implementar e gerenciar.

Em geral, servidores multi-threaded são mais adequados para a maioria das


aplicações modernas. No entanto, servidores single-threaded ainda podem ser
uma boa opção para aplicações simples com cargas de trabalho leves.

B) Uso de sockets em programas cliente e servidor:

Sockets são interfaces de programação de rede que permitem que os programas


se comuniquem entre si. Eles fornecem uma maneira abstrata de enviar e receber
dados através de uma rede.

Programas cliente:

● Criam um socket cliente e se conectam a um socket servidor em um


endereço específico.
● Enviam dados para o socket servidor através da função send() ou
write().
● Recebem dados do socket servidor através da função recv() ou read().
● Fecham o socket quando a comunicação estiver concluída.
Exemplo de código Java:

import java.net.*;
import java.io.*;

public class Cliente {

public static void main(String[] args) throws IOException {


// Cria um socket cliente
Socket socket = new Socket("localhost", 8080);

// Obtém o fluxo de saída do socket


OutputStream out = socket.getOutputStream();

// Envia uma mensagem para o servidor


out.write("Olá servidor!".getBytes());

// Obtém o fluxo de entrada do socket


InputStream in = socket.getInputStream();

// Lê a resposta do servidor
byte[] buffer = new byte[1024];
int bytesRead = in.read(buffer);

// Imprime a resposta do servidor


System.out.println(new String(buffer, 0, bytesRead));

// Fecha o socket
socket.close();
}
}

Programas servidor:

● Criam um socket servidor e se vinculam a um endereço específico.


● Escutam por novas conexões através da função accept().
● Aceitam conexões de clientes e criam um novo socket para cada cliente.
● Recebem dados dos clientes através da função recv() ou read().
● Enviam dados para os clientes através da função send() ou write().
● Fecham os sockets quando a comunicação estiver concluída.
Exemplo de código Java:

import java.net.*;
import java.io.*;

public class Servidor {

public static void main(String[] args) throws IOException {


// Cria um socket servidor e se vincula à porta 8080
ServerSocket serverSocket =

Você também pode gostar