JAVA多线程机制

参考<<疯狂JAVA讲义>> 第16章节,还可以参考sina博文 "JAVA多线程相关介绍"

多线程概述
个人觉得这方面已经有很多资料介绍了,不过还是觉得 <<疯狂JAVA讲义>>线程概述还是挺透彻,明了的

2种方式创建线程,一种是extends Thread,一种是implements Runnable
这里需要注意的是Runnable对象仅仅是作为Thread对象的target,Runnable的run方法作为仅是线程执行体

线程的生命周期(这里疯狂JAVA讲义在这块讲的很好)

JAVA多线程机制

和线程控制有关的方法:
start():新建的线程进入Runnable状态.
run():线程进入Running状态(不要直接在程序中调用线程的run方法,是由系统调用的)。
wait():线程进入等待状态,等待被notify,这是对象方法,而不是线程方法
notify()/notifyAll():唤醒其他线程,这是对象方法,而不是线程方法
yield():线程放弃执行,使其他优先级不低于该线程的线程有机会运行,是静态方法
getPriority()/setPriority():设置线程优先级
sleep():线程睡眠一段时间
join():调用这个方法的主线程,会等待加入的子线程完成。

多线程同步
A:关键字synchronized 来与对象的互斥锁联系
1)synchronized关键字可以修饰代码块,方法,但是不可以修饰构造器,属性等。
2)特别指出的是synchronized锁定的不是方法或代码块,而是对象。当synchronized作为方法的修饰符时,它所取得的对象锁将被转交给方法的调用者;
当synchronized修饰的是对象时,则取得的对象锁将被转交给该引用指向的对象。Synchronized也可以修改类,表示这个类的所有方法都是Synchronized的
3)释放同步监视器(synchronized)的锁定:
当线程执行到synchronized()块结束时,释放对象锁
当在synchronized()块中遇到break, return或抛出exception,则自动释放对象锁
当一个线程调用wait()方法时,它放弃拥有的对象锁并进入blocked 状态

public class SuperTest 

{

    public static void main(String[] str)

    {

        Counter ct = new Counter();

        C1 c1 = new C1(ct, " c1 ");

        C1 c2 = new C1(ct, " c2 ");

        

        //启动2个子线程

        c2.start();

        c1.start();

        

        try {

            c2.join();

            c1.join();

        } catch (InterruptedException e) {

            e.printStackTrace();

        }

        

        System.out.println(ct.getCount());

    }

}

    class Counter {

        private int count = 1000;



        protected int getCount() {

            return count;

        }



        protected void setCount(int count) {

            this.count = count;

        }

    }



    class C1 extends Thread {

        private Counter counter;



        private String sname;



        public C1(Counter cc, String s1) {

            this.counter = cc;

            this.sname = s1;

        }



        public Counter getCounter() {

            return counter;

        }



        public void setCounter(Counter counter) {

            this.counter = counter;

        }



        public String getSname() {

            return sname;

        }



        public void setSname(String sname) {

            this.sname = sname;

        }



        public void run() {

            for (int j = 0; j < 100; j++) {

                synchronized (counter){

                int i = counter.getCount();

                System.out.println(sname + j +  " before " + i);

                //System.out.println("now in :" + sname + " count = " + i);

                

                AddCounter(i - 1);

                    System.out.println(sname + j +  " after " + counter.getCount());

                }

            }

        }

        

        public void AddCounter(int i) {

            counter.setCount(i);

        }

    }

    

 

B:同步锁Lock

public class TestDraw

{

    public static void main(String[] args) 

    {

        //创建一个账户

        Account acct = new Account("1234567" , 1000);

        //模拟两个线程对同一个账户取钱

        new DrawThread("甲" , acct , 800).start();

        new DrawThread("乙" , acct , 800).start();

    }

}



public class DrawThread extends Thread

{

    //模拟用户账户

    private Account account;

    //当前取钱线程所希望取的钱数

    private double drawAmount;



    public DrawThread(String name , Account account , 

        double drawAmount)

    {

        super(name);

        this.account = account;

        this.drawAmount = drawAmount;

    }



    //当多条线程修改同一个共享数据时,将涉及到数据安全问题。

    public void run()

    {

        account.draw(drawAmount);

    }

}



public class Account

{

    //定义锁对象

    private final ReentrantLock lock = new ReentrantLock();

    private String accountNo;

    private double balance;





    public Account(){}



    public Account(String accountNo , double balance)

    {

        this.accountNo = accountNo;

        this.balance = balance;

    }



