Java 多线程

来源:互联网 发布:第七感大底软件 编辑:程序博客网 时间:2024/05/17 20:25

首先传统的C/C++语言,本身不提供多线程编程,主要是调用OS的多线程功能,而Java语言本身就提供了多线程编程能力。
多线程的概念
进程是程序在OS上的一次运行过程,包括了程序、数据和进程控制快(PCB),概括为它可以申请和拥有系统资源,是一个动态的概念,是一个活动的实体。它不只是程序的代码,还包括当前的活动,通过程序计数器的值和处理寄存器的内容来表示。
进程是具有一定独立功能的程序关于某个数据集合上的一次运行活动,进程是系统进行资源分配和调度的一个独立单位线程是进程的一个实体,是CPU调度和分派的基本单位,它是比进程更小的能独立运行的基本单位。线程自己基本上不拥有系统资源,只拥有一点在运行中必不可少的资源(如程序计数器,一组寄存器和栈),但是它可与同属一个进程的其他的线程共享进程所拥有的全部资源。
区别:
a地址空间和其它资源:进程间相互独立,同一进程的各线程间共享这些资源,所以线程不再具有独立的地址空间和其他资源。某进程内的线程在其它进程不可见。
b通信:进程间通信(IPC)开销较大,要明确对象并采用同意协议;线程间可以直接读写进程数据段(如全局变量)来进行通信——需要进程同步和互斥手段的辅助,以保证数据的一致性,所以开销较小。
c.调度和切换:线程上下文切换比进程上下文切换要快得多。
d控制表:在多线程OS中,进程不是一个可执行的实体。进程有PCB,线程有TCB

Java多线程为一个对象,两种途径一种:继承Thread类(已经实现Runnable接口);另一种:直接实现Runnable接口

继承Thread类(已经实现Runnable接口)
Thread类属于java.lang包系统自动导入,无需import;
定义:

public class Threadextends Objectimplements Runnable

For example, a thread that computes primes larger than a stated value could be written as follows:

class PrimeThread extends Thread {         long minPrime;         PrimeThread(long minPrime) {             this.minPrime = minPrime;         }         public void run() {             // compute primes larger than minPrime              . . .         }     }
public class ThreadDemo extends Thread {    private String name;    public ThreadDemo(){    }    public ThreadDemo(String name){        setName(name);        }    public void run(){        for(int i=1;i<=10;i++){            System.out.println("Threads is :"+getName()+", i"+i);        }    }    public static void main(String[] args){        ThreadDemo t1=new ThreadDemo("Thread-1");        ThreadDemo t2=new ThreadDemo("Thread-2");        t1.start();        t2.start();    }}

运行结果
首先Thread的方法getName是final不能重新定义,直接使用;

public final String getName()

然后Run()方法是线程需要执行的内容,start()启动一个线程,并运行Run()方法。
最后因为线程的运行需要本机OS的支持,所以调用start()。如果调用多次start(),会抛出错误!

Exception in thread "main" java.lang.**IllegalThreadStateException**    at java.lang.Thread.start(Unknown Source)    at ThreadDemo.main(ThreadDemo.java:20)

这是单继承导致的,使用Runnable接口可以使之不抛出错误。

Runnable接口创建进程

class PrimeThread implements Runnable {         public void run() {                          . . .         }     }

实例:

public class RunnableDemo implements Runnable {    private String name;    public RunnableDemo(){    }    public RunnableDemo(String name){        setName(name);        }    public void setName(String name){        this.name=name;    }    public String getName(){        return name;    }    public void run(){        for(int i=1;i<=3;i++){            System.out.println("Threads is :"+getName()+", i"+i);        }    }    public static void main(String[] args){        RunnableDemo r1=new RunnableDemo("Thread-1");        RunnableDemo r2=new RunnableDemo("Thread-2");        Thread t1=new Thread(r1);        Thread t2=new Thread(r2);        Thread t3=new Thread(r1);        t1.start();        t2.start();        t3.start();    }}

由于Runnable接口对线程没有任何支持,因此获得线程实例以后,必须通过Thread类的构造方法来实现。

使用匿名内部类的形式创建线程

public class ThreadDemo3 {    public static void main(String[] args){        //1        Thread t1 = new Thread(){            public void run(){                for(int i=0;i<1000;i++){                    System.out.println("你是谁啊");                }            }        };        //2        Runnable runn = new Runnable(){            public void run(){                for(int i=0;i<1000;i++){                    System.out.println("我是查水表的");                }            }        };        Thread t2 = new Thread(runn);        t1.start();        t2.start();    }}

线程池 —-用于重用线程以及控制线程数量

import java.util.concurrent.ExecutorService;import java.util.concurrent.Executors;/** * 线程池  * 用于重用线程以及控制线程数量 * @author Administrator * */public class ThreadPoolDemo {    public static void main(String[] args){        ExecutorService threadPool            =   Executors              .newFixedThreadPool(2);        for(int i=0;i<5;i++){            Runnable runn                   = new Runnable(){                public void run(){                    for(int i=0;i<5;i++){                        System.out.println(i);                        try {                            Thread.sleep(1000);                        } catch (InterruptedException e) {                            e.printStackTrace();                        }                    }                }            };              threadPool.execute(runn);        }    }}

主线程–main()

public class MainThread implements Runnable{    public void run(){        for(int i=1;i<=3;i++){            System.out.println("Threads is :"+Thread.currentThread()+", i"+i);        }    }    public static void main(String[] args){        Thread mt=Thread.currentThread();        System.out.println("Threads is info:"+mt);        System.out.println("Threads is name:"+mt.getName());    }}

java程序至少执行两个线程一个main 另一个是垃圾收集线程

比较继承Thread类和实现Runnable的区别
Runnable的优点:
1. 避免Thread单继承特性带来的局限,一个类只能继承一个类,但是可以上线多个接口。
2. 多个相同程序代码的线程去处理同一资源,把线程同程序的代码、数据分离,更能体现面向对象思想;
3. 健壮性更强

public class ThreadDemo2 extends Thread {    private int ticket=10;    private String name;    public ThreadDemo2(){    }    public ThreadDemo2(String name){        setName(name);        }    public void run(){        while(ticket>0){            if(ticket>0){                System.out.println(Thread.currentThread().getName()+"卖出第 "+(ticket--)+"张 !");            }        }    }    public static void main(String[] args){        ThreadDemo2 t1=new ThreadDemo2("Thread-1");        ThreadDemo2 t2=new ThreadDemo2("Thread-2");        t1.start();        t2.start();    }}


个卖出10张票,有点不合实际!

public class ThreadDemo2 implements Runnable {    private int ticket=10;    public ThreadDemo2(){    }    public void run(){        while(ticket>0){            if(ticket>0){                System.out.println(Thread.currentThread().getName()+"卖出第 "+(ticket--)+"张 !");            }        }    }    public static void main(String[] args){        ThreadDemo2 window=new ThreadDemo2();        Thread t1=new Thread(window,"第一窗口");        Thread t2=new Thread(window,"第二窗口");        t1.start();        t2.start();    }}


共卖10张票

线程状态


1. 新建状态,具有相应的内存空间和其他资源,处于不可运行状态
2. 就绪状态,从新建状态调用start()方法,线程就进入继续状态,JVM为其创建方法调用栈和程序计数器,此时已具备运行条件,并没有进入运行状态,位于可运行池中,等待获得CPU的使用权
3. 运行状态,就绪状态形成被调用,获得JVM为其分配的CPU,自动执行run()方法。
4. 阻塞状态,调用sleep/suspend/wait方法进入此状态。JVM不会为其分配CPU
5. 死亡状态,run()运行结束或者异常退出,已经不是可执行的线程。

线程的优先级

t1.setPriority(Thread.MAX_PRIORITY);        t2.setPriority(2);

让线程休眠
Thread.sleep(100);//100为休眠时间
线程加入
t1.join(3000);//3000为等待时间
线程唤醒
t1.interrupt();
设置后台进程

t1.setDaemon(true);        if(t1.isDaemon()){            t1.start();        }

线程的礼让
Thread.currentThread().yield();只让权限高于或等于自己的。当前线程进入就绪状态;而sleep()当前线程进入阻塞状态;sleep()抛出异常;sleep()更具有可移植性,不能用yield()来提高进程并发性。

线程同步对临界资源的访问控制问题。

public class ThreadDemo2 implements Runnable {    private int ticket=10;    public ThreadDemo2(){    }    public void run(){        while(ticket>0){            if(ticket>0){                System.out.println(Thread.currentThread().getName()+"卖出第 "+(ticket--)+"张 !");            }        }    }    public static void main(String[] args){        ThreadDemo2 window=new ThreadDemo2();        Thread t1=new Thread(window,"第一窗口");        Thread t2=new Thread(window,"第二窗口");        t1.start();        t2.start();    }}

这里写图片描述
第十张票被卖出两次!
解决办法:同步代码块或者同步方法,也可以上锁,注意synchronized的位置不同。
同步代码块

public void run(){        while(ticket>0){            synchronized(this){                try{                    Thread.sleep(100);                }catch (InterruptedException  e){                    e.printStackTrace();                }                if(ticket>0){                    System.out.println(Thread.currentThread().getName()+"卖出第 "+(ticket--)+"张 !");                }            }        }

同步方法

public synchronized void run(){        while(ticket>0){                if(ticket>0){                    try{                        Thread.sleep(100);                    }catch (InterruptedException  e){                        e.printStackTrace();                    }                    System.out.println(Thread.currentThread().getName()+"卖出第 "+(ticket--)+"张 !");                }            }        }

同步锁

import java.util.concurrent.locks.*;public class ThreadDemo2 implements Runnable {    private final ReentrantLock lock=new ReentrantLock();//创建锁    private int ticket=10;    public ThreadDemo2(){    }    public synchronized void run(){        while(ticket>0){            lock.lock();            try{                Thread.sleep(100);                }catch (InterruptedException  e){                    e.printStackTrace();                    }            if(ticket>0){                System.out.println(Thread.currentThread().getName()+"卖出第 "+(ticket--)+"张 !");            }            lock.unlock();            }         }    public static void main(String[] args){        ThreadDemo2 window=new ThreadDemo2();        Thread t1=new Thread(window,"第一窗口");        Thread t2=new Thread(window,"第二窗口");        t1.start();        t2.start();    }}

线程通信
有两个线程sf和gf,sf放置食物,gf取走食物;并规定如下:
1. sf一次只能放置一批食物,并通知gf取走食物
2. gf一次只能取走一批食物,并通知sf放置食物
3. sf不放置食物,则gf不取食物;gf不取走食物,则sf无法放置食物
4. 开始先sf放置食物

/** * 有两个线程sf和gf,sf放置食物,gf取走食物;并规定如下: * 1.  sf一次只能放置一批食物,并通知gf取走食物 * 2. gf一次只能取走一批食物,并通知sf放置食物 * 3. sf不放置食物,则gf不取食物;gf不取走食物,则sf无法放置食物 * 4. 开始先sf放置食物 * @author jiachangbin */public class Food {    private int number=0;    private String food=null;    public Food(){    }    public Food(String food,int number){        this.food=food;        this.number=number;    }    public synchronized void setfood(int n){        if(this.number!=0){            try{                wait();            }catch (InterruptedException  e){                e.printStackTrace();                }            }        number=n;        System.out.println("放置 "+this.food+"第"+this.number+"批次");        notify();//唤醒等待池中的进程,通知去走食物        }    public synchronized String getfood(){        if(this.number==0){            try{                wait();            }catch (InterruptedException  e){                e.printStackTrace();                }            }        System.out.println("\t取走 "+this.number+"第"+this.food+"批次");        number=0;        notify();//唤醒等待池中的进程,通知去走食物        return food+number;        }    public static void main(String[] args){        Food f=new Food("火腿",0);        SetFood sf=new SetFood(f);        GetFood gf=new GetFood(f);        new Thread(sf).start();        new Thread(gf).start();    }           }
/** * 有两个线程sf和gf,sf放置食物,gf取走食物;并规定如下: * 1.  sf一次只能放置一批食物,并通知gf取走食物 * 2. gf一次只能取走一批食物,并通知sf放置食物 * 3. sf不放置食物,则gf不取食物;gf不取走食物,则sf无法放置食物 * 4. 开始先sf放置食物 * @author jiachangbin */public class Food {    private int number=0;    private String food=null;    public Food(){    }    public Food(String food,int number){        this.food=food;        this.number=number;    }    public synchronized void setfood(int n){        if(this.number!=0){            try{                wait();            }catch (InterruptedException  e){                e.printStackTrace();                }            }        number=n;        System.out.println("放置 "+this.food+"第"+this.number+"批次");        notify();//唤醒等待池中的进程,通知去走食物        }    public synchronized String getfood(){        if(this.number==0){            try{                wait();            }catch (InterruptedException  e){                e.printStackTrace();                }            }        System.out.println("\t取走 "+this.number+"第"+this.food+"批次");        number=0;        notify();//唤醒等待池中的进程,通知去走食物        return food+number;        }    public static void main(String[] args){        Food f=new Food("火腿",0);        SetFood sf=new SetFood(f);        GetFood gf=new GetFood(f);        new Thread(sf).start();        new Thread(gf).start();    }           }
/** * 有两个线程sf和gf,sf放置食物,gf取走食物;并规定如下: * 1.  sf一次只能放置一批食物,并通知gf取走食物 * 2. gf一次只能取走一批食物,并通知sf放置食物 * 3. sf不放置食物,则gf不取食物;gf不取走食物,则sf无法放置食物 * 4. 开始先sf放置食物 * @author jiachangbin */public class Food {    private int number=0;    private String food=null;    public Food(){    }    public Food(String food,int number){        this.food=food;        this.number=number;    }    public synchronized void setfood(int n){        if(this.number!=0){            try{                wait();            }catch (InterruptedException  e){                e.printStackTrace();                }            }        number=n;        System.out.println("放置 "+this.food+"第"+this.number+"批次");        notify();//唤醒等待池中的进程,通知去走食物        }    public synchronized String getfood(){        if(this.number==0){            try{                wait();            }catch (InterruptedException  e){                e.printStackTrace();                }            }        System.out.println("\t取走 "+this.number+"第"+this.food+"批次");        number=0;        notify();//唤醒等待池中的进程,通知去走食物        return food+number;        }    public static void main(String[] args){        Food f=new Food("火腿",0);        SetFood sf=new SetFood(f);        GetFood gf=new GetFood(f);        new Thread(sf).start();        new Thread(gf).start();    }           }

0、Java中多线程同步是什么?
在多线程程序下,同步能控制对共享资源的访问。如果没有同步,当一个Java线程在修改一个共享变量时,另外一个线程正在使用或者更新同一个变量,这样容易导致程序出现错误的结果。

1、解释实现多线程的几种方法?
一Java线程可以实现Runnable接口或者继承Thread类来实现,当你打算多重继承时,优先选择实现Runnable。

2、Thread.start()与Thread.run()有什么区别?
Thread.start()方法(native)启动线程,使之进入就绪状态,当cpu分配时间该线程时,由JVM调度执行run()方法。

4、什么是ThreadLocal类,怎么使用它?
ThreadLocal是一个线程级别的局部变量,并非“本地线程”。ThreadLocal为每个使用该变量的线程提供了一个独立的变量副本,每个线程修改副本时不影响其它线程对象的副本(译者注)。
下面是线程局部变量(ThreadLocal variables)的关键点:
1. 一个线程局部变量(ThreadLocal variables)为每个线程方便地提供了一个单独的变量。
2. ThreadLocal实例通常作为静态的私有的(private static)字段出现在一个类中,这个类用来关联一个线程。
3. 当多个线程访问ThreadLocal实例时,每个线程维护ThreadLocal提供的独立的变量副本。
4. 常用的使用可在DAO模式中见到,当DAO类作为一个单例类时,数据库链接(connection)被每一个线程独立的维护,互不影响。(基于线程的单例)
5. ThreadLocal难于理解,下面这些引用连接有助于你更好的理解它。《Good article on ThreadLocal on IBM DeveloperWorks 》、《理解ThreadLocal》、《Managing data : Good example》、《Refer Java API Docs》

5、什么时候抛出InvalidMonitorStateException异常,为什么?
调用wait()/notify()/notifyAll()中的任何一个方法时,如果当前线程没有获得该对象的锁,那么就会抛出IllegalMonitorStateException的异常(也就是说程序在没有执行对象的任何同步块或者同步方法时,仍然尝试调用wait()/notify()/notifyAll()时)。由于该异常是RuntimeExcpetion的子类,所以该异常不一定要捕获(尽管你可以捕获只要你愿意).作为RuntimeException,此类异常不会在wait(),notify(),notifyAll()的方法签名提及。

6、Sleep()、suspend()和wait()之间有什么区别?
1. Thread.sleep()使当前线程在指定的时间处于“非运行”(Not Runnable)状态,线程一直持有对象的监视器。比如一个线程当前在一个同步块或同步方法中,其它线程不能进入该块或方法中,如果另一线程调用了interrupt()方法,它将唤醒那个“睡眠的”线程。注意:sleep()是一个静态方法。这意味着只对当前线程有效,一个常见的错误是调用t.sleep(),(这里的t是一个不同于当前线程的线程)。即便是执行t.sleep(),也是当前线程进入睡眠,而不是t线程。t.suspend()是过时的方法,使用suspend()导致线程进入停滞状态,该线程会一直持有对象的监视器,suspend()容易引起死锁问题。
2. object.wait()使当前线程出于“不可运行”状态,和sleep()不同的是wait是object的方法而不是thread。调用object.wait()时,线程先要获取这个对象的对象锁,当前线程必须在锁对象保持同步,把当前线程添加到等待队列中,随后另一线程可以同步同一个对象锁来调object.notify(),这样将唤醒原来等待中的线程,然后释放该锁。基本上wait()/notify()与sleep()/interrupt()类似,只是前者需要获取对象锁。

7、在静态方法上使用同步时会发生什么事?
同步静态方法时会获取该类的“Class”对象,所以当一个线程进入同步的静态方法中时,线程监视器获取类本身的对象锁,其它线程不能进入这个类的任何静态同步方法。它不像实例方法,因为多个线程可以同时访问不同实例同步实例方法。

8、当一个同步方法已经执行,线程能够调用对象上的非同步实例方法吗?
可以,一个非同步方法总是可以被调用而不会有任何问题。实际上,Java没有为非同步方法做任何检查,锁对象仅仅在同步方法或者同步代码块中检查。如果一个方法没有声明为同步,即使你在使用共享数据Java照样会调用,而不会做检查是否安全,所以在这种情况下要特别小心。一个方法是否声明为同步取决于临界区访问(critial section access),如果方法不访问临界区(共享资源或者数据结构)就没必要声明为同步的。

9、 在一个对象上两个线程可以调用两个不同的同步实例方法吗?
不能,因为一个对象已经同步了实例方法,线程获取了对象的对象锁。所以只有执行完该方法释放对象锁后才能执行其它同步方法。看下面代码示例非常清晰:Common 类 有synchronizedMethod1()和synchronizedMethod2()方法,MyThread调用这两个方法。

10、 什么是死锁
死锁就是两个或两个以上的线程被无限的阻塞,线程之间相互等待所需资源。这种情况可能发生在当两个线程尝试获取其它资源的锁,而每个线程又陷入无限等待其它资源锁的释放,除非一个用户进程被终止。就JavaAPI而言,线程死锁可能发生在。
●当两个线程相互调用Thread.join()
●当两个线程使用嵌套的同步块,一个线程占用了另外一个线程必需的锁,互相等待时被阻塞就有可能出现死锁。

11、什么是线程饿死,什么是活锁?
线程饿死和活锁虽然不是死锁一样的常见问题,但是对于并发编程的设计者来说就像一次邂逅一样。
当所有线程阻塞,或者由于需要的资源无效而不能处理,不存在非阻塞线程使资源可用。JavaAPI中线程活锁可能发生在以下情形:
●当所有线程在程序中执行Object.wait(0),参数为0的wait方法。程序将发生活锁直到在相应的对象上有线程调用Object.notify()或者Object.notifyAll()。
●当所有线程卡在无限循环中。

0 0
原创粉丝点击