English | 简体中文 | 繁體中文 | Русский язык | Français | Español | Português | Deutsch | 日本語 | 한국어 | Italiano | بالعربية

Panoramica dei problemi di concorrenza Java

1 Cos'è un problema di concorrenza.

L'accesso simultaneo di più processi o thread a una risorsa nello stesso momento o in un breve periodo di tempo può causare problemi di concorrenza.

Un esempio tipico è due operatori bancari che operano contemporaneamente sullo stesso conto. Ad esempio, gli operatori A e B leggono contemporaneamente un conto con un saldo di 1000 yuan, l'operatore A aggiunge 100 yuan al conto, l'operatore B sottrae 50 yuan dal conto contemporaneamente. L'operatore A invia prima, l'operatore B invia dopo. Alla fine, il saldo effettivo del conto è 1000 - 50 = 950 yuan, ma dovrebbe essere 1000 + 100 - 50 = 1050 yuan. Questo è un problema di concorrenza tipico. Come risolverlo? Puoi usare un lock.

2 L'uso di synchronized in Java

Uso 1

public class Test{
	public synchronized void print(){
		...;
	}
}

Quando un thread esegue il metodo print(), l'oggetto viene bloccato. Gli altri thread non possono eseguire tutti i blocchi synchronized dell'oggetto.

Uso 2

public class Test{
	public void print(){
		synchronized(this){
			//Bloccare l'oggetto corrente 
			...;
		}
	}
}

Simile all'uso 1, ma更能体现synchronized用法的本质。

Uso 3

public class Test{
	private String a = "test";
	public void print(){
		synchronized(a){
			//Bloccare l'oggetto a 
			...;
		}
	}
	public synchronized void t(){
		...;
		//Questo blocco di codice sincronizzato non sarà bloccato da print().
	}
}

L'esecuzione di print() aggiungerà il blocco di lock all'oggetto a, notare che non blocca l'oggetto Test, quindi i metodi synchronized dell'oggetto Test non saranno bloccati da print(). Dopo l'esecuzione del blocco di codice sincronizzato, viene rilasciato il blocco di lock su a.

Ecco un modo efficiente per bloccare un blocco di codice di un oggetto senza influire sugli altri blocchi synchronized dell'oggetto stesso:

public class Test{
	private byte[] lock = new byte[0];
	public void print(){
		synchronized(lock){
			...;
		}
	}
	public synchronized void t(){
		...;
	}
}

Blocco statico del metodo

public class Test{
	public synchronized static void execute(){
		...;
	}
}

L'effetto è lo stesso

public class Test{
	public static void execute(){
		synchronized(TestThread.class){
			...;
		}
	}
}

3 I blocchi in Java e la coda per l'uso del bagno.

Il blocco è un modo per prevenire che altri processi o thread accedano a risorse, ovvero le risorse bloccate non possono essere accedute da altre richieste. In JAVA, la parola chiave synchronized viene usata per bloccare un oggetto. Ad esempio:

public class MyStack {
	int idx = 0;
	char [] data = new char[6];
	public synchronized void push(char c) {
		data[idx] = c;
		idx++;
	}
	public synchronized char pop() {
		idx--;
		return data[idx];
	}
	public static void main(String args[]){
		MyStack m = new MyStack();
		/**
    Di seguito l'oggetto m è bloccato. In realtà, sono bloccati tutti i blocchi synchronized dell'oggetto m.
    Se esiste un altro thread T che tenta di accedere a m, allora T non può eseguire il push e
    Metodo pop.
  */
		m.pop();
		//L'oggetto m è bloccato.
	}
}

La locking e unlocking in Java è esattamente come se molte persone fossero in fila per un bagno pubblico. La prima persona che entra chiude la porta da dentro, gli altri devono aspettare in fila. Quando la prima persona esce, la porta viene aperta (rilasciata). Quando arriva la seconda persona, fa lo stesso, chiude la porta da dentro e gli altri continuano ad aspettare in fila.

