来源: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同步方法的的运行结果一样

【转】Java多线程学习的更多相关文章

  1. Java多线程学习笔记

    进程:正在执行中的程序,其实是应用程序在内存中运行的那片空间.(只负责空间分配) 线程:进程中的一个执行单元,负责进程汇总的程序的运行,一个进程当中至少要有一个线程. 多线程:一个进程中时可以有多个线 ...

  2. Java多线程学习(转载)

    Java多线程学习(转载) 时间:2015-03-14 13:53:14      阅读:137413      评论:4      收藏:3      [点我收藏+] 转载 :http://blog ...

  3. java多线程学习笔记——详细

    一.线程类  1.新建状态(New):新创建了一个线程对象.        2.就绪状态(Runnable):线程对象创建后,其他线程调用了该对象的start()方法.该状态的线程位于可运行线程池中, ...

  4. JAVA多线程学习笔记(1)

    JAVA多线程学习笔记(1) 由于笔者使用markdown格式书写,后续copy到blog可能存在格式不美观的问题,本文的.mk文件已经上传到个人的github,会进行同步更新.github传送门 一 ...

  5. Java多线程学习(六)Lock锁的使用

    系列文章传送门: Java多线程学习(二)synchronized关键字(1) Java多线程学习(二)synchronized关键字(2) Java多线程学习(三)volatile关键字 Java多 ...

  6. Java多线程学习(五)线程间通信知识点补充

    系列文章传送门: Java多线程学习(二)synchronized关键字(1) Java多线程学习(二)synchronized关键字(2) Java多线程学习(三)volatile关键字 Java多 ...

  7. Java多线程学习(四)等待/通知(wait/notify)机制

    转载请备注地址:https://blog.csdn.net/qq_34337272/article/details/79690279 系列文章传送门: Java多线程学习(一)Java多线程入门 Ja ...

  8. Java多线程学习(三)volatile关键字

    转载请备注地址:https://blog.csdn.net/qq_34337272/article/details/79680693 系列文章传送门: Java多线程学习(一)Java多线程入门 Ja ...

  9. Java多线程学习(二)synchronized关键字(2)

    转载请备注地址:https://blog.csdn.net/qq_34337272/article/details/79670775 系列文章传送门: Java多线程学习(一)Java多线程入门 Ja ...

随机推荐

  1. QQ音乐API分析记录

    我一直是QQ音乐的用户,最近想做一个应用,想用QQ音乐的API,搜索了很久无果,于是就自己分析QQ音乐的API. 前不久发现QQ音乐出了网页版的,是Flash的,但是,我用iPhone打开这个链接的时 ...

  2. 【Winform】 无法将类型为“System.Windows.Forms.SplitContainer”的对象强制转换为类型“System.ComponentModel.ISupportInitialize”。

    问题:将dotnet framework 4.0 切换到2.0时,编译没有问题,在运行时出现如下错误:System.InvalidCastException: 无法将类型为“System.Window ...

  3. linux安装Vmware的时候出现“Could not open /dev/vmmon”

    在centos6.6上安装了Vmware之后运行出现下列问题 VMware Workstation : Could not open /dev/vmmon: No such file or direc ...

  4. 在linux下配置Nginx+Java+PHP的环境

    Apache对Java的支持很灵活,它们的结合度也很高,例如Apache+Tomcat和Apache+resin等都可以实现对Java应用 的支持.Apache一般采用一个内置模块来和Java应用服务 ...

  5. Linux学习1

    Linux中一切皆文件,且不依靠扩展名区分文件,学习Linux必须要熟悉在字符界面进行文件的管理. 首先是Linux的查询命令. (1)ls -a是显示当前目录所有文件,包含隐藏文件,如图中文件名前加 ...

  6. javascript与DOM的渊源

    1. JavaScript的起源 1.1 JavaScript的诞生与发展 JavaScript最初由Netscape的Brendan Eich设计, Netscape在最初将其脚本语言命名为Live ...

  7. js 手机端触发事事件、javascript手机端/移动端触发事件

    处理Touch事件能让你跟踪用户的每一根手指的位置.你可以绑定以下四种Touch事件: touchstart: // 手指放到屏幕上的时候触发 touchmove: // 手指在屏幕上移动的时候触发 ...

  8. 元类metaClass

    metaClass 实现动态改变对象的能力,这点特别像python(metaClass),Python中类(不是元类)的概念借鉴于Smalltalk groovy demo: class Person ...

  9. iOS 进阶 第一天(0323)

    0323 Storyboard连线错误 如下图: 不允许直接修改对象的结构体属性成员,但允许直接整体修改对象的结构体属性 如下图: 打印一个控件对象的frame 如下图: 如果一个控件无论怎么改变它的 ...

  10. 【转】代码控制UI,View

    [转]Android 步步为营 第5营 代码控制UI,View   http://www.cnblogs.com/vivid-stanley/archive/2012/08/22/2651399.ht ...