Java多线程

Java中,可运行的程序都是有一个或多个进程组成。进程则是由多个线程组成的。

最简单的一个进程,会包括mian线程以及GC线程。

线程的状态

线程状态由以下一张网上图片来说明:

img

在图中,红框标识的部分方法,可以认为已过时,不再使用。

(1)wait、notify、notifyAll是线程中通信可以使用的方法。线程中调用了wait方法,则进入阻塞状态,只有等另一个线程调用与wait同一个对象的notify方法。这里有个特殊的地方,调用wait或者notify,前提是需要获取锁,也就是说,需要在同步块中做以上操作。

(2)join方法。该方法主要作用是在该线程中的run方法结束后,才往下执行。如以下代码:

package com.thread.simple;

public class ThreadJoin {

public static void main(String[] args) {

Threadthread= new Thread(new Runnable() {

​ @Override

​ public void run() {

​ System.err.println(“线程”+Thread.currentThread().getId()+” 打印信息”);

​ }

​ });

​ thread.start();

​ try {

​ thread.join();

​ } catch (InterruptedException e) {

​ // TODO Auto-generated catch block

​ e.printStackTrace();

​ }

​ System.err.println(“主线程打印信息”);

}

}

该方法显示的信息是:

线程8 打印信息

主线程打印信息

如果去掉其中的join方法,则显示如下:

主线程打印信息

线程8 打印信息

(3)yield方法。这个是线程本身的调度方法,使用时你可以在run方法执行完毕时,调用该方法,告知你已可以出让内存资源。

其他的线程方法,基本都会在日常中用到,如start、run、sleep,这里就不再介绍。

Synchronized(同步锁)

在Java中使用多线程,你就不能绕过同步锁这个概念。这在多线程中是十分重要的。

在Java多线程的使用中,你必然会遇到一个问题:多个线程共享一个或者一组资源,这资源包括内存、文件等。

很常见的一个例子是,张三在银行账户存有9999元,经过多次的取100,存100后,账户还有多少钱?

看代码:

以下表示账户信息:

package com.thread.simple;

import java.sql.Time;

import java.util.concurrent.TimeUnit;

public class Account {

private String name;

private float amt;

public Account(String name,float amt) {

this.name=name;

this.amt=amt;

}

public void increaseAmt(float increaseAmt){

​ try {

​ TimeUnit.SECONDS.sleep(1);

​ } catch (InterruptedException e) {

​ // TODO Auto-generated catch block

​ e.printStackTrace();

​ }

​ amt+=increaseAmt;

}

public void decreaseAmt(float decreaseAmt){

​ try {

​ TimeUnit.SECONDS.sleep(1);

​ } catch (InterruptedException e) {

​ // TODO Auto-generated catch block

​ e.printStackTrace();

​ }

amt-=decreaseAmt;

}

public void printMsg(){

​ System.out.println(name+”账户现有金额为:”+amt);

}

}

以下是我们操作账户的方法:

​ final int NUM=100;

Thread[]threads=new Thread[NUM];

for(inti=0;i

​ if(threads[i]==null){

​ threads[i]=new Thread(new Runnable() {

​ @Override

​ public void run() {

​ account.increaseAmt(100f);

​ account.decreaseAmt(100f);

​ }

​ });

​ threads[i].start();

​ }

​ }

for(inti=0;i

​ try {

​ threads[i].join();

​ } catch (InterruptedException e) {

​ // TODO Auto-generated catch block

​ e.printStackTrace();

​ }

​ }

​ account.printMsg();

你会发现,每次打印出来的账户余额都不一定是一样的。这就是同步锁的必要性。

java中,提供了多种使用同步锁的方式。

(1)对动态方法的修饰。

作用的是调用该方法的对象(或者说对象引用)。

public synchronized void doSomething(){}

( 2) 对代码块的修饰。

作用的是调用该方法的对象(或者说对象引用)。

public void increaseAmt(float increaseAmt){

​ try {

​ TimeUnit.SECONDS.sleep(1);

​ } catch (InterruptedException e) {

​ // TODO Auto-generated catch block

​ e.printStackTrace();

​ }

​ synchronized (this) {

​ System.out.println(this);

​ amt+=increaseAmt;

​ }

}

(3)对静态方法的修饰。

作用的是静态方法所在类的所有对象(或者说对象引用)。

public synchronized static void increaseAmt(float increaseAmt){

​ try {

​ TimeUnit.SECONDS.sleep(1);

​ } catch (InterruptedException e) {

​ // TODO Auto-generated catch block

​ e.printStackTrace();

​ }

​ amt+=increaseAmt;

}

(4)对类的修饰。

作用的是静态方法所在类的所有对象(或者说对象引用)。

synchronized (AccountSynchronizedClass.class) {

amt-=decreaseAmt;

}

以修饰代码块的方式为例,我们重新运行以上代码后,得到了正确的结果。代码如下:

package com.thread.simple;

import java.util.concurrent.TimeUnit;

/**

* Synchronized 代码块

* @author 战国

*

*/

