MS Lesson26: Threads

1. Process - emeliyyat sistemi terefinden ishe salinan, oz ayrica yaddash sahesine malik olan programin icra nusxesidir. 

Mes biz kompumuzda muxtelif programlar acanda Paint, Excel ve s. bunlar ferqli proseslerdir. 

OS her bir prosese ferqli registerler, stack memory, heap memory assign edir. 


Restoran misali:

Restoran = process

Bina = yaddash (RAM)

Ishciler = thread


Yeni process ishe salinmish bir programdir.


* Her proses oz yaddashinda ishleyir, bashqasina toxuna bilmez.

* OS her prosese unikal ID verir.

* Yaratmaq / silmek bahalidir.

* Biri crash olsa, digerine tesir etmir. 


Bir prosesin icinde coxlu thread ola biler. Amma bir thread yalniz bir prosese aid ola biler. 

Java kodu bashlayanda JVM ozu de bir nece thread yaradir:

* main thread - bizim kodumuz

* GC thread - Garbage Collector

* Finalizer thread

* Signal Dispatcher thread


2. Thread - process icinde ishleyen ayrica ish axinidir. Kodun setir setir icra olundugu yoldur. 

Restoran - process

Ashpaz 1 - pizza bishirir - thread1

Ashpaz 2 - salat hazirlayir - thread2

Ashpaz 3 - desert duzeldir - thread3

Hami eyni anda ishleyir - paralel. 

Hami eyni metbexte ishelyir - eyni yaddash.



3. Time Slicing

CPU - tekce beyin deyil, milyardlarla tranzistordan ibaret cipdir. Tranzistor elektrik kecirik ya kecirmir 0 ve 1. 




Core - CPU-nun icinde musteqil bir ishcidir. Ona kod verirsen o da icra edir, bashqa hecne bilmir. 

CPU - fabrika, Core - fabrikadaki ishci.

Core-un icinde 4 esas sahe var:

1. Fetch unit: "Novbeti emri getir"

RAM-da bizim Java kodumuz durur:

Unvan 100: int a = 5;

Unvan 104: int b = 3;

Unvan 108: int c = a + b;

Fetch unit: 

"Unvan 100 get -> Decode ver"

"Unvan 104 get -> Decode var"

...

Problemi: RAM yavashdir, 70 nanosaniye gozlemek lazimdir, Core ise 0.3 nanosaniyede ishleyir. Yeni Core RAM-dan 200x daha suretlidir. Buna gore Core-un daxilinde oz cache-i var. 


2. Decode unit: "Bu emr ne demekdir?"

Tercumeci, genen emri CPU diline cevirir. 

Sən yazırsan:

int c = a + b;


JVM çevirir (Bytecode):

iload_1      ← a-nı yüklə

iload_2      ← b-ni yüklə

iadd         ← topla

istore_3     ← c-yə yaz


CPU görür (Machine Code):

10110000 00000101

10110011 00000011

00000001 11000011


Decode unit bunlari oxuyur ve deyir:

"A-nı Register-ə yüklə"

"B-ni Register-ə yüklə"  

"İkisini ALU-da topla"

"Nəticəni Register-ə yaz"


3. ALU: "Hesablama et"

Arithmetic Logic Unit - butun hesablama burada gedir. 


4. Register: "Neticeni saxla"

Yaddaş növləri sürətə görə:


Register  │██████████████████████│ 0.3 ns  ← Core-un içi

L1 Cache  │████████████████      │ 1   ns

L2 Cache  │████████              │ 5   ns

L3 Cache  │████                  │ 15  ns

RAM       │█                     │ 70  ns  ← 200x yavaş!

SSD       │                      │ 100 µs

HDD       │                      │ 10  ms


Core-da cəmi ~16 Register var Hər biri 64 bit = 8 byte saxlayır
















Bir core eyni anda yalniz 1 ish gore biler. Amma ele suretli novbe ile kecir ki, sanki hamisini eyni anda icra edir. 
Heqiqet: Core -> eyni anda yalniz 1 thread icra edir. 
Gorunush: Core -> sanki 100 thread-i eyni anda idare edir. 