La teoria del bagno può essere facilmente comprensibile: quando una persona entra in una cabina, questa cabina viene bloccata, ma non provoca che un'altra cabina venga bloccata, perché una persona non può sedersi contemporaneamente in due cabine. Per Java significa: i blocchi in Java sono per lo stesso oggetto, non per la classe. Guarda l'esempio:

MyStatckm1 = new MyStack();
MyStatckm2 = new MyStatck();
m1.pop();
m2.pop();

Il blocco di m1 non influisce sul blocco di m2, perché non sono nella stessa cabina. Questo significa che, supponiamo che ci siano 3 thread t1, t2, t3 che operano su m1, allora questi 3 thread possono aspettare solo su m1. Supponiamo che altri 2 thread t8, t9 operino su m2, allora t8, t9 aspetteranno solo su m2. E t2 e t8 non sono correlati, anche se il blocco su m2 viene rilasciato, t1, t2, t3 potrebbero ancora aspettare su m1. La ragione è semplice, non sono nella stessa cabina.

Java non può aggiungere due blocchi a un blocco di codice contemporaneamente, il che è diverso dal meccanismo di blocco del database, che può aggiungere diversi tipi di blocco a un record contemporaneamente.

4 Quando rilasciare il blocco?

Di solito, dopo aver completato il blocco sincronizzato (il blocco bloccato), si rilascia il blocco, ma può anche essere rilasciato tramite wait(). Il modo wait() è come se si stesse sedendo in bagno a metà e improvvisamente si scoprisse che il lavandino è bloccato e si dovesse uscire da parte per lasciare che lo scrittore (un thread pronto per eseguire notify) entri per riparare il WC. Dopo aver risolto il problema, lo scrittore grida: "Riparato!" E i compagni che erano usciti rientrano in coda. Attenzione, devono aspettare che lo scrittore usciva, se non esce, nessuno può entrare. Questo significa che dopo notify, non è che altri thread possano entrare immediatamente nella regione bloccata, ma devono aspettare che il blocco dove si trova il codice notify sia completato e rilasci il blocco prima che altri thread possano entrare.

Questo è un esempio di codice wait e notify:

public synchronized char pop() {
	char c;
	while (buffer.size() == 0) {
		try {
			this.wait();
			// Uscire dal water
		}
		catch (InterruptedException e) {
			// Ignorare...
		}
	}
	c = ((Character)buffer.remove(buffer.size()-1)). 
	charValue();
	return c;
}
public synchronized void push(char c) {
	this.notify();
	// Notificare i thread wait() per riorganizzarsi in coda. Attenzione: è solo una notifica per loro. 
	Character charObj = new Character(c);
	buffer.addElement(charObj);
}
// Operazione completata, rilasciare il blocco. I thread in coda possono entrare.

Andiamo più a fondo.

I compagni che sono usciti a metà a causa dell'operazione wait() non riceveranno il segnale notify prima di non rientrare più nella coda, resteranno a guardare queste persone in coda (tra cui anche il idraulico). Attenzione, l'idraulico non può saltare la coda, deve fare la coda come chi va in bagno, non è che se qualcuno è uscito a metà e torna indietro, l'idraulico può improvvisamente saltare fuori e entrare immediatamente per la riparazione. Deve competere in modo equo con la gente che era in coda prima, perché anche lui è un thread normale. Se l'idraulico è in coda, quando la persona davanti entra e trova il blocco, aspetta, poi esce a stare da parte, entra un altro, aspetta, esce, sta da parte, fino a che lo scrittore entra per eseguire notify. In questo modo, in breve tempo, accanto alla coda ci saranno molte persone che aspettano notify.

Finalmente, il maestro entra e chiama notify, poi cosa succede?

1. C'è qualcuno che aspetta (thread) che viene notificato.

