Java多线程系列-线程的状态、wait()、notify()、synchronized关键字与Lock接口

本文主要记录Java多线程以及多线程的状态转换、多线程中的wait()和notify(),以及synchronized关键字和Lock接口的讲解。

Java多线程

Java中,可运行的程序都是有一个或多个进程组成。进程则是由多个线程组成的。
最简单的一个进程,会包括mian线程以及GC线程。

线程的状态

这里写图片描述

在图中,红框标识的方法表示已经过期,不再使用。

(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) {  

        Thread thread= 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中使用多线程时,一定会遇到一个问题:当多个线程共享一个或一组资源时,该怎么处理,这种资源包括内存、文件等。

很常见的一个例子是,张三在银行账户存有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);  
    }  
}  

接下来是我们操纵账户的方法:

<span style="white-space:pre">        </span>final int NUM=100;  

        Thread[] threads=new Thread[NUM];  
        for(int i=0;i<NUM;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(int i=0;i<NUM;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 AccountSynchronizedBlock account=new AccountSynchronizedBlock("张三", 9999.0f);  
        final int NUM=50;  

        Thread[] threads=new Thread[NUM];  
        for(int i=0;i<NUM;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(int i=0;i<NUM;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 Lock lock=new ReentrantLock();  
    private ReadWriteLock rwLock=new ReentrantReadWriteLock();  

    private List<Integer> list=new ArrayList<Integer>();  

    public void doReentrantLock(Thread thread){  
        lock.lock();  
        System.out.println(thread.getName()+"获取锁");  
        try {  
              for(int i=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(int i=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(int i=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 LockImp lockImp=new LockImp();  

        final Thread thread1=new Thread();  
        final Thread thread2=new Thread();  
        final Thread thread3=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同时使用,避免死锁。

线程池的使用

首先要回答这个问题,为什么要使用线程池?

因为线程池有如下好处:

  1. 在界面上,简化了写法,代码更简洁;
  2. 对程序中的线程可以进行适度的管理;
  3. 有效较低了多个线程的内存占有率等。

这是一篇讲述线程池非常好的文章:
http://www.cnblogs.com/dolphin0520/p/3932921.html

在这里只是简单的封装一个线程池的工具类,仅供参考:

package com.thread.simple;  

import java.util.concurrent.ExecutorService;  
import java.util.concurrent.Executors;  

public class ThreadPoolUtil {  

     private volatile static ThreadPoolUtil instance;  
     private ThreadPoolUtil(){}  
     private static ExecutorService threadPool;  


     public static ThreadPoolUtil getInstance(){  
         if(instance==null){  
             synchronized (ThreadPoolUtil.class) {  
                  instance=new ThreadPoolUtil();  
                 threadPool=Executors.newCachedThreadPool();  
            }  
         }  
         return instance;  
     }  

    public void excute(Runnable runnable){  
        threadPool.execute(runnable);  
    }  

    public void shutdown(){  
        threadPool.shutdown();  
    }  

    public boolean isActive(){  
        if(threadPool.isTerminated()){  
            return false;  
        }  
        return true;  
    }  
}  
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值