Thread 1 işləyir:
├── Register-lər: a=5, b=3, c=8
├── Hansı sətirdəyəm: 42-ci sətir
└── Stack-da nələr var: method çağırışları

OS deyir: "Vaxtın bitdi! T2-nin sırası!"

Context Switch baş verir:
├── T1-in bütün vəziyyəti SAXLANILIR (RAM-a)
│   └── Register-lər, neçənci sətir, stack...
├── T2-nin əvvəlki vəziyyəti YÜKLƏNİR (RAM-dan)
│   └── T2 sanki heç dayanmamış kimi davam edir
└── Core indi T2-ni icra edir



*** Her bir Thread-in oz Stack-i var.


Thread Lifecycle:






package az.etibarli;

class Runner1 {

public void execute() {
for (int i = 0; i < 10; i++) {
System.out.println("Runner1: " + i);
}
}

}

class Runner2 {

public void execute() {
for (int i = 0; i < 10; i++) {
System.out.println("Runner2: " + i);
}
}

}

public class Main {

static void main() {
Runner1 runner1 = new Runner1();
Runner2 runner2 = new Runner2();

runner1.execute();
runner2.execute();
}

}



indi ise ferqe baxaq:

package az.etibarli;

class Runner1 implements Runnable {

@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println("Runner1: " + i);
}
}
}

class Runner2 implements Runnable {

@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println("Runner2: " + i);
}
}

}

public class Main {

static void main() {
Thread thread1 = new Thread(new Runner1());
Thread thread2 = new Thread(new Runner2());

thread1.start();
thread2.start();
}

}







join() metodu main thread-a deyirki men ishimi bitirene kimi gozle:

package az.etibarli;

class Runner1 implements Runnable {

@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println("Runner1: " + i);
}
}
}

class Runner2 implements Runnable {

@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println("Runner2: " + i);
}
}

}

public class Main {

static void main() throws InterruptedException {
System.out.println("Start");
Thread thread1 = new Thread(new Runner1());
Thread thread2 = new Thread(new Runner2());

thread1.start();
thread2.start();

thread1.join();
System.out.println("Finish");
}

}


ikinci thread-a Thread.sleep() elave etsek o zaman ferqi goreceyik:

package az.etibarli;

class Runner1 implements Runnable {

@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println("Runner1: " + i);
}
}
}

class Runner2 implements Runnable {

@Override
public void run() {
for (int i = 0; i < 10; i++) {
try {
Thread.sleep(1000);
} catch (Exception e) {
e.printStackTrace();
}
System.out.println("Runner2: " + i);
}
}

}

public class Main {

static void main() throws InterruptedException {
System.out.println("Start");
Thread thread1 = new Thread(new Runner1());
Thread thread2 = new Thread(new Runner2());

thread1.start();
thread2.start();

thread1.join();
System.out.println("Finish");
}

}



Ardicil veya paralel ishlemelerini subut etmek ucun bele bir kod numunesi:

package az.etibarli;


public class Main {

static long heavyWork() {
long result = 0;
for (long i = 0; i < 1_000_000_000L; i++) {
result += i;
}
return result;
}

static void main() throws InterruptedException {
System.out.println("=== 1 Thread ===");
long start1 = System.currentTimeMillis();

heavyWork();
heavyWork();

long end1 = System.currentTimeMillis();
System.out.println("1 Thread time: " +
(end1 - start1) + "ms");

System.out.println("\n=== 2 Thread ===");
long start2 = System.currentTimeMillis();

Thread t1 = new Thread(() -> heavyWork());
Thread t2 = new Thread(() -> heavyWork());

t1.start();
t2.start();

t1.join();
t2.join();

long end2 = System.currentTimeMillis();
System.out.println("2 Thread time: " +
(end2 - start2) + "ms");
}

}



JVM-de olan threadleri cap etmek:

package az.etibarli;

class Runner1 implements Runnable {

@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println("Runner1: " + i);
}
}
}

class Runner2 implements Runnable {

@Override
public void run() {
for (int i = 0; i < 10; i++) {
try {
Thread.sleep(1000);
} catch (Exception e) {
e.printStackTrace();
}
System.out.println("Runner2: " + i);
}
}

}