2. Perché è stato notificato lui e non un altro? Dipende dal JVM. Non possiamo prevedere

Determinare quale sarà notificato. Questo significa che i thread con priorità più alta non vengono necessariamente svegliati prima, in attesa

Non è necessariamente svegliato per il tempo più lungo, tutto è imprevedibile! (Naturalmente, se conosci il JVM)

Implementazione, allora è prevedibile).

3. Dovrà fare la coda di nuovo.

4. Andrà in testa alla coda? La risposta è: non necessariamente. Andrà in coda? Anche questo non è necessario.

Ma se la priorità del thread è più alta, la probabilità di essere in coda è maggiore.

5. Quando arriva il suo turno di tornare nella coda, riprenderà dall'ultimo punto wait(). Non eseguirà di nuovo.

Per essere schietti, continuerà a fare pipì senza ricominciare.

6. Se il maestro chiama notifyAll(). Tutti quei che hanno iniziato ma non sono arrivati a fine processo torneranno a fare la coda. L'ordine è sconosciuto.

Secondo la JavaDOC, Theawakenedthreadswillnotbeabletoproceeduntilthecurrentthreadrelinquishesthelockonthisobject (finché il thread corrente non rilascia il lock su questo oggetto, i threadawakens non possono procedere).

Questo è ovvio spiegato dalla teoria del numero di posti.

Utilizzo di 5Lock

Il sintagma 'synchronized' può essere utilizzato per bloccare le risorse. Anche la parola 'Lock' può essere utilizzata. È una nuova funzionalità aggiunta in JDK1.5. L'uso è come segue:

class BoundedBuffer {
	final Lock lock = new ReentrantLock();
	final Condition notFull = lock.newCondition();
	final Condition notEmpty = lock.newCondition();
	final Object[] items = new Object[100];
	int putptr, takeptr, count;
	public void put(Object x) throws InterruptedException {
		lock.lock();
		try {
			while (count == items.length) 
			      notFull.await();
			items[putptr] = x;
			if (++putptr == items.length) putptr = 0;
			++count;
			notEmpty.signal();
		}
		finally {
			lock.unlock();
		}
	}
	public Object take() throws InterruptedException {
		lock.lock();
		try {
			while (count == 0) 
			      notEmpty.await();
			Object x = items[takeptr];
			if (++takeptr == items.length) takeptr = 0;
			--count;
			notFull.signal();
			return x;
		}
		finally {
			lock.unlock();
		}
	}
}