    public void setAccountNo(String accountNo)

    {

        this.accountNo = accountNo;

    }

    public String getAccountNo()

    {

         return this.accountNo;

    }



    public double getBalance()

    {

         return this.balance;

    }

    public void draw(double drawAmount)

    {

        lock.lock();

        try

        {

            //账户余额大于取钱数目

            if (balance >= drawAmount)

            {

                //吐出钞票

                System.out.println(Thread.currentThread().getName() + 

                    "取钱成功!吐出钞票:" + drawAmount);

                try

                {

                    Thread.sleep(1);            

                }

                catch (InterruptedException ex)

                {

                    ex.printStackTrace();

                }

                //修改余额

                balance -= drawAmount;

                System.out.println("\t余额为: " + balance);

            }

            else

            {

                System.out.println(Thread.currentThread().getName() +

                    "取钱失败!余额不足!");

            }            

        }

        finally

        {

            lock.unlock();

        }

    }



    public int hashCode()

    {

        return accountNo.hashCode();

    }

    public boolean equals(Object obj)

    {

        if (obj != null && obj.getClass() == Account.class)

        {

            Account target = (Account)obj;

            return target.getAccountNo().equals(accountNo);

        }

        return false;

    }

}

 

线程通信
(1) 线程的协调运行
wait(),notify(),notifyAll().这3个方法是Object类的方法,而不是Thread的方法,而且这3个方法必须由同步监视器(synchronized)对象来调用,2种情况:
1.synchronized修饰同步方法,因为该类默认实例(this)就是同步监视器,所以可以在同步方法中直接调用这3个方法
2.synchronized修饰同步代码块,同步监视器是synchronized括号中的对象,所以必须使用该对象调用这3个方法

wait()方法:
1. wait()方法是Object对象的方法,而不是Thread的方法
2. wait()方法只可能在synchronized块中被调用
3. wait()被调用时,原来的锁对象打开锁,线程进入blocked状态
4. wait()时间到期或被notify()唤醒的线程从wait()后面的代码开始继续执行
5. wait()和sleep()的主要区别是wait()会释放对象锁,而sleep()不会。

notify()和notifyAll():
1.只能在synchronized中被调用
2.notify它会唤起同一个锁对象上的一个等待线程.但如果有几个线程在等待列表中,它无法决定是哪一个线程被唤醒。所以,为了防止不该唤醒的线程被唤醒,应该调用notifyAll,让所有的等待线程都有机会运行。

public class SuperTest 

{

    public static void main(String[] str)

    {

        Counter ct = new Counter();

        C2 c2 = new C2(ct, " c2 ");

        C1 c1 = new C1(ct, " c1 ");

        C2 c3 = new C2(ct, " c3 ");

        C1 c4 = new C1(ct, " c4 ");

        c3.start();

        c4.start();

        c2.start();

        c1.start();

        try {

            c2.join();

            c1.join();

            c3.join();

            c4.join();



        } catch (InterruptedException e) {

            // TODO Auto-generated catch block

            e.printStackTrace();

        }

        System.out.println(ct.getCount());

    }

}



class Counter {

    private int count = 1000;



    public   int getCount() {

        return count;

    }



    public  void setCount(int count) {

        this.count = count;

    }

    

    public synchronized void addCounter(int j,String sname) {

        while (this.getCount() > 1007){

            try {

                this.wait();

            } catch (InterruptedException e) {

                // TODO Auto-generated catch block

                e.printStackTrace();

            }

        }

                

        int i = this.getCount();

        System.out.println(sname + j +  " before " + i);

        //System.out.println("now in :" + sname + " count = " + i);

        

        this.setCount(i+1);

        this.notifyAll();

        System.out.println(sname + j +  " after " + this.getCount());

    }    

    

    public synchronized void delCounter(int j,String sname) {

        while (this.getCount() < 990){

            try {

                this.wait();

            } catch (InterruptedException e) {

                // TODO Auto-generated catch block

                e.printStackTrace();

            }

        }

                

        int i = this.getCount();

        System.out.println(sname + j +  " before " + i);

        //System.out.println("now in :" + sname + " count = " + i);

        this.setCount(i - 1);

        this.notifyAll();

        System.out.println(sname + j +  " after " + this.getCount());

    }    

}



class C1 extends Thread {

    private Counter counter;



    private String sname;



    public C1(Counter cc, String s1) {

        this.counter = cc;

        this.sname = s1;

    }



    public Counter getCounter() {

        return counter;

    }



