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

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
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值