(Nota: questo è un esempio di JavaDoc, un esempio di implementazione di coda bloccata. Una coda bloccata è una coda che, se piena o vuota, blocca i thread in attesa. In Java, ArrayBlockingQueue fornisce una coda bloccata pronta all'uso, non è necessario scriverne una personalizzata.)

Il codice tra lock.lock() e lock.unlock() di un oggetto sarà bloccato. In cosa questo metodo è migliore rispetto a synchronize? In breve, categorizza i thread che aspettano. Utilizzando la teoria delle toilette per descrivere, è come se le persone che hanno iniziato a usare il water e poi sono uscite per aspettare avessero motivi diversi, alcuni perché il water è bloccato, altri perché non c'è acqua. Quando si notifica, si può gridare: chi aspettava perché il water era bloccato può tornare a fare la coda (ad esempio, il problema di blocco del water è stato risolto), o gridare, chi aspettava perché non c'era acqua può tornare a fare la coda (ad esempio, il problema di assenza di acqua è stato risolto). Questo permette di controllare in modo più dettagliato. A differenza di wait e notify all'interno di synchronize, sia che il water sia bloccato o che non ci sia acqua, si può solo gridare: chi aspettava può tornare a fare la coda! Se le persone nella coda entrano e vedono che il problema di blocco del water è stato risolto, ma il problema che desideravano risolvere (assenza di acqua) non è ancora stato risolto, devono tornare a aspettare(wait), fare un giro inutilmente, sprecando tempo e risorse.

Corrispondenza tra Lock e synchronized:

LockawaitsignalsignalAll

synchronizedwaitnotifynotifyAll

Attenzione: non chiamare wait, notify o notifyAll all'interno di un blocco bloccato in modalità Lock

6 Utilizzare i tubi per la comunicazione tra thread

Il principio è semplice. Due thread, uno che opera su PipedInputStream e l'altro su PipedOutputStream. I dati scritti da PipedOutputStream vengono prima conservati nel Buffer, se il Buffer è pieno, questo thread wait. PipedInputStream legge i dati dal Buffer, se il Buffer non ha dati, questo thread wait.

Le coda bloccata in JDK1.5 può implementare la stessa funzione.

package io;
import java.io.*;
public class PipedStreamTest {
	public static void main(String[] args) {
		PipedOutputStream ops=new PipedOutputStream();
		PipedInputStream pis=new PipedInputStream();
		try{
			ops.connect(pis);
			//实现管道连接 
			new Producer(ops).run();
			new Consumer(pis).run();
		}
		catch(Exception e){
			e.printStackTrace();
		}
	}
}
//生产者 
class Producer implements Runnable{
	private PipedOutputStream ops;
	public Producer(PipedOutputStream ops) 
	{
		this.ops=ops;
	}
	public void run()
	{
		try{
			ops.write("hell,spell".getBytes());
			ops.close();
		}
		catch(Exception e)
		    {
			e.printStackTrace();
		}
	}
}
//消费者 
class Consumer implements Runnable{
	private PipedInputStream pis;
	public Consumer(PipedInputStream pis) 
	{
		this.pis=pis;
	}
	public void run()
	{
		try{
			byte[] bu=new byte[100];
			int len=pis.read(bu);
			System.out.println(new String(bu,0,len));
			pis.close();
		}
		catch(Exception e)
		    {
			e.printStackTrace();
		}
	}
}

Esempio 2: Aggiungere alcune modifiche al programma sopra menzionato rende due thread.

package io;
import java.io.*;
public class PipedStreamTest {
	public static void main(String[] args) {
		PipedOutputStream ops=new PipedOutputStream();
		PipedInputStream pis=new PipedInputStream();
		try{
			ops.connect(pis);
			//实现管道连接 
			Producer p = new Producer(ops);
			new Thread(p).start();
			Consumer c = new Consumer(pis);
			new Thread(c).start();
		}
		catch(Exception e){
			e.printStackTrace();
		}
	}
}
//生产者 
class Producer implements Runnable{
	private PipedOutputStream ops;
	public Producer(PipedOutputStream ops) 
	{
		this.ops=ops;
	}
	public void run()
	{
		try{
			for (;;){
				ops.write("hell,spell".getBytes());
				ops.close();
			}
		}
		catch(Exception e)
		    {
			e.printStackTrace();
		}
	}
}
//消费者 
class Consumer implements Runnable{
	private PipedInputStream pis;
	public Consumer(PipedInputStream pis) 
	{
		this.pis=pis;
	}
	public void run()
	{
		try{
			for (;;){
				byte[] bu=new byte[100];
				int len=pis.read(bu);
				System.out.println(new String(bu,0,len));
			}
			pis.close();
		}
		catch(Exception e)
		    {
			e.printStackTrace();
		}
	}
}

Esempio 3. Questo esempio è più vicino all'applicazione.

import java.io.*;
public class PipedIO {
	//程序运行后将sendFile文件的内容拷贝到receiverFile文件中 
	public static void main(String args[]){
		try{
			//构造读写的管道流对象 
			PipedInputStream pis=new PipedInputStream();
			PipedOutputStream pos=new PipedOutputStream();
			//实现关联 
			pos.connect(pis);
			//构造两个线程,并且启动。 
			new Sender(pos,”c:\text2.txt”).start();
			new Receiver(pis,”c:\text3.txt”).start();
		}
		catch(IOException e){
			System.out.println(“Pipe Error”+ e);
		}
	}
}
//线程发送 
class Sender extends Thread{
	PipedOutputStream pos;
	File file;
	//构造方法 
	Sender(PipedOutputStream pos, String fileName){
		this.pos=pos;
		file=new File(fileName);
	}
	//线程运行方法 
	public void run(){
		try{
			//读文件内容 
			FileInputStream fs=new FileInputStream(file);
			int data;
			while((data=fs.read())!=-1){
				//写入管道始端 
				pos.write(data);
			}
			pos.close();
		}
		catch(IOException e) {
			System.out.println(“Sender Error” +e);
		}
	}
}
//线程读 
class Receiver extends Thread{
	PipedInputStream pis;
	File file;
	//构造方法 
	Receiver(PipedInputStream pis, String fileName){
		this.pis=pis;
		file=new File(fileName);
	}
	//线程运行 
	public void run(){
		try {
			//写文件流对象 
			FileOutputStream fs=new FileOutputStream(file);
			int data;
			//Leggi dalla fine del tubo 
			while((data=pis.read())!=-1){
				//Scrivi su un file locale    
				fs.write(data);
			}
			pis.close();
		}
		catch(IOException e){
			System.out.println("Error del ricevitore" + e);
		}
	}
}

7 Coda bloccata

La coda bloccata può sostituire il modo di flusso dei tubi per implementare il modello di tubazione ad acqua/scarico (produttore/consumatore). La JDK1.5 fornisce alcune code bloccate pronte all'uso. Ora vediamo il codice di ArrayBlockingQueue:

Ecco una coda bloccata

BlockingQueue blockingQ = new ArrayBlockingQueue 10; 

Un thread prende dalla coda

for(;;){ 
Object o = blockingQ.take();//Quando la coda è vuota, aspetta (blocca) 
} 

Un altro thread mette nella coda

for(;;){ 
blockingQ.put(new Object());//Quando la coda è piena, aspetta (blocca) 
} 

È chiaro che l'uso della coda bloccata è più semplice rispetto a quello dei tubi.

8 Utilizzare Executors, Executor, ExecutorService, ThreadPoolExecutor

È possibile gestire le attività utilizzando la gestione delle thread. Inoltre, è possibile utilizzare un insieme di classi fornite dalla JDK1.5 per gestire più facilmente le attività. Da queste classi possiamo cogliere un modo di pensare orientato alle attività. Queste classi sono:

Interfaccia Executor. Modo d'uso:

Executor executor = anExecutor;//Genera un'istanza di Executor. 
executor.execute(new RunnableTask1()); 

L'intenzione è che l'utente si preoccupi solo dell'esecuzione delle attività, senza doversi preoccupare della creazione delle attività, delle dettagli di esecuzione e di altri problemi che i realizzatori di terze parti si preoccupano. In altre parole, decouplare la chiamata di esecuzione delle attività dalla loro implementazione.

In realtà, nella JDK1.5 esiste già un'implementazione eccellente di questa interfaccia. Basta.

Executors è una classe di fabbrica o una classe di strumenti come Collections, utilizzata per generare istanze di vari interfacce.

L'interfaccia ExecutorService, che eredita da Executor.Executor, si occupa solo di gettare i compiti all'interno di executor() per eseguirli, e non si preoccupa di altre cose. Invece, ExecutorService fa un po' più di lavoro di controllo. Ad esempio:

class NetworkService {
	private final ServerSocket serverSocket;
	private final ExecutorService pool;
	public NetworkService(int port, int poolSize) throws IOException {
		serverSocket = new ServerSocket(port);
		pool = Executors.newFixedThreadPool(poolSize);
	}
	public void serve() {
		try {
			for (;;) {
				pool.execute(new Handler(serverSocket.accept()));
			}
		}
		catch (IOException ex) {
			pool.shutdown();
			// Non eseguire nuove attività
		}
	}
}
class Handler implements Runnable {
	private final Socket socket;
	Handler(Socket socket) {
		this.socket = socket;
	}
	public void run() {
		// Leggere e servire la richiesta
	}
}

Dopo che ExecutorService (ovvero l'oggetto pool nel codice) ha eseguito shutdown, non può più eseguire nuove attività, ma le attività esistenti continueranno a essere eseguite fino alla fine, e le attività in attesa non aspetteranno più.

Comunicazione tra il mittente delle attività e l'esecutore

public static void main(String args[])throws Exception {
	ExecutorService executor = Executors.newSingleThreadExecutor();
	Callable task = new Callable(){
		public String call()throws Exception{
			return "test";
		}
	}
	;
	Future f = executor.submit(task);
	String result = f.get();
	// Attendere (bloccare) il ritorno del risultato 
	System.out.println(result);
	executor.shutdown();
}

L'istanza di Executor ottenuta da Executors.newSingleThreadExecutor() ha le seguenti caratteristiche:

Esecuzione in ordine dei compiti. Ad esempio:}}