    public void setCounter(Counter counter) {

        this.counter = counter;

    }



    public String getSname() {

        return sname;

    }



    public void setSname(String sname) {

        this.sname = sname;

    }



    public void run() {

        for (int j = 0; j < 100; j++) {

            /*synchronized (counter){

            delCounter(j);

            }

            */

            counter.delCounter(j,sname);

        }

    }

}



class C2 extends Thread {

    private Counter counter;

    private String sname;

    

    public C2(Counter cc, String s1) {

        this.counter = cc;

        this.sname = s1;

    }



    public Counter getCounter() {

        return counter;

    }



    public void setCounter(Counter counter) {

        this.counter = counter;

    }



    public String getSname() {

        return sname;

    }



    public void setSname(String sname) {

        this.sname = sname;

    }



    public void run() {

        for (int j = 0; j < 100; j++) {

            /*

            synchronized (counter) {

            addCounter(j);

            }

            */

            if (j % 19 == 0) 

            { 

                try {

                        sleep(0,100);

                    } catch (InterruptedException e) {

                            // TODO Auto-generated catch block

                            e.printStackTrace();

                    }

            }

            

            counter.addCounter(j,sname);

        }

    }

}

这里使用的while作为判断语句,而不是if,这就是多线程中的“旋锁spin lock”的概念。这样可以避免用if带来的问题(仔细想想),是这样的,我们在一个线程被唤醒时候要重新检测它的等待条件,一般用旋锁来实现。


我们可以和以前我们做项目用的UDI的线程处理来做对比,以前我们要保证共享数据操作的完整性,实现数据的同步时需要我们自己创建互斥量CSUDIOSMutexCreate,然后自己主动调用CSUDIOSMutexWait和CSUDIOSMutexRelease等待和释放互斥量来实现,而JAVA直接使用synchronized来实现了UDI互斥量类似的功能。
JAVA来用wait,notify,notifyAll来实现线程之间的通信,我们以前一般没有类似的线程通信,有一个比较类似的做法,比如要我们要结束某个线程,一般都是定义一个全局变量,修改其FLAG,然后调用CSUDIOSThreadJoin等待其结束,而在实际的线程函数中一般会用一个while循环不断检测该全局变量的flag,以便可以及时结束线程。

(2) 使用条件变量控制协调
如果程序不使用synchronized来保存同步,而是直接使用Lock对象来保证同步,那么系统中不存在隐式的同步监视器对象,也就不能使用
wait(),notify(),notifyAll()实现同步了,那么我们可以利用条件变量Condition来实现。

public class Account

{

    //显示定义Lock对象

    private final Lock lock = new ReentrantLock();

    //获得指定Lock对象对应的条件变量

    private final Condition cond  = lock.newCondition(); 



    private String accountNo;

    private double balance;



    //标识账户中是否已经存款的旗标

    private boolean flag = false;



    public Account(){}



    public Account(String accountNo , double balance)

    {

        this.accountNo = accountNo;

        this.balance = balance;

    }



    public void setAccountNo(String accountNo)

    {

        this.accountNo = accountNo;

    }

    public String getAccountNo()

    {

         return this.accountNo;

    }



    public double getBalance()

    {

         return this.balance;

    }

    public void draw(double drawAmount)

    {

        //加锁

        lock.lock();

        try

        {

            //如果账户中还没有存入存款,该线程等待

            if (!flag)

            {

                cond.await();

            }

            else

            {

                //执行取钱操作

                System.out.println(Thread.currentThread().getName() + 

                    " 取钱:" +  drawAmount);

                balance -= drawAmount;

                System.out.println("账户余额为:" + balance);

                //将标识是否成功存入存款的旗标设为false

                flag = false;

                //唤醒该Lock对象对应的其他线程

                cond.signalAll();

            }

        }

        catch (InterruptedException ex)

        {

            ex.printStackTrace();

        }

        //使用finally块来确保释放锁

        finally

        {

            lock.unlock();

        }

    }

    public void deposit(double depositAmount)

    {

        lock.lock();

        try

        {

            //如果账户中已经存入了存款,该线程等待

            if(flag)

            {

                cond.await();                

            }

            else

            {

                //执行存款操作

                System.out.println(Thread.currentThread().getName() + 

                    " 存款:" +  depositAmount);

                balance += depositAmount;

                System.out.println("账户余额为:" + balance);

                //将标识是否成功存入存款的旗标设为true

                flag = true;

                //唤醒该Lock对象对应的其他线程

                cond.signalAll();

            }

        }

        catch (InterruptedException ex)

        {

            ex.printStackTrace();

        }

        //使用finally块来确保释放锁

        finally

        {

            lock.unlock();

        }

    }



