首页 > 程序开发 > 软件开发 > Java >

Java开发之多线程常用应用分析

2017-11-06

Java 多线程常用应用。Java 多线程:多线程比较典型的应用,如:① 迅雷、FlashGet 多线程下载;② 创建灵活响应的桌面程序。单核CPU来说,线程在宏观上是并行的,在微观上是串行的。

Java 多线程

多线程比较典型的应用,如:① 迅雷、FlashGet 多线程下载;② 创建灵活响应的桌面程序。

单核CPU来说,线程在宏观上是并行的,在微观上是串行的。

概念

进程

运行时(runtime)应用程序。 进程之间的内存不是共享(独占) 进程间通信使用的socket(套接字)

线程

进程内并发执行的代码段 线程之间共享内存 可以创建灵活响应的桌面程序 每个运行着的线程对应一个stack 应用程序至少有一个线程(主线程)

创建多线程的方法

继承Thread类

子类覆盖父类中的run方法,将线程运行的代码存放在run中。 建立子类对象的同时线程也被创建 通过调用start方法开启线程。

用start启动线程而不是run(),原因可以看java源码里,通过native关键字调用本地start0线程启动线程。

public synchronized void start() {
        /**
         * This method is not invoked for the main method thread or "system"
         * group threads created/set up by the VM. Any new functionality added
         * to this method in the future may have to also be added to the VM.
         *
         * A zero status value corresponds to state "NEW".
         */
        if (threadStatus != 0)
            throw new IllegalThreadStateException();

        /* Notify the group that this thread is about to be started
         * so that it can be added to the group's list of threads
         * and the group's unstarted count can be decremented. */
        group.add(this);

        boolean started = false;
        try {
            start0();
            started = true;
        } finally {
            try {
                if (!started) {
                    group.threadStartFailed(this);
                }
            } catch (Throwable ignore) {
                /* do nothing. If start0 threw a Throwable then
                  it will be passed up the call stack */
            }
        }
    }

 private native void start0();

    /**
     * If this thread was constructed using a separate
     * Runnable run object, then that
     * Runnable object's run method is called;
     * otherwise, this method does nothing and returns.
     * 

* Subclasses of Thread should override this method. * * @see #start() * @see #stop() * @see #Thread(ThreadGroup, Runnable, String) */ @Override public void run() { if (target != null) { target.run(); } }

一个实现案例

ThreadDemo1.java

class ThreadDemo1{
    public static void main(String[] args){
        //创建线程对象
        MyThread t1 = new MyThread();
        YourThread t2 = new YourThread();
        //
        t1.start();
        t2.start();
    }
}

//线程1
class MyThread extends Thread{
    public void run(){
        for(;;){
            System.out.println("hello world - 1");
        }   
    }
}

class YourThread extends Thread{
    public void run(){
        for(;;){
            System.out.println("hello world - 2");
        }
    }
}

线程创建后,线程t1和t2各自有一块栈空间,栈中压入的是run()方法。

main是主线程,压入的是main方法。

线程栈空间<喎"http://www.2cto.com/kf/ware/vc/" target="_blank" class="keylink">vcD4NCjxoMyBpZD0="实现runnable接口">实现Runnable接口

Thread 其实也是实现了Runnable接口,线程的启动相当于代理模式。

实现Callable接口

线程启动还可以实现Callable接口。

java.lang.Thread中的API方法学习

yield 方法

让当前线程让出cpu抢占权,具有谦让之意,瞬时的动作。

ThreadDemo2.java

① 当没有添加yield方法时