executor.submit(task1); 
executor.submit(task2); 

È necessario aspettare che task1 sia completato prima di eseguire task2.

task1 e task2 saranno messi in una coda, trattati da una thread di lavoro. Ciò significa: ci sono in totale 2 thread (thread principale, thread che gestisce i compiti).

Altre classi si referiscono al JavaDoc

Controllo di flusso concorrente 9

Questo esempio è tratto dal tutorial di Java concorrente di Wen, potrebbe esserci delle modifiche. Omaggio a Wen.

Contatore di blocco CountDownLatch

Avvia le thread, quindi attendi la fine delle thread. Questo è il problema comune di quando la thread principale deve aspettare la fine di tutte le thread figlie prima di eseguire.

public static void main(String[] args)throws Exception {
	// TODO metodo stub generato automaticamente 
	final int count=10;
	final CountDownLatch completeLatch = new CountDownLatch(count);
	// Definizione del numero di barre di sicurezza della porta è 10
	for (int i=0;i<count;i++){
		Thread thread = new Thread("worker thread"+i){
			public void run(){
				// fare xxxx                  
				completeLatch.countDown();
				//Ridurre una chiave di porta
			}
		}
		;
		thread.start();
	}
	completeLatch.await();
	// Se la barra di sicurezza della porta non è stata ridotta completamente, attendi.
}