public class Main {

static void main() throws InterruptedException {
System.out.println("Start");
Thread thread1 = new Thread(new Runner1());
Thread thread2 = new Thread(new Runner2());

thread1.start();
thread2.start();

// list of threads in JVM
for (Thread t : Thread.getAllStackTraces().keySet()) {
System.out.println("Thread name: " + t.getName() + ", State: " + t.getState());
}

thread1.join();
System.out.println("Finish");
}

}






                                                        Daemon threads and user threads

1. User threads.

User thread - bizim yaratdigimiz ve JVM user thread-i bitene qeder gozleyir. 







2. Worker threads

Arxa planda konkret bir ish gormek ucun yaradilan thread-lere deyilir. Worker thread-ler en cox Thread Pool-da istifade olunur.  Worker thread = User thread.
Tomcat-in thread-lerini worker thread saymaq olar.






JVM worker veya user thread-leri kill etmir.

Lakin Daemon thread-leri kill edir. Eger hec bir user/worker thread yoxdursa JVM thread-leri kill edir. 

package az.etibarli;

class NormalWorker implements Runnable {

public void run() {
try {
Thread.sleep(1000);
} catch (Exception e) {
e.printStackTrace();
}
System.out.println("Executing the normal thread...");
}

}

class DaemonWorker implements Runnable {

public void run() {
while (true) {
try {
Thread.sleep(100);
} catch (Exception e) {
e.printStackTrace();
}
System.out.println("Executing the daemon thread...");
}
}

}

public class Main {

static void main() throws InterruptedException {
System.out.println("Started...");

Thread t1 = new Thread(new NormalWorker());
Thread t2 = new Thread(new DaemonWorker());
t2.setDaemon(true);
t1.start();
t2.start();
System.out.println("Finished...");
}

}


* User Thread - perform core application task. JVM waits for user threads to finish. All threads are user threads by default. Can have any priority. Acts as foreground threads.
* Daemon Thread - background thread support user thread. JVM does not wait for daemon threads. Must explicitly set using setDaemon(true). Can also have any priority, but usually lower. Acts as background service threads.



                                                                Thread priority


package az.etibarli;

class Task implements Runnable {

public void run() {
for (int i = 0; i < 5; i++) {
System.out.println(Thread.currentThread().getName() + " count: " + i);
try {
Thread.sleep(100);
} catch (Exception e) {
e.printStackTrace();
}
}
}
}

public class Main {

static void main() throws InterruptedException {
System.out.println("Started...");

Thread low = new Thread(new Task(), "Low priority thread");
Thread medium = new Thread(new Task(), "Medium priority thread");
Thread high = new Thread(new Task(), "High priority thread");

low.setPriority(Thread.MIN_PRIORITY);
medium.setPriority(Thread.NORM_PRIORITY);
high.setPriority(Thread.MAX_PRIORITY);

low.start();
medium.start();
high.start();

System.out.println("Finished...");
}

}




                                                                    Synchronization problem

package az.etibarli;

public class SynchronizationExample {

private static int counter = 0;

public static void increment() {
counter++;
}

static void main() throws InterruptedException {

Thread t1 = new Thread(() -> {
for (int i = 0; i < 10000; i++) {
increment();
}
});

Thread t2 = new Thread(() -> {
for (int i = 0; i < 10000; i++) {
increment();
}
});

t1.start();
t2.start();

t1.join();
t2.join();

System.out.println("Counter value is : " + counter);
}
}







synchronized keyword-u ile problemi hell etmek olar. 
Synchronized monitor lock (veya intrinsic lock) istifade edir. 

package az.etibarli;

public class SynchronizationExample {

private static int counter = 0;

public synchronized static void increment() {
counter++;
}

static void main() throws InterruptedException {

Thread t1 = new Thread(() -> {
for (int i = 0; i < 10000; i++) {
increment();
}
});

Thread t2 = new Thread(() -> {
for (int i = 0; i < 10000; i++) {
increment();
}
});

t1.start();
t2.start();

t1.join();
t2.join();

System.out.println("Counter value is : " + counter);
}
}



Lakin synchronized bize problem yarada biler. Cunki her bir java obyektin cemi bir intrinsic lock (monitor lock) var.

