zoukankan      html  css  js  c++  java
  • 【转】Java多线程学习

    来源:http://www.cnblogs.com/samzeng/p/3546084.html

    Java多线程学习总结--线程概述及创建线程的方式(1)


    在Java开发中,多线程是很常用的,用得好的话,可以提高程序的性能。

    首先先来看一下线程和进程的区别:
    1,一个应用程序就是一个进程,一个进程中有一个或多个线程。一个进程至少要有一个主线程。线程可以看做是轻量级的进程。(lightweight process)
    2,多个线程可以共享进程的资源。进程之间是独立的,一个进程不能共享其它进程的资源。
    3,因为系统创建进程需要为其分配空间,所以创建进程的代价高,创建线程的代价则要小得多。

    创建线程的方式:
    Java中创建多线程有3中方式:

    1,继承Thread类。

    一个类继承Thread类并且重写了run方法之后,如果新建这个类的实例,并调用start方法,那么系统就会启动一个新线程,并执行run方法。代码如下:

    public class ThreadApp {
        public static void main(String[] args){
            // 创建线程
            MyThread thread = new MyThread();
            // 启动线程
            thread.start();
        }
    }
    
    class MyThread extends Thread {
        private int i = 0;
        public void run() {
            for (; i < 5; i++) {
                System.out.println(getName() + ":" + i);
            }
        }
    }

    2,实现Runnable接口。

    定义一个类实现Runnable接口,然后创建该类的实例,然后创建Thread对象,将Runnable实例作为Thread对象的target,最后调用Thread对象的start方法。在执行是,Thread对象会调用Runnable对象的run方法。代码如下:

    public class ThreadApp {
        public static void main(String[] args) {
            // 创建runnable对象
            MyRunnable target = new MyRunnable();
            // 创建thread对象,并将runnable作为thread的target
            Thread thread = new Thread(target);
            // 调用thread的start方法,在线程执行时,会调用target的run方法
            thread.start();
        }
    }
    
    class MyRunnable implements Runnable {
        private int i = 0;
        @Override
        public void run() {
    
            while (i < 500) {
                synchronized (this) {
                    System.out
                            .println(Thread.currentThread().getName() + ":" + i++);
                }
            }
        }
    }

    3,实现Callable接口。

    Callable是一个泛型接口,这种创建多线程的方式可以获得线程的执行后的返回值。创建步骤为:定义一个类MyCallable实现Callable接口,创建MyCallable的实例,然后创建FutureTask对象target来包装Callable对象,因为FutureTask类实现了Runnable接口,所以可以作为Thread的target属性。最后创建Thread。当线程执行完毕之后,调用FutureTask的get方法获取的返回值。代码如下:

    public class ThreadApp {
        public static void main(String[] args) {
            // 创建Callable对象
            MyCallable callable = new MyCallable();
            // 创建FutureTask对象包装callable,FutureTask类实现了Runnable接口,所以可以作为Thread类的target
            FutureTask<String> target = new FutureTask<>(callable);
            // 创建线程
            Thread thread = new Thread(target);
            // 启动线程
            thread.start();
            try {
                // 获得线程执行结果
                String result = target.get();
                System.out.println(result);
            } catch (InterruptedException | ExecutionException e) {
                e.printStackTrace();
            }
        }
    }
    
    class MyCallable implements Callable<String> {
        @Override
        public String call() throws Exception {
            synchronized (this) {
                System.out.println(Thread.currentThread().getName());
                Thread.sleep(1000);
                return new Date().toString();
            }
        }
    
    }

    三种创建线程方式的比较:

    使用Thread创建线程最简单,它有getName()方法可以直接获取当前线程的名称。但是不够灵活。多个线程之间不能共享Thread的属性。
    使用Runnable和Callable是一样的,都是先实现这两个接口,然后将实现类的实例作为Thread的target来创建线程。使用Callable接口可以创建带返回值的线程。另外,多个线程之间可以共享target的属性。代码如下:

    public class ThreadApp {
        public static void main(String[] args) {
            MyRunnable target = new MyRunnable();
            // 两个线程使用同一个target,可以共享target中的属性
            Thread thread1 = new Thread(target);
            Thread thread2 = new Thread(target);
            thread1.start();
            thread2.start();
        }
    }

    Java多线程学习总结--线程同步(2)

    线程同步是为了让多个线程在共享数据时,保持数据的一致性。举个例子,有两个人同时取钱,假设用户账户余额是1000,第一个用户取钱800,在第一个用户取钱的同时,第二个用户取钱600。银行规定,用户不允许透支,当余额不足时,应该取钱失败。我们先来看一下,如果线程不同步,会出现什么情况。代码如下:

    public class SynchronizeApp {
    
        /**
         * @param args
         */
        public static void main(String[] args) {
    
            // 获得账户
            Account account = new Account();
            account.setCardNo("95559");
            account.setBalance(1000);
            // 用户1取款800
            DrawMoney user1 = new DrawMoney(account, 800);
            // 用户1取款600
            DrawMoney user2 = new DrawMoney(account, 600);
            user1.start();
            user2.start();
        }
    
    }
    
    class DrawMoney extends Thread {
        private Account account;
        private double amount;
    
        public DrawMoney(Account account, double amount) {
            this.account = account;
            this.amount = amount;
        }
    
        @Override
        public void run() {
            account.draw(amount);
        }
    }
    
    class Account {
        private String cardNo;
        private double balance;
    
        public String getCardNo() {
            return cardNo;
        }
    
        public void setCardNo(String cardNo) {
            this.cardNo = cardNo;
        }
    
        public double getBalance() {
            return balance;
        }
    
        public void setBalance(double balance) {
            this.balance = balance;
        }
    
        /**
         * 用户取款
         * 
         * @param amount
         *            ,取款数量
         */
        public void draw(double amount) {
            if (amount > balance) {
                System.out.println("金额不足!");
            } else {
                try {
                    // 模拟取款过程
                    Thread.sleep(100);
                    balance = balance - amount;
                    System.out.println("成功取款" + amount + "元, 最新余额为" + balance);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
    
        }
    }

    运行结果如下:



    可见,如果没有线程同步,当两个线程同时取款时,就会出现数据错误。第二个线程取款时,读取到的账户余额是1000,所以可以执行取款操作,但是进行实际取款时,账户余额被第一个线程修改,实际余额是200,所以取出600后最新余额是-400,同样第一个用户也出现了数据错误,余额是1000,取款800后却变成了-400。这种情况是不允许的。

    线程同步有两种方式,第一种是用synchronized关键字,第二种是用lock对象。

    使用synchronized关键字可以对方法和代码块进行同步,使用synchronized关键字对方法进行同步时,将synchronized关键字放在方法返回类型前面,synchronized自动锁定当前对象。上边取款操使用synchronized关键字同步方法的代码如下:

    class Account {
        private String cardNo;
        private double balance;
    
        public String getCardNo() {
            return cardNo;
        }
    
        public void setCardNo(String cardNo) {
            this.cardNo = cardNo;
        }
    
        public double getBalance() {
            return balance;
        }
    
        public void setBalance(double balance) {
            this.balance = balance;
        }
    
        /**
         * 用户取款
         * 
         * @param amount
         *            ,取款数量
         */
        public synchronized void draw(double amount) {
    
            if (amount > balance) {
                System.out.println("金额不足!");
            } else {
                try {
                    // 模拟取款过程
                    Thread.sleep(100);
                    balance = balance - amount;
                    System.out.println("成功取款" + amount + "元, 最新余额为" + balance);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
    
        }
    }

    再次运行程序,得到如下结果:

     

    使用synchronized同步代码块的代码如下:

    /**
         * 用户取款
         * 
         * @param amount
         *            ,取款数量
         */
        public void draw(double amount) {
    
            synchronized (this) {
                if (amount > balance) {
                    System.out.println("金额不足!");
                } else {
                    try {
                        // 模拟取款过程
                        Thread.sleep(100);
                        balance = balance - amount;
                        System.out.println("成功取款" + amount + "元, 最新余额为" + balance);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }

    还可以使用同步锁来对代码进行同步。使用同步锁时,先调用Lock对象的lock方法,代码执行完毕后,再调用Lock对象的unlock方法。在lock和unlock之间的代码是同步的,同一时间段内只能有一个线程能访问。为了保证能释放锁,把unlock方法放在finally语句块中是比较安全的,代码如下:

    private final ReentrantLock lock = new ReentrantLock();
    
        /**
         * 用户取款
         * 
         * @param amount
         *            ,取款数量
         */
        public void draw(double amount) {
            lock.lock();
            try {
                if (amount > balance) {
                    System.out.println("金额不足!");
                } else {
                    // 模拟取款过程
                    Thread.sleep(100);
                    balance = balance - amount;
                    System.out.println("成功取款" + amount + "元, 最新余额为" + balance);
                }
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                lock.unlock();
            }
        }

     运行代码,结果和使用synchronized同步方法的的运行结果一样

  • 相关阅读:
    AJPFX:如何保证对象唯一性呢?
    AJPFX关于this用法和注意事项
    AJPFX关于abstract的总结
    AJPFX区分this和super
    AJPFX关于java数组排序
    AJPFX关于异常和file类的总结
    AJPFX总结Java 类加载器
    优先级队列用法详解(priority_queue)
    子类中调用构造函数和析构函数的顺序
    strcpy,memcpy,memset函数实现
  • 原文地址:https://www.cnblogs.com/sudawei/p/3557006.html
Copyright © 2011-2022 走看看