Java中的多線程Demo


一、關於Java多線程中的一些概念

1.1 線程基本概念

從JDK1.5開始,Java提供了3中方式來創建、啟動多線程:

  方式一(不推薦)、通過繼承Thread類來創建線程類,重寫run()方法作為線程執行體;

  方式二、實現Runnable接口來創建線程類,重寫run()方法作為線程執行體;

  方式三、實現Callable接口來創建線程類,重寫run()方法作為線程執行體;

不同的是,其中方式一的效果最差,是因為

  1、線程類繼承了Thread類,無法再繼承其他父類;

  2、因為每條線程都是一個Thread子類的實例,因此多個線程之間共享數據比較麻煩。

二、一些關於synchronized關鍵字的簡單Demo

2.1、多個線程單個鎖

當多個線程訪問myThread的run方法時,以排隊的方式進行處理(這里排對是按照CPU分配的先后順序而定的),一個線程想要執行synchronized修飾的方法里的代碼首先會去嘗試獲得鎖,如果拿到鎖,執行synchronized代碼體內容;

如果拿不到鎖,這個線程就會不斷的嘗試獲得這把鎖,直到拿到為止,而且是多個線程同時去競爭這把鎖。(也就是會有鎖競爭的問題)

package com.sun.multithread.sync;

public class MyThread extends Thread {

    private int count = 5;
    
    // synchronized加鎖
    public synchronized void run() {
        count--;
        System.out.println(this.currentThread().getName() + " count = " + count);
    }
    
    public static void main(String[] args) {
        
        MyThread myThread = new MyThread();
        Thread t1 = new Thread(myThread, "t1");
        Thread t2 = new Thread(myThread, "t2");
        Thread t3 = new Thread(myThread, "t3");
        Thread t4 = new Thread(myThread, "t4");
        Thread t5 = new Thread(myThread, "t5");
        
        t1.start();
        t2.start();
        t3.start();
        t4.start();
        t5.start();
    }
}

2.2、多個線程多把鎖

關鍵字synchronized取得的鎖都是對象鎖,而不是把一段代碼(方法)當成鎖,所以代碼中哪個線程先執行synchronized關鍵字的方法,哪個線程就持有該方法所屬對象的鎖。但是在靜態(static)方法上加synchronized關鍵字,表示鎖定class類,類級別的鎖
package com.sun.multithread.sync;

public class MultiThread {
    
    private static int num = 0;
    
