Java中的多线程(一)关于线程的基本操作

来源:互联网 发布:51单片机怎么烧程序 编辑:程序博客网 时间:2024/05/29 04:29

Java中的多线程

进程:进程是操作系统的基础,是一次程序的执行;是一个程序及其数据在处理机上顺序执行时所发生的活动;是程序在一个数据集合上的运行过程,它是系统进行资源分配和调度的一个独立单位。 线程:线程可以理解为是在进程中独立运行的子任务。当一个类中需要用到多线程时,一方面可以直接继承Thread类,另一方面可以去实现Runnable接口,这两种方法的作用其实是一样的,因为Thread类本身实现了Runnable接口,不过鉴于Java是单继承的特性,所以在这里,建议大家通过第二种方法去使用多线程。

获取当前线程名称的方法:Thread.currentThread().getName()
取得线程的唯一标识:getId()方法
线程放弃当前CPU资源:yield()方法,将此机会让给其他的任务去占用CPU执行时间。

多线程的一些特点 <咱们看例子说话>

public class Thread1Test extends Thread{    @Override    public void run() {        // TODO Auto-generated method stub        System.out.println("MyThread");    }    public static void main(String[] args) {        Thread1Test thread1Test=new Thread1Test();        //开启线程,实现异步操作,如果直接调用run(),则不会产生异步的效果        thread1Test.start();        System.out.println("fdjvdfdkljg");    }}

其运行结果为:
fdjvdfdkljg
MyThread

1.代码的运行结果与代码执行顺序或调用顺序是无关的,这种执行的随机性表现出CPU执行哪个线程具有不确定性。
public class Thread1Test extends Thread{    private int i;    public Thread1Test(int i){        this.i=i;    }    @Override    public void run() {        // TODO Auto-generated method stub        System.out.println(i);    }    public static void main(String[] args) {        Thread1Test thread1Test1=new Thread1Test(1);        Thread1Test thread1Test2=new Thread1Test(2);        Thread1Test thread1Test3=new Thread1Test(3);        Thread1Test thread1Test4=new Thread1Test(4);        Thread1Test thread1Test5=new Thread1Test(5);        Thread1Test thread1Test6=new Thread1Test(6);        thread1Test1.start();        thread1Test2.start();        thread1Test3.start();        thread1Test4.start();        thread1Test5.start();        thread1Test6.start();    }}

其执行结果为:
1
4
5
6
2
3

2. 当一个程序中存在多个start()方法时,执行start()方法的顺序不代表线程启动的顺序。
3. 自定义线程类中的实例变量针对其他线程可以有共享与不共享之分,这个特点在多线程之间进行交互时是很重要的一个技术点。
1)不共享数据
public class Thread1Test extends Thread{    private int count=5;    public Thread1Test(String name){        this.setName(name);    }    @Override    public void run() {        // TODO Auto-generated method stub        while(count>0){            count--;            System.out.println("由"+this.currentThread().getName()+                    "计算,"+"count="+count);        }    }    public static void main(String[] args) {        Thread1Test thread1Test1=new Thread1Test("A");        Thread1Test thread1Test2=new Thread1Test("B");        Thread1Test thread1Test3=new Thread1Test("C");        thread1Test1.start();        thread1Test2.start();        thread1Test3.start();    }}

执行结果为:
由B计算,count=4
由C计算,count=4
由A计算,count=4
由C计算,count=3
由B计算,count=3
由C计算,count=2
由A计算,count=3
由C计算,count=1
由B计算,count=2
由C计算,count=0
由A计算,count=2
由A计算,count=1
由A计算,count=0
由B计算,count=1
由B计算,count=0

  • 从结果中我们会发现,对于每一个线程,都会有独立的一个count,这种情况就是数据不共享的情况。
2)共享数据
public class Thread1Test extends Thread{    private int count=5;    @Override    public void run() {        // TODO Auto-generated method stub        while(count>0){            count--;            System.out.println("由"+this.currentThread().getName()+                    "计算,"+"count="+count);        }    }    public static void main(String[] args) {        Thread1Test thread1Test1=new Thread1Test();        Thread t1=new Thread(thread1Test1, "A");        Thread t2=new Thread(thread1Test1, "B");        Thread t3=new Thread(thread1Test1, "C");        t1.start();        t2.start();        t3.start();    }}

运行结果为:
由B计算,count=3
由A计算,count=3
由B计算,count=2
由A计算,count=1
由B计算,count=0

