【转】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同步方法的的运行结果一样

原文地址:https://www.cnblogs.com/sudawei/p/3557006.html