    /**
     * 在靜態(static)方法上加synchronized關鍵字,表示鎖定class類,類級別的鎖
* * 關鍵字synchronized取得的鎖都是對象鎖,而不是把一段代碼(方法)當成鎖, * 所以代碼中哪個線程先執行synchronized關鍵字的方法,哪個線程就持有該方法所屬對象的鎖 * *
@param tag 參數 */ public static synchronized void printNum(String tag){ try { if(tag.equals("a")){ num = 100; System.out.println("tag a, set num over!"); Thread.sleep(1000); } else { num = 200; System.out.println("tag b, set num over!"); } System.out.println("tag " + tag + ", num = " + num); } catch (InterruptedException e) { e.printStackTrace(); } } public static void main(String[] args) { // 兩個不同的對象 final MultiThread m1 = new MultiThread(); final MultiThread m2 = new MultiThread(); Thread t1 = new Thread(new Runnable() { @Override public void run() { m1.printNum("a"); } }); Thread t2 = new Thread(new Runnable() { @Override public void run() { m1.printNum("b"); } }); t1.start(); t2.start(); } }

 2.3 銀行取錢的多線程例子

1、創建一個銀行賬戶,並在里面寫好取錢的方法,其中使用synchronized關鍵字修飾多線程操作的方法

package com.sun.multithread.sync.bankdemo;

/**
 * 銀行賬戶類
 * 
 * @author ietree
 */
public class Account {

    /**
     * 賬號
     */
    private String accountNo;

    /**
     * 余額
     */
    private double balance;

    public Account() {
    }

    /**
     * 帶參構造函數
     * 
     * @param accountNo 賬戶
     * @param balance 余額
     */
    public Account(String accountNo, double balance) {
        this.accountNo = accountNo;
        this.balance = balance;
    }
    
    // 訪問該賬戶的余額,使用synchronized修飾符將它變成同步方法
    public synchronized double getBalance() {
        return balance;
    }
        
    /**
     * 取錢的方法
     * 
     * @param drawAmount 取錢金額
     */
    public synchronized void draw(double drawAmount) {

        // 如果余額大於等於用戶取的錢,則取款成功
        if (balance >= drawAmount) {
            // 取款成功
            System.out.println(Thread.currentThread().getName() + "取錢成功!用戶取出" + drawAmount + "元");

            // 修改余額
            balance -= drawAmount;
            System.out.println("\t余額為: " + balance);
            
        } else {
            
            System.out.println(Thread.currentThread().getName() + "取錢失敗!您的余額不足");
            
        }

    }

    public String getAccountNo() {
        return accountNo;
    }

    public void setAccountNo(String accountNo) {
        this.accountNo = accountNo;
    }

    // 重寫hashCode()方法
    public int hashCode() {
        return accountNo.hashCode();
    }

    // 重寫equals()方法
    public boolean equals(Object obj) {
        if (this == obj) {
            return true;
        }
        if (obj != null && obj.getClass() == Account.class) {
            Account target = (Account) obj;
            return target.accountNo.equals(accountNo);
        }
        return false;
    }

}

2、創建多個線程同時操作一個賬戶

package com.sun.multithread.sync.bankdemo;

class DrawThread extends Thread {
    
    /**
     * 模擬用戶賬戶
     */
    private Account account;
    
    /**
     * 當前取錢線程所希望取的錢數
     */
    private double drawAmount;
    
    public DrawThread(String name, Account account, double drawAmount){
        super(name);
        this.account = account;
        this.drawAmount = drawAmount;
    }
    
    // 當多條線程修改同一個共享數據時,將涉及數據安全問題
    public void run(){
        account.draw(drawAmount);
    }
}

public class DrawTest {
    
    public static void main(String[] args) {
        // 創建一個賬戶
        Account acct = new Account("1234567", 1000);
        
        // 模擬兩個線程對同一賬戶取錢
        new DrawThread("路人甲", acct, 800).start();
        new DrawThread("路人乙", acct, 800).start();
    }
    
    
}

 2.4 業務整體需要使用完整的synchronized,保持業務的原子性

package com.ietree.multithread.sync;

/**
 * 業務整體需要使用完整的synchronized,保持業務的原子性
 * 
 * @author ietree
 */
public class DirtyRead {

    private String username = "Jack";
    private String password = "123";

    public synchronized void setValue(String username, String password) {
        this.username = username;

        try {
            // 睡眠2秒
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        this.password = password;

        System.out.println("setValue最終結果:username = " + username + ", password = " + password);
    }

    // synchronized
    public void getValue() {
        System.out.println("getValue方法得到:username = " + this.username + ", password = " + this.password);
    }

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

        final DirtyRead dr = new DirtyRead();
        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                dr.setValue("Dylan", "456");
            }
        });
        t1.start();
        Thread.sleep(1000);

        dr.getValue();
    }

}

程序輸出:

getValue方法得到:username = Dylan, password = 123
setValue最終結果:username = Dylan, password = 456

這里出現了臟讀現象,應該要保持業務的原子性,修改如下:

package com.ietree.multithread.sync;

/**
 * 業務整體需要使用完整的synchronized,保持業務的原子性
 * 
 * @author ietree
 */
public class DirtyRead {

    private String username = "Jack";
    private String password = "123";

    public synchronized void setValue(String username, String password) {
        this.username = username;

        try {
            // 睡眠2秒
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        this.password = password;

        System.out.println("setValue最終結果:username = " + username + ", password = " + password);
    }

    // synchronized
    public synchronized void getValue() {
        System.out.println("getValue方法得到:username = " + this.username + ", password = " + this.password);
    }

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

        final DirtyRead dr = new DirtyRead();
        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                dr.setValue("Dylan", "456");
            }
        });
        t1.start();
        Thread.sleep(1000);

        dr.getValue();
    }

}

程序輸出:

setValue最終結果:username = Dylan, password = 456
getValue方法得到:username = Dylan, password = 456

當在set和get方法上同時使用了synchronized能確保業務原子性,不會出現臟讀現象。

 2.5 synchronized的重入

demo1:

package com.ietree.multithread.sync;

public class SyncDemo1 {

    public synchronized void method1(){
        System.out.println("method1..");
        method2();
    }
    public synchronized void method2(){
        System.out.println("method2..");
        method3();
    }
    public synchronized void method3(){
        System.out.println("method3..");
    }
    
    public static void main(String[] args) {
        final SyncDemo1 sd = new SyncDemo1();
        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                sd.method1();
            }
        });
        t1.start();
    }

}

demo2:

package com.ietree.multithread.sync;

public class SyncDemo2 {
    