  • 从结果中我们会发现,A与B同时对count进行了操作,所以count=3,一共出现了两次,这里也就产生了非线程安全的问题
  • 非线程安全主要是指多个线程对同一个对象的同一个变量进行操作,使得变量的值不能够同步的情况)这里其实我们会发现其实count数据已经被三个线程所共享了,但由于出现了非线程安全的问题,所以此时的结果离我们想要的最终结果(count 由5到0依次递减)是有一定的差距。 为此,我们对程序进行了修改,如下所示:
public class Thread1Test extends Thread{    private int count=5;    @Override     public synchronized void run() {        // TODO Auto-generated method stub        count--;        System.out.println("由"+this.currentThread().getName()+                    "计算,"+"count="+count);      }    public static void main(String[] args) {        Thread1Test thread1Test1=new Thread1Test();        Thread t1=new Thread(thread1Test1, "A");        Thread t2=new Thread(thread1Test1, "B");        Thread t3=new Thread(thread1Test1, "C");        t1.start();        t2.start();        t3.start();    }}

通过在run方法前加synchronized关键字,使得多个线程在执行run方法时,以排队的方式进行处理。当一个线程调用r9un方法前,先判断run方法有没有被上锁,如果上锁,说明其它线程正在调用run方法,此线程必须等其它线程对run方法调用结束后才能执行run方法。这样就实现了排队调用run方法的目的,也就达到了按顺序对count进行减一的效果。synchronized关键字可以在任何对象和方法上加锁,而加锁的这段代码被称为“互斥区”或者“临界区”。

—————————————————————————

有一点需要注意的是,线程操作如果放在System.out.println()中执行的话,会产生非线程安全的问题

public class Thread1Test extends Thread{    private int count=5;    @Override     public void run() {        // TODO Auto-generated method stub        System.out.println("由"+this.currentThread().getName()+                    "计算,"+"count="+(count--));      }    public static void main(String[] args) {        Thread1Test thread1Test1=new Thread1Test();        Thread t1=new Thread(thread1Test1);        Thread t2=new Thread(thread1Test1);        Thread t3=new Thread(thread1Test1);        Thread t4=new Thread(thread1Test1);        Thread t5=new Thread(thread1Test1);        Thread t6=new Thread(thread1Test1);        t1.start();        t2.start();        t3.start();        t4.start();        t5.start();        t6.start();    }}

运行结果为:
由Thread-1计算,count=5
由Thread-6计算,count=2
由Thread-4计算,count=3
由Thread-3计算,count=4
由Thread-2计算,count=5
由Thread-5计算,count=1
从结果上我们会发现,非线程安全的问题又一次发生了,为什么会有这种情况呢?看过源码的人都知道println方法中有synchronized关键字修饰,是线程安全的,那为什么还会产生这种错误呢?让我们再仔细看看println方法的源码,看完之后你就懂了。