Nel JDK1.4, il metodo comune era impostare lo stato delle thread figlie, e la thread principale controllava in ciclo. La facilità d'uso e l'efficienza non erano buone.

Avvia molte thread, attendi la notifica prima di iniziare

public static void main(String[] args) throws Exception {
	// TODO metodo stub generato automaticamente 
	final CountDownLatch startLatch = new CountDownLatch(1);
	// Definizione di una barra di sicurezza della porta
	for (int i = 0; i < 10; i++) {
		Thread thread = new Thread("worker thread" + i) {
			public void run() {
				try {
					startLatch.await();
					// Se la barra di sicurezza della porta non è stata ridotta completamente, attendi
				}
				catch (InterruptedException e) {
				}
				// do xxxx
			}
		}
		;
		thread.start();
	}
	startLatch.countDown();
	//Ridurre una chiave di porta
}

CycliBarrier. Può continuare a eseguire solo dopo che tutti i thread hanno raggiunto una linea di partenza.

public class CycliBarrierTest implements Runnable {
	private CyclicBarrier barrier;
	public CycliBarrierTest(CyclicBarrier barrier) {
		this.barrier = barrier;
	}
	public void run() {
		//do xxxx;
		try {
			this.barrier.await();
			//Il thread controlla se tutti gli altri thread sono arrivati qui, se non sono arrivati, continua a aspettare. Se sono arrivati, esegue il contenuto della funzione run di barrier
		}
		catch (Exception e) {
		}
	}
	/**
 * @param args
 */
	public static void main(String[] args) {
		//Parametro 2 rappresenta che entrambi i thread devono raggiungere la linea di partenza per continuare insieme
		CyclicBarrier barrier = new CyclicBarrier(2, new Runnable() {
			public void run() {
				//do xxxx;
			}
		}
		);
		Thread t1 = new Thread(new CycliBarrierTest(barrier));
		Thread t2 = new Thread(new CycliBarrierTest(barrier));
		t1.start();
		t2.start();
	}
}