    public int hashCode()

    {

        return accountNo.hashCode();

    }

    public boolean equals(Object obj)

    {

        if (obj != null && obj.getClass() == Account.class)

        {

            Account target = (Account)obj;

            return target.getAccountNo().equals(accountNo);

        }

        return false;

    }

}

 

(3) 使用管道流
管道流的3种存在形式:
PipedInputStream和PipedOutputStream,PipedReader和PipedWriter,Pipe.SinkChannel和Pipe.SourceChannel

通常没有必要使用管道流来控制2个线程间通信,因为2个线程属于同一个进程,它们可以非常方便的共享数据,不必用管流

class ReaderThread extends Thread

{

    private PipedReader pr;

    //用于包装管道流的BufferReader对象

    private BufferedReader br;

    public ReaderThread(){}

    public ReaderThread(PipedReader pr)

    {

        this.pr = pr;

        this.br = new BufferedReader(pr);

    }

    public void run()

    {

        String buf = null;

        try

        {

            //逐行读取管道输入流中的内容

            while ((buf = br.readLine()) != null)

            {

                System.out.println(buf);

            }

        }

        catch (IOException ex)

        {

            ex.printStackTrace();

        }

        //使用finally块来关闭输入流

        finally

        {

            try

            {

                if (br != null)

                {

                    br.close();

                }

            }

            catch (IOException ex)

            {

                ex.printStackTrace();

            }

        }

    }

}

class WriterThread extends Thread

{

    String[] books = new String[]

    {

        "Struts2权威指南",

        "ROR敏捷开发指南",

        "基于J2EE的Ajax宝典",

        "轻量级J2EE企业应用指南"

    };

    private PipedWriter pw;

    public WriterThread(){}

    public WriterThread(PipedWriter pw)

    {

        this.pw = pw;

    }

    public void run()

    {

        try

        {

            //循环100次,向管道输出流中写入100个字符串

            for (int i = 0; i < 100 ; i++)

            {

                pw.write(books[i % 4] + "\n");

            }

        }

        catch (IOException ex)

        {

            ex.printStackTrace();

        }

        //使用finally块来关闭管道输出流

        finally

        {

            try

            {

                if (pw != null)

                {

                    pw.close();

                }

            }

            catch (IOException ex)

            {

                ex.printStackTrace();

            }

        }

    }

}



public class PipedCommunicationTest

{

    public static void main(String[] args)

    {

        PipedWriter pw = null;

        PipedReader pr = null;

        try

        {

            //分别创建两个独立的管道输出流、输入流

            pw = new PipedWriter();

            pr = new PipedReader();

            //连接管道输出流、出入流

            pw.connect(pr);



            //将连接好的管道流分别传入2个线程,

            //就可以让两个线程通过管道流进行通信

            new WriterThread(pw).start();

            new ReaderThread(pr).start();

        }

        catch (IOException ex)

        {

            ex.printStackTrace();

        }

    }

}

 

JAVA线程池:(参考疯狂JAVA编程对线程池的介绍)

为什么要用线程池:
1)减少了创建和销毁线程的次数,每个工作线程都可以被重复利用,可执行多个任务 2)可以根据系统的承受能力,调整线程池中工作线线程的数目,防止因为因为消耗过多的内存,
而把服务器累趴下(每个线程需要大约1MB内存,线程开的越多,消耗的内存也就越大,最后死机)

几个重要的类:
ExecutorService:真正的线程池接口。
ScheduledExecutorService: 能和Timer/TimerTask类似,解决那些需要任务重复执行的问题。
ThreadPoolExecutor: ExecutorService的默认实现。
ScheduledThreadPoolExecutor: 继承ThreadPoolExecutor的ScheduledExecutorService接口实现,周期性任务调度的类实现。

Executors类里面提供了一些静态工厂,生成一些常用的线程池。
newSingleThreadExecutor:
创建一个单线程的线程池。这个线程池只有一个线程在工作,也就是相当于单线程串行执行所有任务。
如果这个唯一的线程因为异常结束,那么会有一个新的线程来替代它。此线程池保证所有任务的执行顺序按照任务的提交顺序执行。

