• 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("	余额为: " + 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开始
    ......
  • 相关阅读:
    业务层和数据层
    Android开发学习总结——Android开发的一些相关概念(转)
    Android开发学习总结(五)——Android应用目录结构分析(转)
    Android开发学习总结(六)—— APK反编译(转)
    微信开发学习总结(一)——微信开发环境搭建(转)
    PowerMockito使用详解(转)
    java堆栈 (转)
    windows 7 SDK和DDK下载地址
    Linux pipe函数
    火星人的数学观(4)
  • 原文地址:https://www.cnblogs.com/Dylansuns/p/6667051.html
Copyright © 2020-2023  润新知