public class AccountSynchronizedBlock {

private String name;

private float amt;

public AccountSynchronizedBlock(String name,float amt) {

this.name=name;

this.amt=amt;

}

public void increaseAmt(float increaseAmt){

​ try {

​ TimeUnit.SECONDS.sleep(1);

​ } catch (InterruptedException e) {

​ // TODO Auto-generated catch block

​ e.printStackTrace();

​ }

​ synchronized (this) {

​ System.out.println(this);

​ amt+=increaseAmt;

​ }

}

public void decreaseAmt(float decreaseAmt){

​ try {

​ TimeUnit.SECONDS.sleep(1);

​ } catch (InterruptedException e) {

​ // TODO Auto-generated catch block

​ e.printStackTrace();

​ }

​ synchronized (this) {

​ System.out.println(this);

amt-=decreaseAmt;

​ }

}

public void printMsg(){

​ System.out.println(name+”账户现有金额为:”+amt);

}

}

//多线程synchronized修饰代码块 ,每次计算的值都一样

final AccountSynchronizedBlockaccount=new AccountSynchronizedBlock(“张三”, 9999.0f);

final intNUM=50;

Thread[]threads=new Thread[NUM];

for(inti=0;i

​ if(threads[i]==null){

​ threads[i]=new Thread(new Runnable() {

​ @Override

​ public void run() {

​ account.increaseAmt(100f);

​ account.decreaseAmt(100f);

​ }

​ });

​ threads[i].start();

​ }

​ }

for(inti=0;i

​ try {

​ threads[i].join();

​ } catch (InterruptedException e) {

​ // TODO Auto-generated catch block

​ e.printStackTrace();

​ }

​ }

​ account.printMsg();

以上是同步锁的简单说明。

在JDK5中,Java又引入了一个相似的概念Lock,也就是锁。功能与synchronized是类似的。

Lock

Lock对比synchronized有高手总结的差异如下:

总结来说,Lock和synchronized有以下几点不同:

  1)Lock是一个接口,而synchronized是Java中的关键字,synchronized是内置的语言实现;

  2)synchronized在发生异常时,会自动释放线程占有的锁,因此不会导致死锁现象发生;而Lock在发生异常时,如果没有主动通过unLock()去释放锁,则很可能造成死锁现象,因此使用Lock时需要在finally块中释放锁;

  3)Lock可以让等待锁的线程响应中断,而synchronized却不行,使用synchronized时,等待的线程会一直等待下去,不能够响应中断;

  4)通过Lock可以知道有没有成功获取锁,而synchronized却无法办到。

  5)Lock可以提高多个线程进行读操作的效率。

  在性能上来说,如果竞争资源不激烈,两者的性能是差不多的,而当竞争资源非常激烈时(即有大量线程同时竞争),此时Lock的性能要远远优于synchronized。所以说,在具体使用时要根据适当情况选择。

(参考http://www.cnblogs.com/dolphin0520/p/3923167.html)。

Lock的操作与synchronized相比,灵活性更高,而且Lock提供多种方式获取锁,有Lock、ReadWriteLock接口,以及实现这两个接口的ReentrantLock类、ReentrantReadWriteLock类。

对Lock的简单操作代码如下:

package com.thread.simple;

import java.util.ArrayList;

import java.util.List;

import java.util.concurrent.locks.Lock;

import java.util.concurrent.locks.ReadWriteLock;

import java.util.concurrent.locks.ReentrantLock;

import java.util.concurrent.locks.ReentrantReadWriteLock;

public class LockImp {

private Locklock=new ReentrantLock();

private ReadWriteLockrwLock=new ReentrantReadWriteLock();

private List list=new ArrayList();

public void doReentrantLock(Thread thread){

​ lock.lock();

​ System.out.println(thread.getName()+”获取锁”);

​ try {

for(inti=0;i<10;i++){

​ list.add(i);

​ }

​ } catch (Exception e) {

​ }finally{

​ lock.unlock();

​ System.out.println(thread.getName()+”释放锁”);

​ }

}

public void doReentrantReadLock(Thread thread){

​ rwLock.readLock().lock();

​ System.out.println(thread.getName()+”获取读锁”);

​ try {

for(inti=0;i<10;i++){

​ list.add(i);

​ }

​ } catch (Exception e) {

​ }finally{

​ rwLock.readLock().unlock();

​ System.out.println(thread.getName()+”释放读锁”);

​ }

}

public void doReentrantWriteLock(Thread thread){

​ rwLock.writeLock().lock();

​ System.out.println(thread.getName()+”获取写锁”);

​ try {

for(inti=0;i<10;i++){

​ list.add(i);

​ }

​ } catch (Exception e) {

​ }finally{

​ rwLock.writeLock().unlock();

​ System.out.println(thread.getName()+”释放写锁”);

​ }

}

/**

* @param args

*/

public static void main(String[] args) {

final LockImplockImp=new LockImp();

final Threadthread1=new Thread();

final Threadthread2=new Thread();

final Threadthread3=new Thread();

​ new Thread(new Runnable() {

​ @Override

​ public void run() {

​ lockImp.doReentrantLock(thread1);

​ }

​ }).start();

​ new Thread(new Runnable() {

​ @Override

​ public void run() {

​ lockImp.doReentrantLock(thread2);

​ }

​ }).start();

​ new Thread(new Runnable() {

​ @Override

​ public void run() {

​ lockImp.doReentrantLock(thread3);

​ }

​ }).start();

​ lockImp.doReentrantReadLock(thread1);

​ lockImp.doReentrantReadLock(thread2);

​ lockImp.doReentrantReadLock(thread3);

​ lockImp.doReentrantWriteLock(thread1);

​ lockImp.doReentrantWriteLock(thread2);

​ lockImp.doReentrantWriteLock(thread3);

}

}

Lock的使用中,务必需要lock、unlock同时使用,避免死锁。