Biz bir klasin icinde bir nece synchronized metod ishletsek threadler eyni lock uzerinde mubarize aparacaq. 

Ashagidaki example-da ferq hiss olunur.

package az.etibarli;

public class SynchronizationExample {

private static int counter1 = 0;
private static int counter2 = 0;

public synchronized static void increment1() {
try {
Thread.sleep(1);
} catch (Exception e) {
e.printStackTrace();
}
counter1++;
}

public synchronized static void increment2() {
try {
Thread.sleep(1);
} catch (Exception e) {
e.printStackTrace();
}
counter2++;
}

static void main() throws InterruptedException {
long start = System.currentTimeMillis();

Thread t1 = new Thread(() -> {
for (int i = 0; i < 10000; i++) {
increment1();
}
});

Thread t2 = new Thread(() -> {
for (int i = 0; i < 10000; i++) {
increment2();
}
});

t1.start();
t2.start();

t1.join();
t2.join();

long end = System.currentTimeMillis();

System.out.println("Counter value is : " + counter1);
System.out.println("Counter value is : " + counter2);
System.out.println("Time elapsed: " + (end - start));
}
}




package az.etibarli;

public class SynchronizationExample {

private static int counter1 = 0;
private static int counter2 = 0;

private static final Object lock1 = new Object();
private static final Object lock2 = new Object();

public synchronized static void increment1() {
// t1, t2
// synchronized (lock1) {
try {
Thread.sleep(3000);
} catch (Exception e) {
e.printStackTrace();
}
counter1++;
// }
// synchronized (lock2) {
try {
Thread.sleep(2000);
} catch (Exception e) {
e.printStackTrace();
}
counter2++;
// }
}


static void main() throws InterruptedException {
long start = System.currentTimeMillis();

Thread t1 = new Thread(() -> {
// for (int i = 0; i < 5000; i++) {
increment1();
// }
});

Thread t2 = new Thread(() -> {
// for (int i = 0; i < 5000; i++) {
increment1();
// }
});

t1.start();
t2.start();

t1.join();
t2.join();

long end = System.currentTimeMillis();

System.out.println("Counter value is : " + counter1);
System.out.println("Counter value is : " + counter2);
System.out.println("Time elapsed: " + (end - start));
}
}






                                                Object level locking and class level locking

Object level locking

package az.etibarli;

class ObjectLocking {

public synchronized void instanceMethod() {
System.out.println(Thread.currentThread().getName() + " started instance method...");
try {
Thread.sleep(2000);
} catch (Exception e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " finished instance method...");
}

}

public class Main {

static void main() throws InterruptedException {
long start = System.currentTimeMillis();

ObjectLocking obj1 = new ObjectLocking();
ObjectLocking obj2 = new ObjectLocking();

Runnable task1 = obj1::instanceMethod;
Runnable task2 = obj1::instanceMethod;

Thread t1 = new Thread(task1, "First Thread");
Thread t2 = new Thread(task2, "Second Thread");

t1.start();
t2.start();

t1.join();
t2.join();

long end = System.currentTimeMillis();
System.out.println("Time elapsed: " + (end - start));
}

}


Class level locking

package az.etibarli;

class ObjectLocking {

public static synchronized void instanceMethod() {
System.out.println(Thread.currentThread().getName() + " started instance method...");
try {
Thread.sleep(2000);
} catch (Exception e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " finished instance method...");
}

}

public class Main {

static void main() throws InterruptedException {
long start = System.currentTimeMillis();

Runnable task1 = ObjectLocking::instanceMethod;
Runnable task2 = ObjectLocking::instanceMethod;

Thread t1 = new Thread(task1, "First Thread");
Thread t2 = new Thread(task2, "Second Thread");

t1.start();
t2.start();

t1.join();
t2.join();

long end = System.currentTimeMillis();
System.out.println("Time elapsed: " + (end - start));
}

}




                                                            Wait and notify

package az.etibarli;

class Process {

public void produce() throws Exception {
synchronized (this) {
System.out.println("Running the produce method...");
wait();
System.out.println("Again in the produce method...");
}
}

public void consume() throws Exception {
Thread.sleep(4000);
synchronized (this) {
System.out.println("Running the consume method...");
notify();
System.out.println("After the notify() method call in the consume method...");
}
}
}