 public void println(String x) {        synchronized (this) {            print(x);            newLine();        }    }

原来println方法的synchronized关键字是在方法体的内部,而我们上边变量的改变是在println方法的参数部分。所以产生了这种非线程安全的情况。

—————————————————————————

线程的停止

停止线程是在多线程开发时很重要的技术点。停止线程意味着在线程处理完任务之前停止掉正在做的操作,也就是放弃当前的操作。停止一个线程需要用到Thread.stop()方法或者Thread.interrupt()方法,Thread.stop()方法确实可以停止一个正在运行的线程,但是这个方法是不安全的,而且是已被弃用的。而大多数停止一个线程的操作使用的是Thread.interrupt()方法,这个方法不会终止一个正在运行的线程,还需要加入一个判断才可以完成线程的停止。单独调用interrupt()方法仅仅是在当前线程中打了一个停止的标记,并不是真的停止线程。

在Java中一共有三种方法可以终止正在运行的线程:
* 使用退出标志,使线程正常退出,也就是当run方法完成之后线程终止。
* 使用stop方法强行终止线程,但是不推荐使用这个方法,因为stop和suspend及resume方法一样,都是作废过期的方法,使用它们可能产生不可预料的后果。
* 使用interrupt方法中断线程。

1.判断线程是否是停止状态

Thread.java类里提供了两种方法:
1)this.interrupted():测试当前线程(运行此方法的线程)是否已经停止。
2)this.isInterrupted():测试线程是否已经中断。

1.对于this.interrupted()方法,此方法是static的,如果当前的线程处于中断状态,那么调用此方法可以清除线程的中断状态,下面我给出一个例子:
public class Thread1Test extends Thread{    @Override     public void run() {        // TODO Auto-generated method stub    }    public static void main(String[] args) {        Thread.currentThread().interrupt();        System.out.println("是否停止1?="+Thread.interrupted());        System.out.println("是否停止2?="+Thread.interrupted());    }}

运行结果为:
是否停止1?=true
是否停止2?=false

2.对于isInterrupted()方法,此方法不是static的,此方法只能够测试Thread对象是否处于中断状态,但不清除状态标志。下面,我同样给出一个例子:
public class Thread1Test extends Thread {    @Override    public void run() {        // TODO Auto-generated method stub        for(int i=0;i<50000;i++){            System.out.println("i="+(i+1));        }    }    public static void main(String[] args) {        try {            Thread1Test thread1Test=new Thread1Test();            thread1Test.start();            Thread.sleep(2000);            Thread.currentThread().interrupt();            System.out.println("是否停止1?="+Thread.interrupted());            System.out.println("是否停止2?="+Thread.interrupted());        } catch (InterruptedException e) {            // TODO Auto-generated catch block            e.printStackTrace();        }    }}

运行结果为:
i=49995
i=49996
i=49997
i=49998
i=49999
i=50000
是否停止1?=true
是否停止2?=false

2.在沉睡中停止线程

1.在sleep状态下停止某一线程:
public class Thread1Test extends Thread {    @Override    public void run() {        try {            System.out.println("run begin");            Thread.sleep(20000);            System.out.println("run end");        }catch (InterruptedException e){            System.out.println("在沉睡中被停止!进入catch"+this.isInterrupted());            e.printStackTrace();        }    }    public static void main(String[] args) {        try {            Thread1Test thread1Test=new Thread1Test();            thread1Test.start();            Thread.sleep(200);            thread1Test.interrupt();        } catch (InterruptedException e) {            // TODO Auto-generated catch block            System.out.println("main catch");            e.printStackTrace();        }    }}

运行结果为:
run begin
在沉睡中被停止!进入catchfalse
java.lang.InterruptedException: sleep interrupted
at java.lang.Thread.sleep(Native Method)
at com.company.Thread1Test.run(Thread1Test.java:11)
Process finished with exit code 0

在sleep状态下停止某一线程,会进入catch语句,并且清除停止状态值,使之变成false.

2.先用interrupt()停止,然后在sleep:
public class Thread1Test extends Thread {    @Override    public void run() {        try {            for(int i=0;i<10000;i++){                System.out.println("i="+(i+1));            }            System.out.println("run begin");            Thread.sleep(20000);            System.out.println("run end");        }catch (InterruptedException e){            System.out.println("先停止,再遇到了sleep!进入catch");            e.printStackTrace();        }    }    public static void main(String[] args) {        try {            Thread1Test thread1Test=new Thread1Test();            thread1Test.start();            Thread.sleep(200);            thread1Test.interrupt();        } catch (InterruptedException e) {            // TODO Auto-generated catch block            System.out.println("main catch");            e.printStackTrace();        }    }}

运行结果为:
i=9996
i=9997
i=9998
i=9999
i=10000
run begin
先停止,再遇到了sleep!进入catch
java.lang.InterruptedException: sleep interrupted
at java.lang.Thread.sleep(Native Method)
at com.company.Thread1Test.run(Thread1Test.java:15)
Process finished with exit code 0

根据运行结果我们可以看到线程先停止,再遇到了sleep!进入catch。

3.使用return停止线程

将方法interrupt()与return相结合也能达到停止线程的效果,测试代码:

public class Thread1Test extends Thread {    @Override    public void run() {        while(true){           if(this.isInterrupted()){               System.out.println("停止了!");               return;           }           System.out.println("time="+System.currentTimeMillis());        }    }    public static void main(String[] args) {        try {            Thread1Test t1=new Thread1Test();            t1.start();            Thread.sleep(2000);            t1.interrupt();        } catch (InterruptedException e) {            e.printStackTrace();        }        System.out.println("end!");    }}

运行结果如下所示:
time=1490408029279
time=1490408029279
time=1490408029279
time=1490408029279
time=1490408029279
time=1490408029279
time=1490408029279
time=1490408029279
time=1490408029279
end!
停止了!

线程在return和interrupt()方法的配合下实现了停止。 不过一般不建议使用这种方式来停止线程,因为还有一种更好的方式来停止线程,接下来我就给大家介绍这种方式。

4.使用异常停止线程

public class Thread1Test extends Thread {    @Override    public void run() {        try{            for(int i=0;i<500000;i++){                if(this.interrupted()) {                    System.out.println("已经是停止状态了,我要退出");                    throw new InterruptedException();                }                System.out.println("i="+(i+1));            }            System.out.println("我在for下面");        }catch(InterruptedException e){            System.out.println("进入到run方法的catch语句块了");            e.printStackTrace();        }    }    public static void main(String[] args) {        try {            Thread1Test t1=new Thread1Test();            t1.start();            Thread.sleep(2000);            t1.interrupt();        } catch (InterruptedException e) {            e.printStackTrace();        }        System.out.println("end!");    }}

运行结果为:
i=384646
i=384647
i=384648
i=384649
end!
已经是停止状态了,我要退出
进入到run方法的catch语句块了
java.lang.InterruptedException
at com.company.Thread1Test.run(Thread1Test.java:15)
此方法可以有效的停止线程,并在catch语句块中将异常向上抛,使得线程停止的事件1得以传播。

暂停线程

暂停线程意味着此线程还可以恢复运行,在java多线程中,可以通过suspend()方法暂停线程,使用resume()方法恢复线程。

1.suspend()与resume()方法的使用