Questo semplifica il metodo tradizionale di implementazione di questa funzione utilizzando un contatore + wait/notifyAll.

Legge dei 10 concorrenti 3

Legge di Amdahl. Data la scala del problema, la parte che può essere parallelizzata rappresenta il 12%. Anche se si utilizza al massimo la parallelizzazione, la prestazione del sistema può migliorare al massimo di 1/(1-0.12)=1.136 volte. Questo significa che la parallelizzazione ha un limite per migliorare la prestazione del sistema.

Legge di Gustafson. La legge di Gustafson afferma che la legge di Amdahl non tiene conto dell'aumento della capacità di calcolo disponibile con l'aumento del numero di cpu. La sua essenza risiede nel modificare la scala del problema in modo da rendere paralleli quei rimanenti 88% di elaborazione sequenziale previsti dalla legge di Amdahl, superando così il limite di prestazioni. In sostanza, è un cambio di spazio nel tempo.

La legge di Sun-Ni è una ulteriore estensione delle prime due leggi. La sua idea principale è che la velocità di calcolo è limitata dalla memoria piuttosto che dalla velocità del CPU. Pertanto, è necessario sfruttare al massimo lo spazio di archiviazione e altri risorse di calcolo, aumentare la scala del problema per produrre soluzioni migliori/più precise.

11 Dalla concorrenza alla parallela

La riconoscimento degli oggetti da parte del computer richiede calcoli a velocità elevata, tanto da far scaldare e surriscaldare i chip, mentre la persona riconosce gli oggetti in modo chiaro e non provoca discomfort a causa della cellula cerebrale bruciata e carbonizzata (esagerazione), poiché il cervello è un sistema di esecuzione parallela distribuita, proprio come Google utilizza alcuni server Linux a basso costo per eseguire calcoli complessi e vasti. Il calcolo autonomo di milioni di neuroni interni, che condividono i risultati l'uno con l'altro, completa istantaneamente l'effetto che richiederebbe miliardi di operazioni di CPU singola. Immaginiamo se ci fossero innovazioni nel campo del processamento parallelo, avrebbero un impatto incalcolabile sullo sviluppo dei computer e sul futuro. Certamente, le sfide sono immaginabili: molte questioni non sono facili da

Sommario

Questo è tutto il contenuto di questo articolo sull'overview dei problemi di concorrenza in Java, spero sia utile a tutti. Chi è interessato può continuare a consultare altre rubriche correlate su questo sito, e se c'è qualcosa di insufficiente, ti preghiamo di lasciare un messaggio per segnalare. Grazie per il supporto degli amici a questo sito!

Dichiarazione: il contenuto di questo articolo è stato tratto da Internet, il copyright è proprietà del rispettivo proprietario, il contenuto è stato contribuito volontariamente dagli utenti di Internet e caricato autonomamente, questo sito non possiede i diritti di proprietà, non è stato elaborato manualmente e non assume responsabilità per le relative responsabilità legali. Se trovi contenuti sospetti di violazione del copyright, ti preghiamo di inviare una e-mail a: notice#oldtoolbag.com (al momento dell'invio dell'e-mail, sostituisci # con @) per segnalare, fornendo prove pertinenti. Una volta verificata, questo sito eliminerà immediatamente il contenuto sospetto di violazione del copyright.

Ti potrebbe interessare