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