zoukankan      html  css  js  c++  java
  • 《疯狂Java讲义》(三十四)---- 线程

    • 线程的创建和启动
    1. 继承Thread类创建线程类
    package com.ivy.thread;
    
    public class FirstThread extends Thread{
    
        private int i;
        public void run() {
            for( ; i < 100 ; i++) {
                System.out.println(getName() + " " + i);
            }
        }
        public static void main(String[] args) {
            // TODO Auto-generated method stub
    
            for (int i = 0; i < 100 ; i++) {
                System.out.println(Thread.currentThread().getName());
                if (i==20) {
                    new FirstThread().start();
                    new FirstThread().start();
                }
            }
        }
    
    }

      2. 实现Runnable接口创建线程类

    package com.ivy.thread;
    
    public class SecondThread implements Runnable{
    
        private int i;
        
        public static void main(String[] args) {
            // TODO Auto-generated method stub
            for (int i = 0; i < 100 ; i++) {
                System.out.println(Thread.currentThread().getName());
                if (i==20) {
                    SecondThread st = new SecondThread();
                    new Thread(st, "newThread-1").start();
                    new Thread(st, "newThread-2").start();
                }
            }
        }
    
        @Override
        public void run() {
            for( ; i < 100 ; i++) {
                System.out.println(Thread.currentThread().getName() + " " + i);
            }
            
        }
    
    }

    在FirstThread和SecondThread中创建线程对象的方式有所区别:前者直接创建Thread字类即可代表线程对象;后者创建的Runnable对象只能作为线程对象的target。在FirstThread中Thread-0和Thread-1两个线程输出的i变量不连续--注意:i变量是FirstThread的实例属性,而不是局部变量,但因为程序每次创建线程对象时都需要创建一个FirstThread对象,所以Thread-0和Thread-1 不能共享该实例变量。但在SecondThread的输出结果中,两个子线程的i变量是连续的,也就是采用Runnable接口的方式创建的多个线程可以共享线程类的实例属性。

      

      3. 使用Callable和Future创建线程

        (1)创建Callable接口的实现类,并实现call方法,call方法有返回值

        (2)创建Callable实现类的实例,使用FutureTask类来包装Callable对象,该FutureTask对象封装了该Callable对象的call方法的返回值。

        (3)使用FutureTask对象作为Thread对象的target创建并启动新线程。

        (4)调用FutureTask对象的get方法来获得子线程执行结束后的返回值。

    package com.ivy.thread;
    
    import java.util.concurrent.Callable;
    import java.util.concurrent.FutureTask;
    
    public class ThirdThread implements Callable<Integer>{
    
        public static void main(String[] args) {
            // TODO Auto-generated method stub
            ThirdThread rt = new ThirdThread();
            FutureTask<Integer> task = new FutureTask<Integer>(rt);
            for (int i =0; i< 100; i++) {
                System.out.println(Thread.currentThread().getName() + " " + i);
                if (i == 20) {
                    new Thread(task, "hasReturnThread").start();
                }
            }
            try {
                System.out.println("sub Thread return : " + task.get());
            } catch (Exception ex) {
                ex.printStackTrace();
            }
        }
    
        @Override
        public Integer call() throws Exception {
            int i = 0;
            for (; i < 100; i++) {
                System.out.println(Thread.currentThread().getName() + " " + i);
            }
            return i;
        }
    
    }

      

      4. 创建线程的三种方式对比

      通过继承Thread类或实现Runnable/Callable接口都可以实现多线程,不过实现Runnable接口与实现Callable接口的方式基本相同,只是Callable接口里定义的方法有返回值,可以声明抛出异常而已。一般采用实现Runnable/Callable接口来创建多线程。

      5. 线程状态

      A 新建 

      B 就绪

      C 运行

      D 阻塞

      E 死亡

      当主线程结束时,其他线程不受任何影响,并不会随之结束,一旦子线程启动起来之后,它就拥有和主线程相同的地位,它不受主线程的影响。

    •  控制线程
    1. join 线程

    join()方法让一个线程等待另一个线程完成,当在某个程序执行流中调用了其他线程的join()方法时,调用线程将被阻塞,直到被join线程执行完为止。join()方法通常由使用线程的程序调用,以将大问题划分为许多小问题,每个小问题分配一个线程。当所有的小问题都得到处理后,再调用主线程来进一步操作。

    package com.ivy.thread;
    
    public class JoinThread extends Thread{
    
        public JoinThread(String name) {
            super(name);
        }
        
        public void run() {
            for (int i = 0 ; i < 100 ; i++) {
                System.out.println(getName() + " " + i);
            }
        }
        public static void main(String[] args) throws InterruptedException {
            // TODO Auto-generated method stub
    
            new JoinThread("newThread").start();
            for (int i = 0 ; i < 100 ; i++) {
                if (i== 20) {
                    JoinThread jt = new JoinThread("joinThread");
                    jt.start();
                    jt.join();
                }
                System.out.println(Thread.currentThread().getName() + " " + i);
            }
        }
    
    }

    当主线程执行到i==20时,启动joinThread,所以主线程将一直处于阻塞状态,直到joinThread的线程执行完成。

    join()方法有三种重载方式:

    1. join() :等待被join的线程执行完成。
    2. join(long millis) : 等待被join的线程时间最长为millis毫秒。如果在millis毫秒内被join的线程还没有执行结束,则不再等待。

      2.  后台线程(Deamon Thread)

    又叫守护线程或精灵线程。它在后台运行,任务就是为其他线程提供服务。如果所有的前台线程都死亡,后台线程会自动死亡。调用Thread对象的setDeamon(true)方法可将制定线程设置成后台线程。

    package com.ivy.thread;
    
    public class DaemonThread extends Thread{
    
        public void run() {
            for (int i = 0; i < 1000; i++) {
                System.out.println(getName() + " " + i);
            }
        }
        public static void main(String[] args) {
            // TODO Auto-generated method stub
    
            DaemonThread t = new DaemonThread();
            t.setDaemon(true);
            t.start();
            for (int i = 0; i < 10; i++) {
                System.out.println(Thread.currentThread().getName() + " " + i);
            }
        }
    
    }

    本来t线程应该执行到i=999才会结束,但运行程序发现该后台线程无法运行到999,因为当主线程也就是程序中唯一的前台线程运行结束后,JVM会主动退出,因而后台线程也就被结束了。

    Thread类还提供了一个isDaemon()方法,用于判断制定线程是否为后台线程。前台线程创建的子线程默认是前台线程,后台线程创建的子线程默认是后台线程。

      3. 线程睡眠:sleep

     如果需要让当前正在执行的线程暂停一段时间,并进入阻塞状态,则可以通过调用sleep方法来实现。在其睡眠时间段内,该线程不会获得执行的机会,即使系统中没有其他可执行的线程,处于sleep状态的线程也不会执行。

      4. 线程让步:yield

    可以让当前正在执行的线程暂停,但它不会阻塞该线程,只是将线程转入就绪状态。当某个线程调用了yield暂停之后,只有优先级与当前线程相同,或者优先级比当前线程更高的处于就绪状态的线程才会获得执行的机会。

    package com.ivy.thread;
    
    public class YieldDemo extends Thread{
    
        public YieldDemo(String name) {
            super(name);
        }
        
        public void run() {
            for (int i=0; i < 50; i++) {
                System.out.println(getName() + " " + i);
                if (i== 20) {
                    Thread.yield();
                }
            }
        }
        public static void main(String[] args) {
            // TODO Auto-generated method stub
    
            YieldDemo yd1 = new YieldDemo("high");
            yd1.setPriority(Thread.MAX_PRIORITY);
            yd1.start();
            YieldDemo yd2 = new YieldDemo("low");
            yd2.setPriority(Thread.MIN_PRIORITY);
            yd2.start();
        }
    
    }

    sleep和yield的区别如下:

    1.   sleep暂停当前线程后,会给其他线程执行机会,不会理会其他线程的优先级,但是yield只会给优先级相同或优先级更高的线程执行机会。
    2.   sleep会将线程转入阻塞状态,直到经过阻塞时间后才会转入就绪状态;yield会强制线程进入就绪状态。
    3.   sleep方法声明抛出了interruptedException异常,所以调用该方法时要么捕捉该异常,要么显式声明抛出该异常,但是yield没有声明抛出任何异常。
    4.   sleep比yield有更好的移植性,通常不建议使用yield来控制并发线程的执行。
    • 线程同步
    1. 同步代码块

    为了解决线程同步问题,Java多线程支持引入了同步监视器,使用同步监视器的通用方法就是同步代码块。语法格式:

    synchronized(obj) {
        //同步代码块
    }

      括号里的obj就是同步监视器,县城开始执行同步代码块前,必须先获得对同步监视器的锁定。虽然Java允许使用任何对象作为同步监视器,但同步监视器的目的就是阻止两个线程对同一个共享资源进行并发访问,因此推荐使用可能被并发访问的共享资源作为同步监视器。

      这样做法符合“加锁---修改---释放锁”的逻辑。通过这种方式就可以保证并发线程在任一时刻只有一个线程可以进入修改共享资源的代码区(也被称为临界区)。

      2. 同步方法

    package com.ivy.thread;
    
    public class Account {
    
        private String accountNo;
        private double balance;
        public Account() {
            
        }
        
        public Account(String accountNo, double balance) {
            this.accountNo = accountNo;
            this.balance = balance;
        }
        
        public double getBalance() {
            return this.balance;
        }
        
        public synchronized void draw(double drawAmount) {
            if (balance >= drawAmount) {
                System.out.println("successfully draw :" + drawAmount);
                try {
                    Thread.sleep(1);
                } catch (InterruptedException ex) {
                    ex.printStackTrace();
                }
                balance -= drawAmount;
                System.out.println("	 balance = " + balance);
            } else {
                System.out.println("failed to draw, because low balance");
            }
        }
    }

    使用synchronized关键字修饰类的方法,把该方法变成同步方法。同步方法的同步监视器是this,因此对于同一个Account帐户而言,任意时刻只能由一个线程获得对Account对象的锁定。

    可变类的线程安全是以降低程序的运行效率作为代价的,为了减少线程安全所带来的负面影响,程序可以采用如下策略:

    1. 不要对线程安全类的所有方法进行同步,只对那些会改变竞争资源的方法进行同步。
    2. 如果可变类有两种运行环境:单线程环境和多线程环境,应该为该可变类提供两个版本,即线程安全版本和线程不安全版本。
    • 死锁

    当两个线程互相等待对方释放同步监视器时就会发生死锁。一旦出现死锁,整个程序既不会发生任何异常,也不会给出任何提示,只是所有线程处于阻塞状态,无法继续。死锁很容易发生,尤其在系统中出现多个同步监视器的情况下。

    • 线程通信
    1. 传统的线程通信
    package com.ivy.thread;
    
    public class SecondAccount {
    
        private String accountNo;
        private double balance;
        private boolean flag = false;
        public SecondAccount() {}
        public SecondAccount(String accountNo, double balance) {
            this.accountNo = accountNo;
            this.balance = balance;
        }
        public String getAccountNo() {
            return accountNo;
        }
        public void setAccountNo(String accountNo) {
            this.accountNo = accountNo;
        }
        public double getBalance() {
            return balance;
        }
        
        public synchronized void draw(double drawAmount) {
            try {
                if (!flag) {
                    wait();
                }else {
                    System.out.println(Thread.currentThread().getName() + "draw:" + drawAmount);
                    balance -= drawAmount;
                    System.out.println("balance : " + balance);
                    flag = false;
                    notifyAll();
                }
            } catch (InterruptedException ex) {
                ex.printStackTrace();
            }
        }
        
        public synchronized void deposit(double depositeAmount) {
            try {
                if (flag) {
                    wait();
                } else {
                    System.out.println(Thread.currentThread().getName() + "deposit:" + depositeAmount);
                    balance += depositeAmount;
                    System.out.println("balance : " + balance);
                    flag = true;
                    notifyAll();
                }
            } catch (InterruptedException ex) {
                ex.printStackTrace();
            }
        }
    }

      2. 使用Condition控制线程通信

    package com.ivy.thread;
    
    import java.util.concurrent.locks.Condition;
    import java.util.concurrent.locks.Lock;
    import java.util.concurrent.locks.ReentrantLock;
    
    public class ConditionAccount {
    
        private final Lock lock = new ReentrantLock();
        private final Condition cond = lock.newCondition();
        private String accountNo;
        private double balance;
        private boolean flag = false;
        public ConditionAccount() {}
        public ConditionAccount(String accountNo, double balance) {
            this.accountNo = accountNo;
            this.balance = balance;
        }
        public String getAccountNo() {
            return accountNo;
        }
        public void setAccountNo(String accountNo) {
            this.accountNo = accountNo;
        }
        public double getBalance() {
            return balance;
        }
        
        public void draw(double drawAmount) {
            lock.lock();
            try {
                if (!flag) {
                    cond.await();
                }else {
                    System.out.println(Thread.currentThread().getName() + "draw:" + drawAmount);
                    balance -= drawAmount;
                    System.out.println("balance : " + balance);
                    flag = false;
                    cond.signalAll();
                }
            } catch (InterruptedException ex) {
                ex.printStackTrace();
            } finally {
                lock.unlock();
            }
        }
        
        public void deposit(double depositeAmount) {
            lock.lock();
            try {
                if (flag) {
                    cond.await();
                } else {
                    System.out.println(Thread.currentThread().getName() + "deposit:" + depositeAmount);
                    balance += depositeAmount;
                    System.out.println("balance : " + balance);
                    flag = true;
                    cond.signalAll();
                }
            } catch (InterruptedException ex) {
                ex.printStackTrace();
            } finally {
                lock.unlock();
            }
        }
    }

      3. 使用阻塞队列(BlockingQueue)控制线程通信

    BlockingQueue有一个特征:当生产者线程试图向BlockingQueue中放入元素时,如果该队列已满,则该线程阻塞;当消费者线程试图从BlockingQueue中取出元素时,如果该队列已空,则该线程被阻塞。程序的两个线程通过交替向BlockingQueue中放入元素,取出元素,即可很好地控制线程的通信。

    package com.ivy.thread;
    
    import java.util.concurrent.ArrayBlockingQueue;
    import java.util.concurrent.BlockingQueue;
    
    import sun.awt.image.PixelConverter.Bgrx;
    
    class Producer extends Thread {
        private BlockingQueue<String> bq;
        public Producer(BlockingQueue<String> bq) {
            this.bq = bq;
        }
        
        public void run() {
            String[] strArrStrings = new String[] {
                "Java",
                "Struts",
                "Spring"
            };
            for (int i = 0; i < 9999999; i++) {
                System.out.println(getName() + "producer is ready");
                try {
                    Thread.sleep(200);
                    bq.put(strArrStrings[i % 3]);
                } catch (Exception ex) {
                    ex.printStackTrace();
                }
                System.out.println(getName() + "produce end : " + bq);
            }
        }
    }
    
    class Consumer extends Thread {
        private BlockingQueue<String> bq;
        public Consumer(BlockingQueue<String> bq) {
            this.bq = bq;
        }
        
        public void run() {
            while(true) {
                System.out.println(getName() + " consumer is ready ");
                try {
                    Thread.sleep(200);
                    bq.take();
                } catch (Exception ex) {
                    ex.printStackTrace();
                }
                System.out.println(getName() + "consume end : " + bq);
            }
        }
    }
    public class BlockingQueueDemo2 {
    
        public static void main(String[] args) {
            BlockingQueue<String> bQueue = new ArrayBlockingQueue<>(1);
            new Producer(bQueue).start();
            new Producer(bQueue).start();
            new Producer(bQueue).start();
            new Consumer(bQueue).start();
        }
    
    }
    • 线程池

    使用线程池来执行线程任务的步骤如下:

    1. 调用Excutors类的静态工厂方法创建一个ExecutorService对象,该对象代表一个线程池。
    2. 创建Runnable实现类或Callable实现类的实例,作为线程执行任务。
    3. 调用ExecutorService对象的submit方法来提交Runnable实例或Callable实例。
    4. 当不想提交任何任务时,调用ExecutorService对象的shutdown方法来关闭线程池。
    package com.ivy.thread;
    
    import java.util.concurrent.ExecutorService;
    import java.util.concurrent.Executors;
    
    class MyThread implements Runnable {
        public void run() {
            for (int i = 0; i < 100 ; i++) {
                System.out.println(Thread.currentThread().getName() + " i : " + i);
            }
        }
    }
    public class ThreadPoolDemo {
    
        public static void main(String[] args) {
            ExecutorService pool = Executors.newFixedThreadPool(6);
            pool.submit(new MyThread());
            pool.submit(new MyThread());
            pool.shutdown();
    
        }
    
    }
    •   线程相关类
    1. ThreadLocal

    它的作用就是为每个使用该变量的线程都提供一个变量值的副本,使每一个线程都可以独立地改变自己的副本,而不会和其他线程的副本冲突。从线程的角度来看,就好像每个线程都完全拥有该变量一样。

    package com.ivy.thread;
    
    class Account {
        private ThreadLocal<String> name = new ThreadLocal<>();
        public Account(String str) {
            this.name.set(str);
            System.out.println("---" + this.name.get());
        }
        
        public String getName() {
            return name.get();
        }
        
        public void setName(String str) {
            this.name.set(str);
        }
    }
    
    class MyTest extends Thread {
        private Account account;
        public MyTest(Account account, String name) {
            super(name);
            this.account = account;
        }
        
        public void run() {
            for(int i = 0; i < 10; i++) {
                if (i== 6) {
                    account.setName(getName());
                }
                System.out.println(account.getName() + " account i : " + i);
            }
        }
    }
    public class ThreadLocalDemo {
    
        public static void main(String[] args) {
            Account at = new Account("Aa");
            new MyTest(at, "Thread B").start();
            new MyTest(at, "Thread C").start();
    
        }
    
    }

    通常我们认为:如果多个线程之间需要共享资源,以达到线程之间的通信功能,就使用同步机制;如果仅仅需要隔离多个线程之间的共享冲突,则可以使用ThreadLocal。

      2. 包装线程不安全的集合

    <T> Collection<T>  synchronizedCollection(Collection<T> c)

    static <T> List<T>  synchronizedList(List<T> list)

    static <K,V> Map<K,V> synchronizedMap(Map<K,V> m)

    static <T> Set<T>  synchronizedSet(Set<T> set)

      3. 线程安全的集合类

    a. 以Concurrent开头的集合类:ConcurrentHashMap/ConcurrentSkipListMap/ConcurrentSkipListSet/ConcurrentLinkedQueue.

    b. 以CopyOnWrite开头的集合类:CopyOnWriteArrayList/CopyOnWriteArraySet.

  • 相关阅读:
    C# 读取 vCard 格式
    C#自动选择出系统中最合适的IP地址
    WPF专业编程指南
    WPF专业编程指南
    随手复习一下委托:delegate
    迟到的 WPF 学习 —— 控件
    迟到的 WPF 学习 —— 路由事件
    迟到的 WPF 学习 —— 依赖项属性
    迟到的 WPF 学习 —— 布局
    JavaScript 左右上下自动晃动,自动移动。
  • 原文地址:https://www.cnblogs.com/IvySue/p/6418261.html
Copyright © 2011-2022 走看看