public class Main {

static void main() throws InterruptedException {
long start = System.currentTimeMillis();

Process process = new Process();

Thread t1 = new Thread(() -> {
try {
process.produce();
} catch (Exception e) {
e.printStackTrace();
}
});

Thread t2 = new Thread(() -> {
try {
process.consume();
} catch (Exception e) {
e.printStackTrace();
}
});

t1.start();
t2.start();

long end = System.currentTimeMillis();
// System.out.println("Time elapsed: " + (end - start));
}

}


Let's discuss the difference between sleep and wait. They may seem to be very similar but there are fundamental differences between them.

  • you call wait on the Object while on the other hand you call sleep on the Thread itself

  • wait can be interrupter (this is why we need the InterruptedException) while on the other hand sleep can not

  • wait (and notify) must happen in a synchronized  block on the monitor object whereas sleep does not

  • sleep operation does not release the locks it holds while on the other hand wait releases the lock on the object that wait() is called on












package az.etibarli;

import java.util.LinkedList;
import java.util.List;

class SharedBuffer {

private List<Integer> buffer = new LinkedList<>();
private int capacity = 5;

public synchronized void produce() throws Exception {
if (buffer.size() == capacity) {
System.out.println("Buffer is full, produce waiting...");
wait();
}

System.out.println("Adding items with the producer...");
for (int i = 0; i < capacity; i++) {
buffer.add(i);
System.out.println("Added value: " + i);
}

// wake up the consumer
notify();
}

public synchronized void consume() throws Exception {
if (buffer.size() < capacity) {
System.out.println("Buffer not fully yet, consumer waiting...");
wait();
}

while (!buffer.isEmpty()) {
int item = buffer.remove(0);
System.out.println("Consumer removes: " + item);
Thread.sleep(300);
}

// wake up the producer
notify();
}

}

class Consumer implements Runnable {

private SharedBuffer sharedBuffer;

public Consumer(SharedBuffer sharedBuffer) {
this.sharedBuffer = sharedBuffer;
}

@Override
public void run() {
while (true) {
try {
this.sharedBuffer.consume();
Thread.sleep(500);
} catch (Exception e) {
e.printStackTrace();
}
}
}

}

class Producer implements Runnable {

private SharedBuffer sharedBuffer;

public Producer(SharedBuffer sharedBuffer) {
this.sharedBuffer = sharedBuffer;
}

@Override
public void run() {
while (true) {
try {
this.sharedBuffer.produce();
Thread.sleep(500);
} catch (Exception e) {
e.printStackTrace();
}
}
}

}

public class Main {

static void main() throws InterruptedException {
SharedBuffer buffer = new SharedBuffer();

Thread t1 = new Thread(new Producer(buffer));
Thread t2 = new Thread(new Consumer(buffer));

t1.start();
t2.start();

}

}

























package az.etibarli;

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class DeadLock {

private Lock lock1 = new ReentrantLock(true);
private Lock lock2 = new ReentrantLock(true);

static void main() {
DeadLock deadLock = new DeadLock();
new Thread(deadLock::worker1, "worker1").start();
new Thread(deadLock::worker2, "worker2").start();
}

public void worker1() {
lock1.lock();
System.out.println("Worker 1 acquires the lock1...");
try {
Thread.sleep(300);
} catch (InterruptedException e) {
e.printStackTrace();
}
lock2.lock();
System.out.println("Worker 1 acquires the lock2...");
lock1.unlock();
lock2.unlock();
}

public void worker2() {
lock2.lock();
System.out.println("Worker 2 acquires the lock2...");
try {
Thread.sleep(300);
} catch (InterruptedException e) {
e.printStackTrace();
}
lock1.lock();
System.out.println("Worker 2 acquires the lock1...");
lock2.unlock();
lock1.unlock();
}

}








                                                                    Volatile



Her bir core-un oz cache yaddashi var. Thread deyisheni oradan oxuyur. Volatile bir deyisheni cache-de saxlama, haminin gorduyu ortaq yerde saxla. Volatile = bu deyisheni yalniz sen yox butun deyishenler gorsun.



package az.etibarli;

public class WithoutVolatile {

// volatile YOX!
private static boolean running = true;

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

Thread worker = new Thread(() -> {
int count = 0;
while (running) { // ← running-i Cache-dən oxuyur!
count++;
}
System.out.println("Worker dayandı! Count: " + count);
});

worker.start();

Thread.sleep(2000); // 2 saniyə gözlə

System.out.println("running = false edirik...");
running = false; // ← Main thread Cache-inə yazdı
// Worker thread GÖRMÜR!

Thread.sleep(2000); // daha 2 saniyə gözlə

System.out.println("Main bitdi!");
// Worker hələ işləyir! ❌
}

}


package az.etibarli;

public class WithVolatile {

// volatile VAR!
private static volatile boolean running = true;

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

Thread worker = new Thread(() -> {
int count = 0;
while (running) { // ← running-i RAM-dan oxuyur!
count++;
}
System.out.println("Worker dayandı! Count: " + count);
});

worker.start();

Thread.sleep(2000); // 2 saniyə gözlə

System.out.println("running = false edirik...");
running = false; // ← BİRBAŞA RAM-a yazdı!
// Worker dərhal GÖRÜR! ✅

Thread.sleep(2000);

System.out.println("Main bitdi!");
}

}





*** producer and consumer my mini project:

package az.etibarli;

import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;
import java.util.Random;

class Kitchen {

LinkedList<String> meals = new LinkedList<>();
private final int capacity = 8;

public synchronized void addMeal(String chef, String meal) throws Exception {
if (meals.size() == capacity) {
System.out.println("Kitchen is full, " + chef + " is waiting...");
wait();
}
meals.add(meal);
System.out.println(chef + " prepared meal: " + meal);
notify();
}

public synchronized void takeMeal(String waiter) throws Exception {
if (meals.isEmpty()) {
System.out.println("Meal is not ready yet, " + waiter + " is waiting...");
wait();
}
String meal = meals.remove(0);
System.out.println(waiter + " is taking " + meal);
notify();
}

}

class Chef implements Runnable {

private String name;
private Kitchen kitchen;
private List<String> meals = Arrays.asList("Pizza", "Burger", "Pasta", "Sushi", "Salad", "Soup", "Steak", "Kebab");

public Chef(String name, Kitchen kitchen) {
this.name = name;
this.kitchen = kitchen;
}

@Override
public void run() {
Random random = new Random();
try {
while (true) {
String meal = meals.get(random.nextInt(meals.size()));
System.out.println(name + " is preparing " + meal);
Thread.sleep(5000); // ← lock xaricində, Waiter bloklanmır
this.kitchen.addMeal(name, meal);
Thread.sleep(1000);
}
} catch (Exception e) {
e.printStackTrace();
}
}

}

class Waiter implements Runnable {

private String name;
private Kitchen kitchen;

public Waiter(String name, Kitchen kitchen) {
this.name = name;
this.kitchen = kitchen;
}

@Override
public void run() {
try {
while (true) {
this.kitchen.takeMeal(name);
Thread.sleep(2000); // ← lock xaricində
Thread.sleep(1000);
}
} catch (Exception e) {
e.printStackTrace();
}
}

}

public class Task1 {

static void main() {
Kitchen kitchen = new Kitchen();
Chef ali = new Chef("Ali", kitchen);
Waiter nizam = new Waiter("Nizam", kitchen);

Thread t1 = new Thread(ali);
Thread t2 = new Thread(nizam);

t1.start();
t2.start();
}

}





                                                                Executors

package az.etibarli;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

class Task implements Runnable {

private int id;

public Task(int id) {
this.id = id;
}


@Override
public void run() {
System.out.println("Task with id " + id + " is in work - thread id: " + Thread.currentThread().getName());
long duration = (long) (Math.random() * 5);
try {
TimeUnit.SECONDS.sleep(duration);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}

public class SingleThreadExecutor {

static void main() {
ExecutorService executor = Executors.newSingleThreadExecutor();

for (int i = 0; i < 5; i++) {
executor.execute(new Task(i));
}
}
}



























Комментарии

Популярные сообщения из этого блога

Interview questions

Lesson1: JDK, JVM, JRE

Lesson_2: Operations in Java