    static class Parent {
        public int i = 10;

        public synchronized void operationSup() {
            try {
                i--;
                System.out.println("Main print i = " + i);
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    static class Sub extends Parent {
        public synchronized void operationSub() {
            try {
                while (i > 0) {
                    i--;
                    System.out.println("Sub print i = " + i);
                    Thread.sleep(100);
                    this.operationSup();
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    public static void main(String[] args) {

        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                Sub sub = new Sub();
                sub.operationSub();
            }
        });

        t1.start();
    }
}

2.6 synchronized的Exception

package com.ietree.multithread.sync;

public class SyncException {

    private int i = 0;

    public synchronized void operation() {
        while (true) {
            try {
                i++;
                Thread.sleep(100);
                System.out.println(Thread.currentThread().getName() + " , i = " + i);
                if (i == 20) {
                    throw new RuntimeException();
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    public static void main(String[] args) {

        final SyncException se = new SyncException();
        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                se.operation();
            }
        }, "t1");
        t1.start();
    }
}

 2.7 鎖對象的改變問題

demo1:

package com.ietree.multithread.sync;

public class ChangeLock {
    
    private String lock = "lock";

    private void method() {
        synchronized (lock) {
            try {
                System.out.println("當前線程 : " + Thread.currentThread().getName() + "開始");
                lock = "change lock";
                Thread.sleep(2000);
                System.out.println("當前線程 : " + Thread.currentThread().getName() + "結束");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    public static void main(String[] args) {

        final ChangeLock changeLock = new ChangeLock();
        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                changeLock.method();
            }
        }, "t1");
        Thread t2 = new Thread(new Runnable() {
            @Override
            public void run() {
                changeLock.method();
            }
        }, "t2");
        t1.start();
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        t2.start();
    }
}

程序輸出:

當前線程 : t1開始
當前線程 : t2開始
當前線程 : t1結束
當前線程 : t2結束

demo2:

package com.ietree.multithread.sync;

public class ChangeLock {

    private String lock = "lock";

    private void method() {
        synchronized (lock) {
            try {
                System.out.println("當前線程 : " + Thread.currentThread().getName() + "開始");
                // lock = "change lock";
                Thread.sleep(2000);
                System.out.println("當前線程 : " + Thread.currentThread().getName() + "結束");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    public static void main(String[] args) {

        final ChangeLock changeLock = new ChangeLock();
        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                changeLock.method();
            }
        }, "t1");
        Thread t2 = new Thread(new Runnable() {
            @Override
            public void run() {
                changeLock.method();
            }
        }, "t2");
        t1.start();
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        t2.start();
    }
}

程序輸出:

當前線程 : t1開始
當前線程 : t1結束
當前線程 : t2開始
當前線程 : t2結束

兩個程序相比差異在於是否含有 lock = "change lock";這個改變了鎖對象,所以輸出有差異。

2.8 死鎖問題,在設計程序時就應該避免雙方相互持有對方的鎖的情況

package com.ietree.multithread.sync;

public class DeadLock implements Runnable {
    
    private String tag;
    private static Object lock1 = new Object();
    private static Object lock2 = new Object();

    public void setTag(String tag) {
        this.tag = tag;
    }

    @Override
    public void run() {
        if (tag.equals("a")) {
            synchronized (lock1) {
                try {
                    System.out.println("當前線程 : " + Thread.currentThread().getName() + " 進入lock1執行");
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (lock2) {
                    System.out.println("當前線程 : " + Thread.currentThread().getName() + " 進入lock2執行");
                }
            }
        }
        if (tag.equals("b")) {
            synchronized (lock2) {
                try {
                    System.out.println("當前線程 : " + Thread.currentThread().getName() + " 進入lock2執行");
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (lock1) {
                    System.out.println("當前線程 : " + Thread.currentThread().getName() + " 進入lock1執行");
                }
            }
        }
    }

    public static void main(String[] args) {

        DeadLock d1 = new DeadLock();
        d1.setTag("a");
        DeadLock d2 = new DeadLock();
        d2.setTag("b");

        Thread t1 = new Thread(d1, "t1");
        Thread t2 = new Thread(d2, "t2");

        t1.start();
        try {
            Thread.sleep(500);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        t2.start();
    }
}

程序輸出:

當前線程 : t1 進入lock1執行
當前線程 : t2 進入lock2執行

程序一直等待獲取鎖的狀態,造成死鎖問題。

2.9 同一對象屬性的修改不會影響鎖的情況

package com.ietree.multithread.sync;

public class ModifyLock {
    private String name;
    private int age;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public synchronized void changeAttributte(String name, int age) {
        try {
            System.out.println("當前線程 : " + Thread.currentThread().getName() + " 開始");
            this.setName(name);
            this.setAge(age);

            System.out.println("當前線程 : " + Thread.currentThread().getName() + " 修改對象內容為: " + this.getName() + ", "
                    + this.getAge());

            Thread.sleep(2000);
            System.out.println("當前線程 : " + Thread.currentThread().getName() + " 結束");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {
        
        final ModifyLock modifyLock = new ModifyLock();
        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                modifyLock.changeAttributte("Jack", 18);
            }
        }, "t1");
        Thread t2 = new Thread(new Runnable() {
            @Override
            public void run() {
                modifyLock.changeAttributte("Rose", 20);
            }
        }, "t2");

        t1.start();
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        t2.start();
    }
}

程序輸出:

當前線程 : t1 開始
當前線程 : t1 修改對象內容為: Jack, 18
當前線程 : t1 結束
當前線程 : t2 開始
當前線程 : t2 修改對象內容為: Rose, 20
當前線程 : t2 結束

2.10 使用synchronized代碼塊加鎖,比較靈活

package com.ietree.multithread.sync;

public class ObjectLock {
    public void method1() {
        synchronized (this) { // 對象鎖
            try {
                System.out.println("do method1..");
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    public void method2() { // 類鎖
        synchronized (ObjectLock.class) {
            try {
                System.out.println("do method2..");
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    private Object lock = new Object();

    public void method3() { // 任何對象鎖
        synchronized (lock) {
            try {
                System.out.println("do method3..");
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    public static void main(String[] args) {

        final ObjectLock objLock = new ObjectLock();
        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                objLock.method1();
            }
        });
        Thread t2 = new Thread(new Runnable() {
            @Override
            public void run() {
                objLock.method2();
            }
        });
        Thread t3 = new Thread(new Runnable() {
            @Override
            public void run() {
                objLock.method3();
            }
        });

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

    }
}

程序輸出:

do method2..
do method3..
do method1..

以上語句是同時輸出的,因為他們拿到的鎖都是不同的鎖,所以互不影響。

2.11 使用synchronized代碼塊減小鎖的粒度,提高性能

package com.ietree.multithread.sync;

public class Optimize {
    
    public void doLongTimeTask() {
        try {
            System.out.println("當前線程開始:" + Thread.currentThread().getName() + ", 正在執行一個較長時間的業務操作,其內容不需要同步");
            Thread.sleep(2000);

            synchronized (this) {
                System.out.println("當前線程:" + Thread.currentThread().getName() + ", 執行同步代碼塊,對其同步變量進行操作");
                Thread.sleep(1000);
            }
            System.out.println("當前線程結束:" + Thread.currentThread().getName() + ", 執行完畢");

        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {
        final Optimize otz = new Optimize();
        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                otz.doLongTimeTask();
            }
        }, "t1");
        Thread t2 = new Thread(new Runnable() {
            @Override
            public void run() {
                otz.doLongTimeTask();
            }
        }, "t2");
        t1.start();
        t2.start();

    }
}

程序輸出:

當前線程開始:t2, 正在執行一個較長時間的業務操作,其內容不需要同步
當前線程開始:t1, 正在執行一個較長時間的業務操作,其內容不需要同步
當前線程:t1, 執行同步代碼塊,對其同步變量進行操作
當前線程結束:t1, 執行完畢
當前線程:t2, 執行同步代碼塊,對其同步變量進行操作
當前線程結束:t2, 執行完畢

2.12 避免使用字符串常量鎖

package com.ietree.multithread.sync;

public class StringLock {
    
    public void method() {
        // new String("字符串常量")
        synchronized ("字符串常量") {
            try {
                while (true) {
                    System.out.println("當前線程 : " + Thread.currentThread().getName() + "開始");
                    Thread.sleep(1000);
                    System.out.println("當前線程 : " + Thread.currentThread().getName() + "結束");
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    public static void main(String[] args) {
        final StringLock stringLock = new StringLock();
        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                stringLock.method();
            }
        }, "t1");
        Thread t2 = new Thread(new Runnable() {
            @Override
            public void run() {
                stringLock.method();
            }
        }, "t2");

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

程序輸出:

當前線程 : t1開始
當前線程 : t1結束
當前線程 : t1開始
當前線程 : t1結束
當前線程 : t1開始
當前線程 : t1結束
當前線程 : t1開始
......

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM