码迷,mamicode.com
首页 > 编程语言 > 详细

Java线程使用大全

时间:2017-08-26 23:33:40      阅读:337      评论:0      收藏:0      [点我收藏+]

标签:ase   border   frp   random   应用程序   pwa   interrupt   lrm   vtk   

1.线程实现

1.Thread类

构造方法:

技术分享

案例代码:

public class Ex10_1_CaseThread extends Thread {// 创建一个类继承(extend)Thread类
    String studentName;

    public Ex10_1_CaseThread(String studentName) {// 定义类的构造函数,传递参数
        System.out.println(studentName + "申请访问服务器");
        this.studentName = studentName;
    }

    public void run() {// 用需在此线程中执行的代码覆盖Thread类的run()方法
        for (int i = 0; i < 5; i++) {
            System.out.println("当前的服务对象是" + studentName + "同学");
            try {
                Thread.sleep((int) (Math.random() * 2000));
            } catch (InterruptedException ex) {
                System.err.println(ex.toString());
            }
        }// for
    }// run

    public static void main(String[] args) {
        Ex10_1_CaseThread t1 = new Ex10_1_CaseThread("张三"); // 用new实例化对象
        Ex10_1_CaseThread t2 = new Ex10_1_CaseThread("李四");
        t1.start(); // 调用该对象的start()方法启动线程。
        t2.start();
    } // main
}// class

 

 结果:

张三申请访问服务器
李四申请访问服务器
当前的服务对象是张三同学
当前的服务对象是李四同学
当前的服务对象是张三同学
当前的服务对象是李四同学
当前的服务对象是张三同学
当前的服务对象是张三同学
当前的服务对象是李四同学
当前的服务对象是李四同学
当前的服务对象是张三同学
当前的服务对象是李四同学

2.实现Runnable接口

(1)创建一个类实现Runnable接口

(2)用需在此线程中执行的代码覆盖Thread类的run方法

(3)类中定义一个Thread类对象

(4)用第二个构造方法实例化(3)对象

(5)调用对象的start()启动线程

package Thread;

public class Ex10_1_CaseRunnable implements Runnable {// 创建一个类实现(implements)Runnable接口
    String studentName;

    public Ex10_1_CaseRunnable(String studentName) {// 定义类的构造函数,传递参数
        System.out.println(studentName + "申请访问服务器");
        this.studentName = studentName;
    }

    public void run() {// 用需在此线程中执行的代码覆盖Thread类的run()方法
        for (int i = 0; i < 5; i++) {
            System.out.println("当前的服务对象是" + studentName + "同学");
            try {
                Thread.sleep((int) (Math.random() * 2000));
            } catch (InterruptedException ex) {
                System.err.println(ex.toString());
            }
        }// for
    }// run

    public static void main(String[] args) {
        Thread t1 = new Thread(new Ex10_1_CaseRunnable("张三")); // 用new实例化对象
        Thread t2 = new Thread(new Ex10_1_CaseRunnable("李四"));
        t1.start(); // 调用该对象的start()方法启动线程。
        t2.start();
    } // main
}

 

 

 结果:

张三申请访问服务器
李四申请访问服务器
当前的服务对象是张三同学
当前的服务对象是李四同学
当前的服务对象是张三同学
当前的服务对象是李四同学
当前的服务对象是张三同学
当前的服务对象是张三同学
当前的服务对象是李四同学
当前的服务对象是李四同学
当前的服务对象是张三同学
当前的服务对象是李四同学

2.线程的状态:

线程从创建、运行到结束总是处于下面五个状态之一:新建状态、就绪状态、运行状态、阻塞状态及死亡状态。

技术分享

    1.新建状态(New): 
        当用new操作符创建一个线程时, 例如new Thread(r),线程还没有开始运行,此时线程处在新建状态。 当一个线程处于新生状态时,程序还没有开始运行线程中的代码

     2.就绪状态(Runnable)

        一个新创建的线程并不自动开始运行,要执行线程,必须调用线程的start()方法。当线程对象调用start()方法即启动了线程,start()方法创建线程运行的系统资源,并调度线程运行run()方法。当start()方法返回后,线程就处于就绪状态。

        处于就绪状态的线程并不一定立即运行run()方法,线程还必须同其他线程竞争CPU时间,只有获得CPU时间才可以运行线程。因为在单CPU的计算机系统中,不可能同时运行多个线程,一个时刻仅有一个线程处于运行状态。因此此时可能有多个线程处于就绪状态。对多个处于就绪状态的线程是由Java运行时系统的线程调度程序(thread scheduler)来调度的。

    3.运行状态(Running)

        当线程获得CPU时间后,它才进入运行状态,真正开始执行run()方法.

    4. 阻塞状态(Blocked)

        线程运行过程中,可能由于各种原因进入阻塞状态:
        1>线程通过调用sleep方法进入睡眠状态;
        2>线程调用一个在I/O上被阻塞的操作,即该操作在输入输出操作完成之前不会返回到它的调用者;
        3>线程试图得到一个锁,而该锁正被其他线程持有;
        4>线程在等待某个触发条件;
        ......           

        所谓阻塞状态是正在运行的线程没有运行结束,暂时让出CPU,这时其他处于就绪状态的线程就可以获得CPU时间,进入运行状态。

    5. 死亡状态(Dead)

        有两个原因会导致线程死亡:
        1) run方法正常退出而自然死亡,
        2) 一个未捕获的异常终止了run方法而使线程猝死。
        为了确定线程在当前是否存活着(就是要么是可运行的,要么是被阻塞了),需要使用isAlive方法。如果是可运行或被阻塞,这个方法返回true; 如果线程仍旧是new状态且不是可运行的, 或者线程死亡了,则返回false.

3.线程的基本控制

与线程休眠类似,线程的优先级仍然无法保障线程的执行次序。只不过,优先级高的线程获取CPU资源的概率较大,优先级低的并非没机会执行。
 
线程的优先级用1-10之间的整数表示,数值越大优先级越高,默认的优先级为5。
 
在一个线程中开启另外一个新线程,则新开线程称为该线程的子线程,子线程初始优先级与父线程相同。
 
对于优先级相同的线程遵循队列的"先到先服务原则"。
/**
* Java线程:线程的调度-优先级
*
* @author leizhimin 2009-11-4 9:02:40
*/
public class Test {
        public static void main(String[] args) {
                Thread t1 = new MyThread1();
                Thread t2 = new Thread(new MyRunnable());
                t1.setPriority(10);
                t2.setPriority(1);

                t2.start();
                t1.start();
        }
}

class MyThread1 extends Thread {
        public void run() {
                for (int i = 0; i < 10; i++) {
                        System.out.println("线程1第" + i + "次执行!");
                        try {
                                Thread.sleep(100);
                        } catch (InterruptedException e) {
                                e.printStackTrace();
                        }
                }
        }
}

class MyRunnable implements Runnable {
        public void run() {
                for (int i = 0; i < 10; i++) {
                        System.out.println("线程2第" + i + "次执行!");
                        try {
                                Thread.sleep(100);
                        } catch (InterruptedException e) {
                                e.printStackTrace();
                        }
                }
        }
}

 线程1第0次执行!
线程2第0次执行!
线程2第1次执行!
线程1第1次执行!
线程2第2次执行!
线程1第2次执行!
线程1第3次执行!
线程2第3次执行!
线程2第4次执行!
线程1第4次执行!
线程1第5次执行!
线程2第5次执行!
线程1第6次执行!
线程2第6次执行!
线程1第7次执行!
线程2第7次执行!
线程1第8次执行!
线程2第8次执行!
线程1第9次执行!
线程2第9次执行!

Process finished with exit code 0

4.线程的主要方法:

  参考JDK的API类Thread,可以获得线程名,优先级等

5.线程的同步

  由于同一进程的多个线程共享同一片存储空间,在带来方便的同时,也带来了访问冲突的严重问题。当;两个以上线程访问同一个变量(全局或静态变量)的时候,并且一个线程需要修改这个变量的时候,如果不加以控制,将会带来数据不一致的问题,Java采用如下方法解决这类问题:

5.1  同步方法与同步块

1.同步方法:synchronized方法

  即有synchronized关键字修饰的方法。 由于java的每个对象都有一个内置锁,当用此关键字修饰方法时, 内置锁会保护整个方法。在调用该方法前,需要获得内置锁,否则就处于阻塞状态。

  注: synchronized关键字也可以修饰静态方法,此时如果调用该静态方法,将会锁住整个类。

例如:

    public synchronized void accountAccess(int num, boolean k) {
        for (int i = 0; i < 3; i++) {
            accessType = Thread.currentThread().getName();
            if (k)
                fund += num;
            else {
                fund -= num;
            }
            try {
                System.out.println("当前线程是" + accessType + ",账户剩余资金为" + fund
                        + "。");
                Thread.sleep(2000);
            } catch (InterruptedException ex) {
                System.err.println(ex.toString());
            }
        }
    }

 

 2.同步块:synchronized 

  即有synchronized关键字修饰的语句块。 被该关键字修饰的语句块会自动被加上内置锁,从而实现同步

  语法:

synchronized(object){ 
}

 

 例如:

            public void save1(int money) {
                synchronized (this) {
                    account += money;
                }
            }

 

 

5.2  wait与notify

wait():使一个线程处于等待状态,并且释放所持有的对象的lock。

sleep():使一个正在运行的线程处于睡眠状态,是一个静态方法,调用此方法要捕捉InterruptedException异常。
notify():唤醒一个处于等待状态的线程,注意的是在调用此方法的时候,并不能确切的唤醒某一个等待状态的线程,而是由JVM确定唤醒哪个线程,而且不是按优先级。
notifyAll():唤醒所有处入等待状态的线程,注意并不是给所有唤醒线程一个对象的锁,而是让它们竞争。

例如:

public class Ex10_4_seatorderedCase {
    private int seatResource;                     // 共享缓冲区
    private boolean empty = true;         // seatResource是否为空的信号量
    public void setEmpty(){
        empty=true;
    }
    public synchronized void push(int pubResource) {
        while (!empty) {                   // 当缓冲区满的时候,等待
            try {                             // 阻塞自己
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        seatResource = pubResource;        // 将生成的座位号放到缓冲区
        empty = false;                      // 设置缓冲区满状态
        notify();                           // 唤醒其他等待线程
    }
    public synchronized int pop() {       // 从缓冲区订座位
        while (empty) {
            try {
                wait();                    // 当缓冲区空的时候,等待
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        int popResource = seatResource;
        seatResource = 0;
        empty = true;                    // 设置缓冲区空状态
        notify();
        return popResource;            // 返回所订座位号
    }
    public static void main(String[] args) {
        Ex10_4_seatorderedCase so = new Ex10_4_seatorderedCase();
        SeatProcedure sp = new SeatProcedure(so);
        sp.start();
        SeatConsumer sc = new SeatConsumer(so);
        sc.start();
        SeatRelease sr=new SeatRelease(so);
        sr.start();
    }
}
class SeatProcedure extends Thread { //生成空座位线程            
    private Ex10_4_seatorderedCase so;          
    public SeatProcedure(Ex10_4_seatorderedCase so) {   
        this.so = so;
    }
    public void run() {
        for (int i = 1; i <= 30; i++) {     //连续向缓冲区生成空座位号
            int pubResource = i;
            so.push(pubResource);
            System.out.println("第" + pubResource + "号座位为空");
            try {
                sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}//class end
class SeatConsumer extends Thread{    //预订座位线程
    private Ex10_4_seatorderedCase so;          
    public SeatConsumer(Ex10_4_seatorderedCase so) {         
        this.so= so;  
    }
        public void run() {
            for (int i = 1; i <= 50; i++) {//50个学生连续从缓冲区取出座位号 
                synchronized (so) {
                    int sh = so.pop();
                    if (sh != 0) {
                        System.out.println("学生" + i + " "+"占了第" + sh+"号座位");
                    } else {
                        System.out.println("没有空座,请等待!");
                    }
                }
                try {
                    sleep(200);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }//class end
class SeatRelease  extends Thread {  //释放座位线程                     
    private Ex10_4_seatorderedCase so;                         
    public SeatRelease(Ex10_4_seatorderedCase so) {        
        this.so = so;
    }
    public void run() {
        try {
            sleep(20000);//20秒后
             this.so.setEmpty();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        for (int i = 1; i <= 30; i++) {      //从第一个开始,连续释放已预订的座位
            int pubResource = i;
            so.push(pubResource);
            System.out.println("第" + pubResource + "号座位取消预订");
            try {
                sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

第1号座位为空
学生1 占了第1号座位
第2号座位为空
学生2 占了第2号座位
第3号座位为空
学生3 占了第3号座位
第4号座位为空
学生4 占了第4号座位
第5号座位为空
学生5 占了第5号座位
第6号座位为空
学生6 占了第6号座位
第7号座位为空
学生7 占了第7号座位
第8号座位为空
学生8 占了第8号座位
第9号座位为空
学生9 占了第9号座位
第10号座位为空
学生10 占了第10号座位
第11号座位为空
学生11 占了第11号座位
第12号座位为空
学生12 占了第12号座位
第13号座位为空
学生13 占了第13号座位
第14号座位为空
学生14 占了第14号座位
第15号座位为空
学生15 占了第15号座位
第16号座位为空
学生16 占了第16号座位
第17号座位为空
学生17 占了第17号座位
第18号座位为空
学生18 占了第18号座位
第19号座位为空
学生19 占了第19号座位
第20号座位为空
学生20 占了第20号座位
第21号座位为空
学生21 占了第21号座位
第22号座位为空
学生22 占了第22号座位
第23号座位为空
学生23 占了第23号座位
第24号座位为空
学生24 占了第24号座位
第25号座位为空
学生25 占了第25号座位
第26号座位为空
学生26 占了第26号座位
第27号座位为空
学生27 占了第27号座位
第28号座位为空
学生28 占了第28号座位
第29号座位为空
学生29 占了第29号座位
第30号座位为空
学生30 占了第30号座位

5.3    使用特殊域变量(volatile)实现线程同步

5.4使用局部变量实现线程同步

 

5.5 使用阻塞队列实现线程同步

 

前面5种同步方式都是在底层实现的线程同步,但是我们在实际开发当中,应当尽量远离底层结构。 使用javaSE5.0版本中新增的java.util.concurrent包将有助于简化开发。 本小节主要是使用LinkedBlockingQueue<E>来实现线程的同步 LinkedBlockingQueue<E>是一个基于已连接节点的,范围任意的blocking queue。 队列是先进先出的顺序(FIFO),关于队列以后会详细讲解~LinkedBlockingQueue 类常用方法 LinkedBlockingQueue() : 创建一个容量为Integer.MAX_VALUE的LinkedBlockingQueue put(E e) : 在队尾添加一个元素,如果队列满则阻塞 size() : 返回队列中的元素个数 take() : 移除并返回队头元素,如果队列空则阻塞代码实例: 实现商家生产商品和买卖商品的同步

 

 

 

注:BlockingQueue<E>定义了阻塞队列的常用方法,尤其是四种操作元素的方法,我们要多加注意,当队列满或空时:

  add()方法会抛出异常

  offer()方法返回false

  take()方法会阻塞

  put()方法会阻塞

 

 

7.使用原子变量实现线程同步

 

需要使用线程同步的根本原因在于对普通变量的操作不是原子的。

那么什么是原子操作呢?原子操作就是指将读取变量值、修改变量值、保存变量值看成一个整体来操作即-这几种行为要么同时完成,要么都不完成。在java的util.concurrent.atomic包中提供了创建了原子类型变量的工具类,使用该类可以简化线程同步。其中AtomicInteger 表可以用原子方式更新int的值,可用在应用程序中(如以原子方式增加的计数器),但不能用于替换Integer;可扩展Number,允许那些处理机遇数字类的工具和实用工具进行统一访问。

AtomicInteger类常用方法:

AtomicInteger(int initialValue) : 创建具有给定初始值的新的

AtomicIntegeraddAddGet(int dalta) : 以原子方式将给定值与当前值相加

get() : 获取当前值

代码实例:

只改Bank类,其余代码与上面第一个例子同

技术分享
class Bank {
    private AtomicInteger account = new AtomicInteger(100);
    public AtomicInteger getAccount() {
        return account; 
    } 
    public void save(int money) {
        account.addAndGet(money);
    }
}
技术分享

补充--原子操作主要有:  

对于引用变量和大多数原始变量(long和double除外)的读写操作;  

对于所有使用volatile修饰的变量(包括long和double)的读写操作。

5.6  使用重入锁实现线程同步

    在JavaSE5.0中新增了一个java.util.concurrent包来支持同步。 
    ReentrantLock类是可重入、互斥、实现了Lock接口的锁,它与使用synchronized方法和快具有相同的基本行为和语义,并且扩展了其能力。
 ReenreantLock类的常用方法有:
ReentrantLock() : 创建一个ReentrantLock实例 
lock() : 获得锁 
unlock() : 释放锁 
注:ReentrantLock()还有一个可以创建公平锁的构造方法,但由于能大幅度降低程序运行效率,不推荐使用 
        
    例如: 
        在上面例子的基础上,改写后的代码为: 
技术分享
       //只给出要修改的代码,其余代码与上同
        class Bank {
            
            private int account = 100;
            //需要声明这个锁
            private Lock lock = new ReentrantLock();
            public int getAccount() {
                return account;
            }
            //这里不再需要synchronized 
            public void save(int money) {
                lock.lock();
                try{
                    account += money;
                }finally{
                    lock.unlock();
                }
                
            }
        }
技术分享
    注:关于Lock对象和synchronized关键字的选择: 
        a.最好两个都不用,使用一种java.util.concurrent包提供的机制,能够帮助用户处理所有与锁相关的代码。 
        b.如果synchronized关键字能满足用户的需求,就用synchronized,因为它能简化代码 
        c.如果需要更高级的功能,就用ReentrantLock类,此时要注意及时释放锁,否则会出现死锁,通常在finally代码释放锁

5.7  使用特殊域变量(volatile)实现线程同步

下面使用volatile示例场景是错误的:
技术分享
package com.dxz.volatiledemo;

//只给出要修改的代码,其余代码与上同
class Bank {
    // 需要同步的变量加上volatile
    private volatile int account = 100;

    public int getAccount() {
        return account;
    }

    // 这里不再需要synchronized
    public void save(int money) {
        account += money;
    }
}

package com.dxz.volatiledemo;

import java.util.concurrent.CountDownLatch;

public class MyThread implements Runnable {
    Bank bank;
    CountDownLatch cdl;
    MyThread(Bank bank, CountDownLatch cdl) {
        this.bank = bank;
        this.cdl = cdl;
    }
    @Override
    public void run() {
        for(int i = 1; i < 101; i++) {
            bank.save(i);
        }
        cdl.countDown();
    }
}

package com.dxz.volatiledemo;

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

public class Client {

    public static void main(String[] args) throws InterruptedException {
        int num = 70;
        ExecutorService es = Executors.newFixedThreadPool(num);
        Bank bank = new Bank();
        CountDownLatch cdl = new CountDownLatch(num);
        for(int i = 0; i<num;i++) {
            MyThread mt = new MyThread(bank, cdl);
            es.submit(mt);
            //TimeUnit.SECONDS.sleep(1);
        }
        cdl.await();
        es.shutdown();
        System.out.println(bank.getAccount());
        
    }
}
技术分享

每个线程间隔一秒时,计算结果是:353600,如果去掉线程间隔让线程竞争起来,结果很多时候不一致。验证了volatile变量的以下特性:

  • 可见性。对一个volatile变量的读,总是能看到(任意线程)对这个volatile变量最后的写入。
  • 原子性:对任意单个volatile变量的读/写具有原子性,但类似于volatile++这种复合操作不具有原子性。

 

Java线程使用大全

标签:ase   border   frp   random   应用程序   pwa   interrupt   lrm   vtk   

原文地址:http://www.cnblogs.com/qlqwjy/p/7436638.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!