• JAVA多线程机制


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

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

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

    线程的生命周期(这里疯狂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. 不要滥用同步机制,避免无谓的同步控制

  • 相关阅读:
    Hbase安装与简单使用
    Integer Int
    Maven是什么
    对Servlet执行流程的初步认识
    android studio 开发环境的搭建
    centos下更换默认的python版本
    写出我心(七)
    写出我心(六)
    写出我心(五)
    写出我心(四)
  • 原文地址:https://www.cnblogs.com/lijunamneg/p/2976050.html
Copyright © 2020-2023  润新知