一、关于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开始
......