Yazar: mert

  • Thread Lifecycle, Context Switch ve Thread Memory Hakkında

    Merhaba bu yazıda java threadleri hakkında yazmaya çalışacağım. Burada bahsedeceklerim Virtual Threads ile alakalı olmayacak sadece klasik platform threadlerinde bahsediyor olacağım.

    Thred için bir tanım getirmemiz gerekirse basitce CPU üzerinde çalışan iş parçacığı diyebiliriz. Java’da JVM tarafından oluşturulan Thread nesnesi , işletim sisteminin sahip olduğu native threadler ile bire bir mapping kurulması ile oluşur. Threadin cpu üzerindeki işleri yürütmesi konusu işletim sisteminin schedulerı ile gerçekleştirilir. Threadler programımızın eşzamanlı veya paralel şekilde yürütülen iş parçacıklarıdır.

    Thread t = new Thread(() -> System.out.println("Mert uykusuz.."));
    t.start();

    Thread Lifecycle

    Threadler javada temel olarak 6 ana durumda bulunurlar. Bunlar şunlar ; NEW, RUNABLE , BLOCKED, WAITING, TIMED_WAITING , TERMINATED.

    • NEW -> Threadi oluşturduk fakat henüz nesne üzerinde .start() ile çağırmadıysak new durumundadır. Java tarafında bir nesnemiz var fakat cpuya hiç gitmedik yani cpu üzerinde şu an çalışmıyor.

    • RUNABLE -> .start() çağrıldıktan sonra jvm tarafında artık thread statüsü RUNABLE duruma gelir. Yani isminde de anlayacağımız üzere thread çalışmak için hazır durumdadır. İşletim sistemi tarafında schedular threade CPU vermek için zamanı vermek için beklemeye başlar. CPU meşgulse RUNABLE durumda beklemeye devam eder ama bu durumda beklemesi bloklandığı vs anlamına gelmez.

    • BLOCKED -> Thread başka bir threadin tuttuğu bir kiliti bekliyorsa BLOCKED statüsüne geçer. Bu blocked statüsü aslında bana göre threadlerde en kritik mekanizma olan kilit mekanizmasının sonucunda açığa çıkan bir statüdür. Javadaki threadlerin aynı kaynağa aynı anda erişmesinin engellendiği ve bu sayede race conditionın olmadığını belirten bir statüdür. Eğer bu engelleme olmasaydı farklı threadler aynı kaynağı güncelleyebilir , veri tutarlılığı olmaz ve istenmeyen durumlar ortaya çıkardı. Blocked statüsü threadin kaynak kullanımda güvenlik için “cezalı” olduğu anlamına gelir.
        private final Object lock = new Object();
    
        public void criticalSection() {
            synchronized(lock) {
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {}
            }
        }

    • WAITING -> Bazı durumlarda bir threadin işinin tamamlanması diğer threding işinin tamamlanmasına bağlı olabilir. WAITING statüsü threadlerin bir birleri arasında senkronize olmalarını sağlar. WAITING statüsü sayesinde sonsuz döngülerle diğer bağımlı olunan threading işinin bitip bitmediğini sürekli sorgulayıp cpuyu yormak yerine biten işlemşin bittiğinden notify olmamızı sağlar böyleye gereksiz CPU harcamamızın önüne geçer. Kısacası WAITING statülü bir thread başka bir threadin kendisine haber vermesini bekler.
    public class WaitingExample {
        public static void main(String[] args) {
            Object lock = new Object();
    
            Thread t1 = new Thread(() -> {
                synchronized (lock) {
                    try {
                        System.out.println("Thread 1: Bekliyorum.");
                        lock.wait(); // WAITING durumu
                        System.out.println("Thread 1: Devam ediyorum.");
                    } catch (InterruptedException e) {}
                }
            });
    
            Thread t2 = new Thread(() -> {
                try {
                    Thread.sleep(2000); // 2 saniye bekle
                    synchronized (lock) {
                        System.out.println("Thread 2: Thread 1'i uyandırıyorum.");
                        lock.notify(); // t1 runnable duruma geçer ve cpudan zaman alabilirse runninge geçer 
                    }
                } catch (InterruptedException e) {}
            });
    
            t1.start();
            t2.start();
        }
    }

    Ana thread t1.join() metodunu kullanarak kendi akışını WAITING statüsüne alır ve join() çağrısı yapılan thread’in işini tamamlamasını bekler.
    wait() kullanımından farklı olarak burada manuel bir notify() çağrısına gerek yoktur; ilgili thread tamamlandığında JVM ana thread’i otomatik olarak uyandırır.
    Thread tamamlandıktan sonra ana thread RUNNABLE duruma geri döner ve CPU zamanı alabilirse çalışmaya devam eder.
    Örnek verecek olursak :

    Thread t1 = new Thread(() -> {
        try { Thread.sleep(3000); } catch (InterruptedException e) {}
        System.out.println("t1 bitti");
    });
    t1.start();
    t1.join(); // Ana thread burada WAITING durumuna girer , t1 terminate olursa devam eder.
    System.out.println("Ana thread devam :)");

    TIMED_WAITING: Thread’i manuel olarak belli bir süre bekletmek istediğimizde kullanılır. Thread.sleep(3000) veya object.wait(3000) çağrıları thread’i TIMED_WAITING statüsüne sokar. Süre bitince veya notify gelirse thread RUNNABLE duruma geçer ve CPU zamanı alabilirse running hale gelir.


    TERMINATED -> Threadin işini bitirdikten sonra aldığı statüdür. Yani kabaca threadin görevini tamamlayıp öldüğü haldir.


    Blogda yazarken akılda canlanabilmesi için yazılarda gerçek hayat örneklemlerine sıklıkla yer vermeye çalışacağım. O zaman threadleri de gerçek hayat ile örneklendirelim 🙂

    Gerçek Hayat Örnekleri İle Thread Statüleri

    Bu örneklememde Garsonu thread , mutfağı CPU , locku ve paylaşılan ortak kaynağı tencere olarak örneklendireceğim.

    • Garson iş yerine geldi fakat mutfağa henüz girmemişse NEW statüsündedir -> thread oluşturulda fakat .start() çağırılmadı.
    • Garson mutfak kapısında bekliyor , çalışmaya hazır ve mutfak kapısı açılırsa çalışmaya başlayacak. Bu durumda RUNABLE statüsündedir -> Thread çalışmaya hazır sadece CPU zamanı bekliyor
    • Garson mutfakta ama tek bir tane düdüklü tencere var o tencerede de başka bir garson yemek yapıyor. Tencerenin kilidi alınmış durumda. Bizim garson BLOCKED (CPU zamanı olsa bile iş yapamamaz) durumda. Mutfakta olmasına rağmen iş yapamıyor. İlk garson işini bitiriyor ve kilit otomatikmen serbest hale geliyor. Bizim garson running duruma geçiyor. Burada bahsettiğin running bir thread statüsü değil thread çalışıyor demek 🙂
    • Garsonumuz mutfağa girdi tenceredeki yemeğin üzerine sos yapacak. Ama tencerede başka garson yemek yapıyor ve işi devam ediyor. Bizim garson “işin bitince bana haber ver diyor” ve beklemeye başlıyor. Garsonumuz WAITING statüsünde. notify() olunca çalışmaya başlayacak.
    • Garsonumuz mutfağa giriyor ve tencereye sos eklemek istiyor. Başka bir garson tencereyi zaten kullanıyor. Bizim garsonda iki dakika mola veriririm süre dolarsa başka bir işe geçerim diyor. Garsonumuz TIMED_WAITING statüsünde.
    • Garsonumuz mutfağa girdi , tencereyi aldı , yemeği bitirdi ve tencereyi bıraktı. Tüm işini bitirdi ve başka iş beklemiyor artık TERMINATED statüsünde.

    Thredlerin statüleri hem açıklama ve hem de mutfak örneklendirmesi ile bu kadar anlatmak yeterli diye düşünüyorum. Şimdi threadlerle alakalı başka bir konu başlağı ile devam edelim.

    Çok Thread Her Zaman Hız Demek Değildir – Context Switching’in Görünmeyen Maliyeti

    CPU çekirdek başına aynı anda her zaman 1 thread çalıştırabilir. 4 çekirdekli bir CPU ya sahipsek aynı anda 4 thread çalıştırabilir bundan fazla thread ile çalışmak istediğimizde bu threadleri CPU sırayla çalıştıracaktır.

    İşte tam burada CPU bir threadden başka bir threade geçtiğinde OS ve CPU tarafında çeşitli işlemler yapılır. Bu da bize context switch maliyeti olarak döner.
    Burada context switchde bize en çok maliyet oluşturan şey “cache” kaybıdır.

    Thread A çalışırken kullandığı veriler CPU üzerindeki cache de tutulur. Context switch gerçekleşip Thread B çalışmaya başladığında ise CPU cache yi bu sefer Thread B nin kullandığı verilerle doldurulur.

    Thread A tekrar çalışmaya başladığında ihtiyaç duyduğu verileri cache de bulamazsa bu verileri RAM üzerinden yeniden okumak zorunda kalır. RAM, CPU cache’e kıyasla çok daha yavaş olduğu için bu durum ek bir latency oluşturur ve uygulamanın genel performansını olumsuz etkiler. Buradan da anlaşılabileceği üzere kısacası context switch arttıkça verimsizlik artar.

    IO erişimi yoğun işlemlerde zaten IO işlemi için doğal bir bekleme olduğundan aslında thread sayısını fazla vermek context switch bazlı çok da bir verimsizlik oluşturmaz fakat CPU bazlı işlerin çoğunlukta olduğu işlerde (hesaplama vs) thread sayısına dikkat edilmedir.

    Stack, Heap ve Thread-Local Memory Kavramlarına Bakış

    Threadlerde memory kavramı benimde epeyce uzak ve yabancı olduğum bir konuydu. Fakat önemli olduğunu düşündüğüm için üzerinde notlar oluşturmanın ve burada değinmenin önemli olacağını düşünüyorum.

    Stack için kısaca en güvenli bellek alanı diyebilir. Stack alanı threade özgü bir alandır ve diğer threadler tarafından görülemez ve erişilemez . Bu sebeple safedir ve race conditionu engeller. Metot çağrıları , return typelar , metot parametreleri ve local değişkenler burada tutulur. Garson bir thread ise Stack cebindeki notlardır.

    Heap ise new ile oluşturduğumuz nesnelerin barındığı alandır. Tüm threadler erişebilir ve değiştirebilir. Bu sebeple race conditiona açıktır. Heap için mutfakta ortak kullanabilir durumdaki bir tencereyi örnek verebiliriz. Eğer herkes tencereye erişip içine bir şeyler doğramaya çalışırsa kaos çıkabilir 🙂 . Bunu engellemek için synchronized , Lock vs gibi yapılar kullanabiliriz.

    ThreadLocal ise Stack ve Heap’in karması diyebiliriz. Anlaması biraz daha güç ama anladığım kadarı ile şöyle açıklayayım. Fiziksel olarak heap ama mantıksal olarak stack özelliklerini taşıyor. Threadler ThreadLocal ile kendilerine özgü kopyalar tutar ve bu kopyaları başka threadler göremez.

    ThreadLocal<Integer> threadId = ThreadLocal.withInitial(() -> 0);
    threadId.set(1905);
    System.out.println(threadId.get());

    Bu şekilde bir setleme ile farklı threadler farklı idler dönecektir.

    Peki neden ThreadLocal gibi bir şeye ihtiyaç duyuyoruz ? Bir threadin set ettiği bir şeyi başka bir thread override edip async çağırımlarda bağlamın kopmasına neden olmasın diye.

    ThreadLocal<UserContext> currentUser;

    Bu şekilde bir kullanımda her thread kendi UserContext bilgisini taşıyacaktır. Burada dikkat etmemiz gereken en önemli şey eğer iş bittikten sonra bir yerde currentUser.remove() etmezsek memory leak e neden olabiliriz.

    Bugünlü bu yazınında sonuna geldik , bir sonraki yazıda görüşmek üzere 🙂

  • Asenkron Programlama Nedir? Java Spring’de Beklemeyi Yönetmek

    1 ay kadar bir süreden sonra blogun ikinci yazısıyla ve 2026’nın ilk yazısıyla yeniden buradayım. Oldukça geniş bir kavram olan asenkron programlama için bu yazıyı bir giriş oluşturma amacıyla kaleme almak istedim.


    Asenkron kavramı nedir?

    Asenkron kavramını basitçe şöyle özetleyebiliriz:

    “Bir işi başlatırım, o işin bitmesini beklemeden başka işlere devam ederim.
    İş tamamlandığında kaldığım yerden devam ederim.”

    Buradaki kritik nokta beklememek değil, beklerken meşgul olmamaktır.

    Gerçek hayattan bir örnek

    Bir restoranda sipariş verdiğimizi düşünelim. Garson siparişi alır ve mutfağa iletir; ancak yemek hazırlanırken masanın başında beklemez. Bu süre boyunca başka müşterilerle ilgilenir. Yemek hazır olduğunda tekrar masaya döner.

    Garsonun yaptığı şey şudur: beklemeyi yönetmek.

    Waiter taking orders from a couple

    Senkron iş için bir canlı örnek :

    Senkron modele örnek vermek istersek, banka kuyruğunu düşünebiliriz.

    Gişe görevlisi bir müşterinin işlemini gerçekleştirirken, sıradaki herkes bu işlemin bitmesini bekler. Görevli, mevcut müşterinin işlemi tamamlanmadan bir sonraki kişiden kimlik ya da evrak istemez.

    Yani kuyruktaki herkes, en öndeki kişinin işinin bitmesine bağlıdır.

    Peki asenkron programlama Java Spring’de neyi çözer?

    Burada ilk akılda tutulması gereken ve bence en önemli nokta şudur:

    Asenkron programlama bir teknik değil,
    bir kaynak yönetimi stratejisidir.

    Bir Spring Boot uygulamasında temel olarak üç kaynağımız var :

    • CPU
    • Memory
    • Thread

    Asenkron programlama konusunda doğrudan threadlerle ilgileniriz.

    Thread gerçekte ne yapar?

    Bir thread temelde iki durumda bulunur:

    • CPU üzerinde aktif olarak çalışır
    • I/O bekler (veritabanı, HTTP, disk, mesajlaşma sistemleri vb.)

    Buradak kritik nokta şu :

    Thread I/O beklerken CPU kullanmaz, fakat bekleme süresi boyunca kilitli kalır ve başka bir iş alamaz.

    Bu yüzden prod ortamlarda sıklıkla şu tabloyu görürüz:

    • CPU kullanımı düşük
    • Sistem yavaş veya tıkalı

    Spring Boot’un varsayılan modeli

    Spring Boot varsayılan olarak:

    1 request = 1 thread

    modeliyle çalışır.

    Bu yaklaşım aslında bize şunu sağlıyor:

    • Basit
    • Okunabilir
    • Öğrenmesi kolaydır

    Ancak I/O ağırlıklı sistemlerde yük altında ciddi verimsizlik ve çökmelere kapı aralıyor.

    Basit bir örnek kod

    Aşağıdaki endpoint’i ele alalım:

    @GetMapping("/order/{id}")
    public OrderDto getOrder(Long id) {
        Order order = calculateAndHash(id);      // CPU (~50 ms)
        Payment payment = paymentClient.get(order);    // HTTP (~300 ms)
        Shipment shipment = shipmentClient.get(order); // HTTP (~400 ms)
        return new OrderDto(order, payment, shipment);
    }
    

    Bu kodda:

    • calculateAndHash metodu tamamen CPU-bound bir işlemdir.
      Thread bu süre boyunca aktif olarak CPU üzerinde çalışır.
    • HTTP çağrıları da I/O bekleme içerir

    Java thread açısından baktığımızda:

    • Yaklaşık 50 ms aktif CPU çalışması vardır
    • Yaklaşık 700 ms I/O bekleme süresi vardır

    Yani thread’in:

    %93’ü fiilen çalışmadan geçmesine rağmen
    hala tahsisli ve meşguldür.

    Basit bir kapasite hesabı

    Tomcat thread pool’unun 200 olduğunu varsayalım.

    Ortalama response süresi:

    750 ms

    Bu durumda saniyede işlenebilecek maksimum istek sayısı:

    200 / 0.75 ≈ 266 request/sec
    

    Bu sırada:

    • CPU neredeyse boştur
    • Ama sistem daha fazla isteği kabul edemez

    Çünkü thread’ler beklemektedir.

    Kritik soru: Thread neden bekliyor?

    Çünkü sistem, başlatılan bir işin sonucuna
    aynı akış içinde ihtiyaç duyar.

    Bu da şu anlama gelir:

    • İş tamamlanmadan ilerlenemez
    • Thread beklemek zorunda kalır
    • Beklerken başka bir iş alamaz

    Sonuç olarak:

    Sistem CPU açısından boş,
    ancak thread kapasitesi açısından doludur.

    Senkron ve asenkron düşünce farkı

    Bu noktada iki farklı yaklaşım ortaya çıkar:

    Senkron düşünce

    • Her adımın bir öncekini beklemesini zorunlu kılar
    • Beklemeyi doğal kabul eder

    Asenkron düşünce

    • Zaman bağını gevşetmeyi hedefler
    • Bekleme süresini verimli kullanmayı amaçlar

    Multithreading = Asenkronluk yanılgısı

    Yaygın bir yanlış inanış şudur:

    “Yeni thread açarsam asenkron olurum.”

    Eğer kullanılan yapılar blocking ise:

    • Daha fazla thread
    • Daha fazla context switch
    • Daha fazla memory tüketimi

    elde edilir.

    Asenkronluğun gücü:

    Az thread ile çok isteği verimli şekilde yönetebilmekten gelir.”

    Message Brokerlar tek başına yeterli değildir

    Kafka veya benzeri mesajlaşma sistemlerini kullanmak,
    uygulamanın otomatik olarak asenkron olduğu anlamına gelmez.

    Mesajlaşma sistemleri:

    • Sadece mesajı taşır
    • Thread yönetimi ve blocking / non-blocking davranışlar
      uygulama tarafında belirlenir

    Tabii ki asenkron programlama her derde deva değil. Debug etmesi daha zor, akış takibi daha karmaşık. Bu, benim en sevmediğim ve hatta uzun süre asenkron programlamaya direnç göstermeme neden olan konulardan biri oldu.

    Özellikle senkron ve üstten alta sıralı kod akışına alışık biri için; asenkron yapıların insanda sürekli olarak “kontrol bende değil” hissi uyandırdığını düşünüyorum.
    Kod çalışıyor gibi, ama bir şeyler bir yerden bir yere “uçuyormuş” hissi veriyor.

    Akışın tek bir call stack üzerinde ilerlememesi, debug sırasında zihinsel modeli ciddi şekilde zorlayan bir durum.

    Sonuca Gelirsek ..

    Asenkron programlama kod yazma tekniği değil veya bir tür performans hilesi değil. Burada beklemeyi yönetebilmek kritik nokta.

    Bu yazımda asenkronluğu nasıl uygulayacağımızdan özellikle bahsetmedim. Çünkü önce konsept için basit bir giriş yapmak istedim. Yazıda asenkronluğu nasıl uygulayacağımızı değinmeden buna neden buna ihtiyaç duyduğumuzu ele almaya çalıştım.

    Bir sonraki yazıda ele alacağım konu “Thread Lifecycle, Context Switch ve Thread Memory Hakkında” , görüşmek üzere 🙂

  • Hello World – Yapay Zeka Varken Blog Tutmaya Başlamak

    Hello World! diyerek bir yazılım klişesi ile mertinal.org sitesinin ilk yazısı ile karşınızdayım.

    Bugün bu yazıda neden blog tutasım geldi ve günümüzde blog tutmanın neden bana hala önemli geldiği konularını kişisel olarak ele alacağım. Bu blogda yazdığım bana ait cümlelerin hiçbirinde yazım ve noktalama yanlışları olmasına takılmayacağım. Çünkü insani olarak kalmasını , bana ait olmasını ve gerekirse hatalı olmasını istiyorum.

    Bugüne kadar çok fazla blog açıp kapatan biri olarak bu blogun hem hayatım boyunca kalıcı olmasını temenni ediyor hem de öğrenme ve yazma serüvene bakış açımı değiştirmesine katkı sunmasını umuyorum. Genelde yazılım notlarımdan oluşacak bu blog “mutlaka yazman lazım ! mutlaka yazman için mutlaka öğrenmen lazım !” gibi kural setlerini bana dayatmayacak. Özel hayatımda artık “Şu kursu şu kadar sürede bitireceksin, bitirmelisin” yok. Burası sadece rutin oluşturma çabamda bilinç altıma sinyaller verecek. Bu blog okunmak için yazılmayacak ve sitede google analytics bile olmayacak.

    Özgün Düşünce Hala Değerli Olmalı

    Gerek iş yerinde gerekse kendi kişisel hobi projelerinde aktif olarak yapay zekayı kullanan biri olarak (şirketin tanıdığı aylık premium requestleri ayın yarısında bitiren) bu blogu tutmaya başlamamdaki ilk neden; kendimde gözlemdiğim ve beni içten içe rahatsız eden o salaklaşıyorum ve tembelleşiyorum hissidir. Ben bu duyguyu vibe coding yapmadan , yapay zekayı olabildiğince ihtiyaç duyduğum kod parçacıklarında kullanmaya çalışırken yaşıyorsam ; neredeyse hiç bir mühendislik , sorgulama ve özgünlük içermeden sadece bir kaç prompt girerek yaptırılan uygulamalardaki hissi merak ediyorum.

    Belki çoğu kişiden duyduğum ben bu işin ameleliğini yapay zekaya yaptırıyorum sözünü de irdelemek lazım. Burada amelelik denilen şey sadece klavyeden tuşlara basmanın ötesine sık sık taşıyor ve ufak da olsa “düşün” gerektiren noktalara da sarkıyor olmalı ; çünkü çoğu zaman kod satırının bizati kendisi kadar detay prompt girmekten kaçınırız.

    Ünlü bilim insanı Charles Darwin’e atfedilen “Ne en güçlü olan tür hayatta kalır, ne de en zeki olan; değişime en açık olandır, hayatta kalan.” sözüne katılıyorum. Burada yapay zekaya bir düşmanlık içerisinde değilim. Kullanıyorum ve artarak kullanmaya devam edeceğim. Sadece insanın en değerli yapısını düşün üretme mekanizmasını gönüllü ve koşulsuz yapay zekaya delege etmesinin oluşturucağı potansiyel tehlikeye işaret etmek istiyorum. Günün sonunda yapay zeka olmadan 20 satırlık basit bir fonksiyon bile yazamayan , düşünme yetisi tamamen felç olmuş nesillerin oluşturabileceği tehlike göz önünde bulundurulmalı.

    Yapay Zekayı Eleştirmek = Ludizm midir ?

    İnternette ve özellikle twitterda kendine vibe coder diyen , genellikle yazılım mühendislerine sallayan hatta “software engineering is not real job anymore” şeklinde trollükler yapan bir kaç kişinin “Osmanlıda matbaya karşı çıkmıştı , ludistler işten kovulmamak makineleri yakmıştı. Yapay zekaya saldırınız bu yüzden” şeklinde demeçlerine de biraz bakmak gerekiyor.

    Yazılım/Bilgisayar mühendisliği yapamamanın verdiği ağır kompleks yüzünden açığa çıktığı çok bariz bu saldırı vari cümlelerin altında aslında trollükten başka bir şey yatmıyor. Her zaman ki gibi kolay olana akın eden , az emek çok çıktı ve çok para beklentisinden başka bir şey hedeflemeyen mentalinin bir ürünü bu saldırı. Kendisine kolay olan şeyin dünya üzerinde yaşayan tüm insanlara aynı derece kolay olduğunu ve bir şey herkes için aynı derece kolay ise oradan elle tutulur bir kazanç elde etmenin şans haline geleceğini bilmiyor.

    Kendine güvenen , analitik düşünebilen hiç bir mühendisin yapay zekadan korkmasını ve çekineceğini düşünmüyorum. En iyi araçlar en iyi pilotların elinde gerçek performansına ulaşabilir. Yapay zeka sistemdeki bazı insanların işten çıkmasına neden olacaktır tabii ki fakat yeni işe alanları da açacaktır ve açacağı bu yeni iş alanlarına yine ilgili konuların vasıflı ve kalifiye kişileri girecektir.

    Fakat bence yapay zeka karşıtlığı yapanlar varsa bile bu yaptıkları yine de matbaya karşı çıkmakla eş değer değildir. Tekerlek , matbaa , makine , bilgisayar gibi insanlık tarihinin farklı zamanlarında ortaya çıkmış araçlar insanların fiziksel operasyonlarını insanların “zekasının” kontrolüne sunmuştur ve bu zekadan çıkan düşünce tarafından yönetilmiştir. Fakat yapay zeka ilerledikçe ve geliştikçe özgün düşünceden , zekadan vazgeçiliyor ve bu kontrolde kaybedilmiş oluyor. İnsan olmanın , yapay zeka ile donatılmış bir makineden bilinç dışında bir farkı kalmıyor , şimdilik.

    Yapay Zekasız Yazma ve Notlar Alma

    İşte tüm bu sebeplerden dolayı yapay zekanın verdiği o konfor alanından bir tık uzaklamaya çalışmak , bu konuda kendime bir miktar direnç oluşturmak , yazılım konseptlerini öğrenirken ve kurslara devam ederken kendime notlar almak için bu blog sitesinin benim için faydalı olacağını düşündüm. Sadece işim düştüğünde , işim düştüğü konuyu yapay zekaya sorarak ilerlemenin öğrenme açısından bende kalıcı bir etki yaratmadığını fark ettim. Blogda yazacağım ilk konular asenkron programlama ve design patternler üzerine olacağını düşünüyorum. Yazının başında da belirttiğim gibi burada kendime bir tarih kıstası koymadan ilerleyeceğim ve bu siteye notlar alacağım. Nadiren de olsa kişisel yazılar ve paylaşımlar da yaparım diye düşünüyorum.

    Şimdilik bu kadar 🙂