newFixedThreadPool:
创建固定大小的线程池。每次提交一个任务就创建一个线程,直到线程达到线程池的最大大小。
线程池的大小一旦达到最大值就会保持不变,如果某个线程因为执行异常而结束,那么线程池会补充一个新线程。

newCachedThreadPool:
创建一个可缓存的线程池。如果线程池的大小超过了处理任务所需要的线程,那么就会回收部分空闲(60秒不执行任务)的线程,
当任务数增加时,此线程池又可以智能的添加新线程来处理任务。此线程池不会对线程池大小做限制,
线程池大小完全依赖于操作系统(或者说JVM)能够创建的最大线程大小。

newScheduledThreadPool:
创建一个大小无限的线程池。此线程池支持定时以及周期性执行任务的需求。

newSingleThreadExecutor:
创建一个单线程的线程池。此线程池支持定时以及周期性执行任务的需求。

那我个人感觉就是new ThreadPoolExecutor(CORE_POOL_SIZE, MAX_POOL_SIZE, KEEPALIVE_TIME, TIME_UNIT, workQueue, rejectedExecutionHandler);
提供了更定制化的线程池制造方法。因为newFixedThreadPool方法其实也是return new ThreadPoolExecutor

例子一:

//实现Runnable接口来定义一个简单的

class TestThread implements Runnable

{

    public void run()

    {

        for (int i = 0; i < 100 ; i++ )

        {

            System.out.println(Thread.currentThread().getName()

                + "的i值为:" + i);

        }

    }

}



public class ThreadPoolTest

{

    public static void main(String[] args) 

    {

        //创建一个具有固定线程数(6)的线程池

        ExecutorService pool = Executors.newFixedThreadPool(6);

        //向线程池中提交2个线程

        pool.submit(new TestThread());

        pool.submit(new TestThread());

        //关闭线程池

        pool.shutdown();

    }

}

例子二:

public class ThreadPoolTask implements Runnable { 

  // 保存任务所需要的数据 

  private Object threadPoolTaskData; 

  private static int consumeTaskSleepTime = 2000; 



  ThreadPoolTask(Object tasks) { 

    this.threadPoolTaskData = tasks; 

  } 



  public void run() { 

    // 处理一个任务,这里的处理方式太简单了,仅仅是一个打印语句 

    System.out.println("start .." + threadPoolTaskData); 

    try { 

      //便于观察,等待一段时间 

      Thread.sleep(consumeTaskSleepTime); 

    } catch (Exception e) { 

      e.printStackTrace(); 

    } 

    threadPoolTaskData = null; 

  } 



  public Object getTask() { 

    return this.threadPoolTaskData; 

  } 

} 



public class ThreadPool { 

  private static int produceTaskSleepTime = 2; 

  private static int consumeTaskSleepTime = 2000; 

  private static int produceTaskMaxNumber = 10; 



  public static void main(String[] args) { 

    // 构造一个线程池 

    ThreadPoolExecutor threadPool = new ThreadPoolExecutor(2, 4, 3, 

        TimeUnit.SECONDS, new ArrayBlockingQueue<Runnable>(3), 

        new ThreadPoolExecutor.DiscardOldestPolicy()); 



    for (int i = 1; i <= produceTaskMaxNumber; i++) { 

      try { 

        // 产生一个任务,并将其加入到线程池 

        String task = "task@ " + i; 

        System.out.println("put " + task); 

        threadPool.execute(new ThreadPoolTask(task)); 



        // 便于观察,等待一段时间 

        Thread.sleep(produceTaskSleepTime); 

      } catch (Exception e) { 

        e.printStackTrace(); 

      } 

    } 

  } 

} 

 

多线程的一般规则:
1. 如果2个或以上的线程都修改一个对象,那么把执行修改的方法定义为同步的,如果对象更新影响到只读方法,那么只读方法也要定义成同步的。
2. 如果一个线程必须等待一个对象的状态发生变化,那么它应该在对象内部等待,而不是在外部。它可以调用一个被同步的方法,并让这个方法调用wait()
3. 每当一个方法返回某个对象的锁时,它应该调用notify()/notifyAll()来让等待中的其他线程有机会调用。
4. 有wait()在的地方必须相应的有notify/notifyAll方法,且他们都作用于同一对象
5. 针对wait/notify/notifyAll使用旋锁(spin lock)
6. 优先使用notifyAll,而不是notify
7. 按照固定的顺序获取对象锁,以免死锁
8. 不要对上锁的对象改变他的引用
9. 不要滥用同步机制,避免无谓的同步控制

 

你可能感兴趣的:(java多线程)