class ThreadDemo2{
    public static void main(String[] args){
        MyThread t1 = new MyThread("Thread-1");
        MyThread t2 = new MyThread("Thread-2");

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

class MyThread extends Thread{
    private  String name;
    public MyThread(){}
    public MyThread(String name){
        this.name = name;
    }
    @Override
    public  void run(){
        while(true){
            System.out.println(name);
            //线程释放资源,处于Runnable的状态
            //Thread.yield();
            //加上yield后的直观体会是线程间的
        }
    }
}

输出结果看,Thread-1 和 Thread-2 交替很慢

Thread-1
Thread-1
Thread-1
Thread-1
Thread-1
Thread-1
Thread-1
Thread-1
Thread-1
Thread-1
Thread-1
Thread-1
Thread-1
Thread-1
Thread-1
Thread-1
Thread-1
Thread-1
Thread-1
Thread-1
Thread-1
Thread-1
Thread-1
Thread-1
Thread-1
Thread-1
Thread-1
Thread-1
Thread-1
Thread-1
Thread-1
Thread-1
Thread-1
Thread-1
Thread-1
Thread-1
Thread-1
Thread-1
Thread-1
Thread-1
Thread-1
Thread-1
Thread-1
Thread-1
Thread-1
Thread-1
Thread-1
Thread-1
Thread-1
Thread-1
Thread-1
Thread-1
Thread-1
Thread-1
Thread-1
Thread-1
Thread-1
Thread-1
Thread-1
Thread-1
Thread-1
Thread-1
Thread-1
Thread-1
Thread-1
Thread-1
Thread-1
Thread-1
Thread-1
Thread-1
Thread-1
Thread-1
Thread-1
Thread-1
Thread-1
Thread-1
Thread-1
Thread-1
Thread-1
Thread-1
Thread-1
Thread-1
Thread-1
Thread-1
Thread-1
Thread-1
Thread-1
Thread-1
Thread-1
Thread-1
Thread-1
Thread-1
Thread-1
Thread-1
Thread-1
Thread-1
Thread-1
Thread-1
Thread-2
Thread-2
Thread-2
Thread-2
Thread-2
Thread-2
Thread-2
Thread-2
Thread-2
Thread-2
Thread-2
Thread-2
Thread-2
Thread-2
Thread-2
Thread-2
Thread-2
Thread-2
Thread-2
Thread-2
Thread-2
Thread-2
Thread-2
Thread-2
Thread-2
Thread-2
Thread-2
Thread-2

① 当没有添加yield方法时

class ThreadDemo2{
    public static void main(String[] args){
        MyThread t1 = new MyThread("Thread-1");
        MyThread t2 = new MyThread("Thread-2");

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

class MyThread extends Thread{
    private  String name;
    public MyThread(){}
    public MyThread(String name){
        this.name = name;
    }
    @Override
    public  void run(){
        while(true){
            System.out.println(name);
            //线程释放资源,处于Runnable的状态
            Thread.yield();
            //加上yield后的直观体会是线程间的
        }
    }
}

输出结果看,Thread-1 和 Thread-2 交替加快了。yield在线程中有”谦让”之意,英语之中是“屈服”的意思。

Thread-1
Thread-1
Thread-1
Thread-1
Thread-1
Thread-1
Thread-2
Thread-2
Thread-2
Thread-2
Thread-2
Thread-2
Thread-2
Thread-1
Thread-2
Thread-2
Thread-2
Thread-2
Thread-1
Thread-1
Thread-1
Thread-1
Thread-2
Thread-2
Thread-1
Thread-1
Thread-1
Thread-2
Thread-1
Thread-1

sleep 方法

让当前线程休眠指定毫秒数,释放cpu抢占权,和锁旗标的监控权没有关系。

join

当前线程等待指定的线程结束后才能继续运行。

以打麻将为例子。现在一个老板要找四个人打麻将,等待四个人赶过来后,开局。

ThreadDemo3.java

class ThreadDemo3{
    public static void main(String[] args){
        Player p1 = new Player("施瓦辛格",3000);
        Player p2 = new Player("成    龙",1000);
        Player p3 = new Player("李 连 杰",2000);
        Player p4 = new Player("史 泰 龙",2500);

        //
        p1.start();
        p2.start();
        p3.start();
        p4.start();

        try{
            p1.join();
            p2.join();
            p3.join();
            p4.join();
        }
        catch(Exception e){
        }
        //
        System.out.println("开局!");
    }
}
//线程1
class Player extends Thread{
    private String name ;
    private int time ;
    public Player(String name,int time){
        this.name = name ;
        this.time = time ;
    }
    public void run(){
        System.out.println(name + "出发了!");
        try{
            //
            Thread.sleep(time);
        }
        catch(Exception e){
        }
        System.out.println(name + "到场了.===>耗时 : " + time);
    }
}

daemon 方法

守护线程,类似“服务员”。最典型的是:java的垃圾回收。

Thread.setDaemon(true);

ThreadDemo4.java

class ThreadDemo4{
    public static void main(String[] args){
        Room r1 = new Room("001",1000);
        Room r2 = new Room("002",2000);
        Room r3 = new Room("003",3000);

        Waiter w = new Waiter();
        //设置线程为守护线程
        w.setDaemon(true);

        r1.start();
        r2.start();
        r3.start();

        try{
            r1.join();
            r2.join();
            r3.join();
        }catch(InterruptedException ie){
            ie.printStackTrace();
        }
        System.out.println("\n结束营业");
    }
}

class Room extends Thread{
    private String no;
    private int time;

    public Room(){}
    public Room(String no,int time){
        this.no = no;
        this.time = time;
    }
    @Override
    public  void run(){
        System.out.println(no+ " 包房开始消费...");
        try{
            Thread.sleep(time);
        }catch(InterruptedException ie){
            ie.printStackTrace();
        }
        System.out.println(no+ " 包房消费了"+time+"秒");
    }
}
class Waiter extends Thread
{
    @Override
    public void run(){
        while (true){
            System.out.println("服务员服务中...."+new java.util.Date());
        }
    }
}

输出

No2号包房开始消费!
No1号包房开始消费!
Wed Jul 20 14:43:24 CST 2016
Wed Jul 20 14:43:25 CST 2016
Wed Jul 20 14:43:25 CST 2016
Wed Jul 20 14:43:26 CST 2016
Wed Jul 20 14:43:26 CST 2016
Wed Jul 20 14:43:27 CST 2016
No1号包房消费时间 : 3000,结束消费!
Wed Jul 20 14:43:27 CST 2016
Wed Jul 20 14:43:28 CST 2016
Wed Jul 20 14:43:28 CST 2016
Wed Jul 20 14:43:29 CST 2016
Wed Jul 20 14:43:29 CST 2016
Wed Jul 20 14:43:30 CST 2016
Wed Jul 20 14:43:30 CST 2016
Wed Jul 20 14:43:31 CST 2016
No2号包房消费时间 : 7000,结束消费!

从输出中,我们也可以看到,当被守护线程结束,守护线程才结束。

线程间通信

先从一个案例看起,领会线程间通信的必要性。

以2售票个售票员共同售100张票为例。

ThreadDemo5.java

class ThreadDemo5{
    public static void main(String[] args){
        Saler s1 = new Saler("Saler1");
        Saler s2 = new Saler("Saler2");

        s1.start();
        s2.start();
    }
}

class Saler extends Thread{
    private String name;
    private static int tickets = 100;

    public Saler(){}
    public Saler(String name){
        this.name = name;
    }
    @Override
    public  void run(){
        while(tickets>0){
                System.out.println(name+ ": " + tickets--);
        }
    }
}

可能出现的错误情形

Saler1: 100
Saler2: 100
Saler1: 99
Saler1: 97
Saler1: 96
Saler1: 95
Saler2: 98
Saler2: 93
Saler1: 94
Saler2: 92
Saler1: 91

……

有时会买出重复的票,这就造成数据的不一致。

同步代码块

线程间共享资源,并发访问,为了确保数据的一致性,解决的方法是加锁。使得线程访问共享资源由并行转为串行。

线程间通信,需要一个“参照物”,锁旗标,类似“信号灯”。锁标记需要线程有“锁定权”。

修改后

ThreadDemo5.java

class ThreadDemo5{
    public static void main(String[] args){
        Saler s1 = new Saler("S1");
        Saler s2 = new Saler("S2");
        s1.start();
        s2.start();
    }
}
//售票员
class Saler extends Thread{
    static int tickets = 100 ;
    //锁旗标
    static Object lock = new Object();
    private String name ;
    public Saler(String name){
        this.name = name ;
    }
    public void run(){
        while(true){
            int t = getTicket();
            if(t == -1){
                return ;
            }
            else{
                System.out.println(name + " : " + t);
            }
        }
    }
    //取票
    public int getTicket(){
        synchronized(lock){
            int t = tickets ;
            try{
                Thread.sleep(500);
            }
            catch(Exception e){
            }

            tickets = tickets - 1 ;
            return t < 1 ? -1 : t ;
        }
    }
}

输出:

S1 : 100
S2 : 99
S1 : 98
S2 : 97
S1 : 96
S2 : 95
S1 : 94
S2 : 93
S1 : 92
S2 : 91
S1 : 90
S2 : 89
S1 : 88
S2 : 87
S1 : 86
S2 : 85
S1 : 84
S2 : 83
S1 : 82
S2 : 81
S1 : 80
S2 : 79
……

继续改写,将锁从外部传入。

ThreadDemo6.java

class ThreadDemo6{
    public static void main(String[] args){
        Object lock = new Object();
        Saler s1 = new Saler("S1",lock);
        Saler s2 = new Saler("S2",lock);
        s1.start();
        s2.start();
    }
}
//售票员
class Saler extends Thread{
    static int tickets = 100 ;
    //锁旗标
    Object lock ;
    private String name ;
    public Saler(String name,Object lock){
        this.name = name ;
        this.lock = lock ;
    }
    public void run(){
        while(true){
            int t = getTicket();
            if(t == -1){
                return ;
            }
            else{
                System.out.println(name + " : " + t);
            }
        }
    }
    //取票
    public int getTicket(){
        synchronized(lock){
            int t = tickets ;
            try{
                //Thread.sleep(50);
            }
            catch(Exception e){
            }
            tickets = tickets - 1 ;
            return t < 1 ? -1 : t ;
        }
    }
}

上面使用的都是同步代码块的方法。同步代码块执行期间,线程始终持有对象的监控权,其他线程处于阻塞状态。

//同步代码块
        synchronized{
            ...
        }

卖票而言,通常票池和取票的方法可以封装成一个实体。这是一个临界区。

ThreadDemo7.java

class ThreadDemo7{
    public static void main(String[] args){
        TicketPool pool = new TicketPool();
        Saler s1 = new Saler("S-1",pool);
        Saler s2 = new Saler("S-2",pool);
        Saler s3 = new Saler("S-3",pool);
        Saler s4 = new Saler("S-4",pool);

        s1.start();
        s2.start();
        s3.start();
        s4.start();
    }
}
//售票员
class Saler extends Thread{
    private String name ;
    private TicketPool pool;
    public Saler(String name,TicketPool pool){
        this.name = name ;
        this.pool = pool ;
    }
    public void run(){
        while(true){
            int no = pool.getTicket();
            if(no == 0 ){
                return ;
            }
            else{
                System.out.println(name + " : " + no);
                Thread.yield();
            }
        }
    }
}
//票池
class TicketPool{
    private int tickets = 9 ;
    public synchronized int getTicket(){
        //同步代码块,以票池本身作为锁旗标
        /*synchronized(this){*/
            int temp = tickets ;
            tickets = tickets - 1 ;
            return temp > 0 ? temp : 0 ;
        //}
    }
}

同步方法

如上例,同步代码块可以改写成同步方法。不过建议写作同步代码块。

同步方法是以当前所在对象做锁旗标。

synchronized(this) === 同步方法。

同步静态方法

同步静态方法,使用类作为同步标记。

public static synchronized xxxx(...){
    ...
}

wait/notify 机制

通过一个没有正确通信的例子,最终报出异常凸显需要通信机制。

ThreadDemo9.java

class ThreadDemo9{
    public static void main(String[] args){
        //使用java中集合类,List是列表。
        java.util.List list = new java.util.ArrayList();
        Productor p = new Productor("生产者",list);
        Consumer c = new Consumer("消费者",list);
        p.start();
        c.start();
    }
}

//生产者
class Productor extends Thread{
    private String name ;
    private java.util.List list ;
    public Productor(String name ,java.util.List list){
        this.name = name ;
        this.list = list ;
    }
    public void run(){
        int i = 0 ;
        while(true){
            list.add(new Integer(i ++));

            System.out.print("add: " + i + " ");
        }
    }
}
//消费者
class Consumer extends Thread{
    private String name ;
    private java.util.List list ;
    public Consumer(String name ,java.util.List list){
        this.name = name ;
        this.list = list ;
    }
    public void run(){
        while(true){
            if(list.size() > 0){
                int i = list.remove(0); 
                try{
                Thread.sleep(2000);
                }
                catch(Exception e){}
                System.out.print("remove : " + i + " ");
            }
        }
    }
}

这里,编译后,运行时指定好最大堆空间。

java -Xmx10m ThreadDemo9

经过漫长的等待,会报出这样的错:

 Exception in thread "Thread-0" java.lang.OutOfMemoryError: Java heap space
        at java.util.Arrays.copyOf(Arrays.java:3210)
        at java.util.Arrays.copyOf(Arrays.java:3181)
        at java.util.ArrayList.grow(ArrayList.java:261)
        at java.util.ArrayList.ensureExplicitCapacity(ArrayList.java:235)
        at java.util.ArrayList.ensureCapacityInternal(ArrayList.java:227)
        at java.util.ArrayList.add(ArrayList.java:458)
        at Productor.run(ThreadDemo9.java:23)

出现这种现象的原因是生成的速度远远大于消费的速度,最终造成堆溢出。

解决这样的问题,线程间需要一种通信机制。

就是等待/唤醒机制。

wait是在Object类中的,由此可知,任何一个对象都可以作为锁旗标。

相关文章
最新文章
热点推荐