    public class MyThread extends Thread {    private long i=0;    public long getI() {        return i;    }    public void setI(long i) {        this.i = i;    }    @Override    public void run() {        while(true){            i++;        }    }    public  static void main(String []args){        try {            MyThread thread = new MyThread();            thread.start();            Thread.sleep(5000);            //A段            thread.suspend();            System.out.println("A="+System.currentTimeMillis()+"i="+thread.getI());            Thread.sleep(5000);            System.out.println("A="+System.currentTimeMillis()+"i="+thread.getI());            //B段            thread.resume();            Thread.sleep(5000);            System.out.println("B="+System.currentTimeMillis()+"i="+thread.getI());            Thread.sleep(5000);            System.out.println("B="+System.currentTimeMillis()+"i="+thread.getI());        } catch (InterruptedException e) {            e.printStackTrace();        }    }}

运行结果为:
A=1490414043916i=2697026143
A=1490414048917i=2697026143
B=1490414053917i=5513982481
B=1490414058917i=8322025282

从运行结果可以看到线程确实被暂停了,而且还可以恢复到运行状态

2.suspend()与resume()方法的缺点——独占

在使用suspend()与resume()方法,如果使用不当,极易造成公共的同步对象的独占,使得其他线程无法访问公共同步对象。

public class SynchronizeObject {    synchronized public void printString(){        System.out.println("begin");        if (Thread.currentThread().getName().equals("a")){            System.out.println("a线程永远suspend");            Thread.currentThread().suspend();        }        System.out.println("end");    }    public  static void main(String []args){        try {            final SynchronizeObject object=new SynchronizeObject();            Thread t1=new Thread(){                @Override                public void run() {                    object.printString();                }            };            t1.setName("a");            t1.start();            Thread.sleep(1000);            Thread t2=new Thread(){                @Override                public void run() {                    super.run();                    System.out.println("t2启动了,但进入不了printString()方法!只打印了一个begin" );                    System.out.println("因为printString()方法被a线程锁定并且永远suspend暂停了!");                    object.printString();                }            };            t2.start();        } catch (InterruptedException e) {            e.printStackTrace();        }    }}

运行结果为:
begin
a线程永远suspend
t2启动了,但进入不了printString()方法!只打印了一个begin
因为printString()方法被a线程锁定并且永远suspend暂停了!

从运行结果中我们可以看出,当线程t1执行printString()方法时遇到suspend()被暂停,然后t2就不能再执行printString()方法了。也就造成了公共同步对象的独占。

3.suspend()与resume()方法的缺点——不同步

在使用suspend()与resume()方法时也容易出现因为线程的暂停而导致数据不同步的情况。

public class MyObject {    private String username="l";    private String password="ll";    public void setValue(String u,String p){        this.username=u;        if(Thread.currentThread().getName().equals("a")){            System.out.println("停止a线程");            Thread.currentThread().suspend();        }        this.password=p;    }    public void printUsernamePassword(){        System.out.println(username+" "+password);    }    public  static void main(String []args){        try {            MyObject object=new MyObject();            Thread t1=new Thread(){                @Override                public void run() {                    super.run();                    object.setValue("a","aa");                }            };            t1.setName("a");            t1.start();            Thread.sleep(500);            Thread t2=new Thread(){                @Override                public void run() {                    super.run();                    object.printUsernamePassword();                }            };            t2.start();        } catch (InterruptedException e) {            e.printStackTrace();        }    }}

运行结果为:
停止a线程
a ll
从运行结果来看,当在线程执行setValue()方法时,将线程通过suspend()方法暂停,则会出现以上值不同步的现象。

线程的优先级

在操作系统中,线程可以划分优先级,优先级较高的线程得到的CPU资源较多,也就是CPU优先执行优先级较高的线程对象中的任务。
线程优先级的一些特性:
  • 继承性:在Java中,线程的优先级具有继承性,比如A线程启动B线程,则B线程的优先级与A是一样的。
  • 规则性:两个优先级不同的线程同时开始处理某些任务时了,高优先级的线程总是大部分先执行完。
  • 随机性:随机性是说,优先级较高的线程不一定每一次都会被先执行完。

守护线程

在Java线程中有两种线程,一种是用户线程,另一种是守护线程。守护线程是一种特殊的线程,它的特性有”陪伴“的含义,当进程中不存在非守护线程了,则守护线程自动销毁。守护线程最典型的应用就是GC(垃圾回收器)
1 0
原创粉丝点击