多线程2-使用多线程

此章包含:

  1、线程的实现方式、2、实例变量与线程安全3、线程常用的几个api4、停止线程5、暂停线程6、yield方法7、线程的优先级、8、守护线程。

1、线程的常见实现方式有两种。继承Thread或实现Runable接口。代码如下:

//继承Thraed类,重写run方法
class ThreadTest extends Thread{
    @Override
    public void run(){
        super.run();
        System.out.println("ThreadTest");
    }
}
//实现Runbale接口,重写run方法
class ThreadTest1 implements Runnable{
    @Override
    public void run() {
        System.out.println("ThreadTest1");
    }
}

2、实例变量和线程安全

  在自定义线程类中的实例变量针对其他线程可以有共享与不共享之分。

  不共享数据,即对于同一个实例变量每个线程都有各自的独立一份,该实例变量在多线程下共享

  如下一段演示代码,每个变量都有属于自己的count值,对该值进行递减互不影响

public class MyThread2 {
    public static void main(String[] args) {
        Test1 a = new Test1("A");
        Test1 b = new Test1("B");
        Test1 c = new Test1("C");
        a.start();
        b.start();
        c.start();
    }
}

class Test1 extends Thread{
    private int count = 5;
    public Test1(String name){
        super();
        //设置线程名称
        this.setName(name);
    }
    @Override
    public void run(){
        super.run();
        while (count > 0){
            count--;
            System.out.println("由" + currentThread().getName()
            +"计算,count = " + count);
        }
    }
}

  结果如下:

由B计算,count = 4
由C计算,count = 4
由C计算,count = 3
由C计算,count = 2
由C计算,count = 1
由A计算,count = 4
由C计算,count = 0
由B计算,count = 3
由A计算,count = 3
由A计算,count = 2
由A计算,count = 1
由A计算,count = 0
由B计算,count = 2
由B计算,count = 1
由B计算,count = 0

  共享数据,多个线程可以访问同一个变量,比如实现一个投票功能,多个线程可以同时处理一个人的票数,代码如下:

public class MyThread2 {
    public static void main(String[] args) {
        Test1 test1 = new Test1();
        Thread a = new Thread(test1,"A");
        Thread b = new Thread(test1,"B");
        Thread c = new Thread(test1,"C");
        Thread d = new Thread(test1,"D");
        Thread e = new Thread(test1,"E");
        a.start();
        b.start();
        c.start();
        d.start();
        e.start();
    }
}
class Test1 extends Thread{
    private int count = 5;
    @Override
    public void run(){
        super.run();
        count--;
        //此示例去掉循环,因为同步后其他线程就得不到运行机会。
        System.out.println("由" + currentThread().getName()
            +"计算,count = " + count);
    }
}

  结果之一如下:

由A计算,count = 3
由B计算,count = 3
由D计算,count = 1
由C计算,count = 2
由E计算,count = 0

  线程A和B打印的count值都是3,说明A和B同时对count进行处理,产生了“非线程安全“问题。我们想要的结果是依次递减,而不是重复的。

  在这个操作过程中,产生该问题的原因是i--操作不是原子性操作,它实际是三个操作:1、取得i值;2、计算i-1;3、对i赋值;

  当多个线程同时访问时就会出现非线程安全问题。这种典型问题我们称之为静态条件,当某个计算的正确性取决于多个线程的交替执行时序时,就会发生竞态条件。

  对于这个情况我们可以使用线程同步,来串行执行程序。重新运行更改后的代码,就不会出现竞态条件了

class Test1 extends Thread{
    private int count = 5;
    @Override
    synchronized  public void run(){
        super.run();
        count--;
        //此示例去掉循环,因为同步后其他线程就得不到运行机会。
        System.out.println("由" + currentThread().getName()
            +"计算,count = " + count);
    }
}
结果为:
由A计算,count = 4
由D计算,count = 3
由E计算,count = 2
由B计算,count = 1
由C计算,count = 0

  在run方法前加synchronized关键字,当一个线程调用run前,会判断runfangf有没有被上锁。如果上锁,说明其他线程正在调用,必须等其他线程调用结束后才可以执行。这样也就实现了排队调用run方法的目的。synchronized可以在任意对象及方法上加锁,而加锁的这段代码称为“互斥区”或“临界区”。

3、线程常用的几个api

  3.1、 currentThread()方法

  该方法可以返回代码段正在被哪个线程调用的信息。

public class ThreadTest3 {
    public static void main(String[] args) {
        System.out.println(Thread.currentThread().getName());
    }
}
结果:
main

  3.2、isAlive()方法

  该方法可以判断当前的线程是否处于活动状态,活动状态指线程已经启动且尚未终止,处于正在运行或装备开始运行的状态。

public class ThreadTest3 {
    public static void main(String[] args) throws InterruptedException {
        Test test = new Test();
        System.out.println("begin=" + test.isAlive());
        test.start();
        Thread.sleep(1000);
        System.out.println("end=" + test.isAlive());
    }
}
class Test extends Thread{
    @Override
    public void run(){
        System.out.println("run=" + this.isAlive());
    }
}

  3.3、sleep()方法

  在指定的毫秒数内让当前“正在执行的线程”休眠(暂停执行)。这个正在执行的线程是this.currentThread()返回的线程。

  代码如上所示

  3.3、getId()方法

  取得线程的唯一标识

public class ThreadTest3 {
    public static void main(String[] args) throws InterruptedException {
        Thread thread = Thread.currentThread();
        System.out.println(thread.getName() +"  "+ thread.getId());
    }
}
结果:
main  1

   4、停止线程

  停止一个线程意味着在线程处理完任务之前停掉正在做的操作,一般采用的方式有三种:

  1、使用退出标志,使线程正常退出,也就是当run方法完成后线程终止

  2、使用stop方法强行终止线程,但是不推荐使用此方法,因为stop和suspend及resume一样,都是作废过期的方法,可能造成不可预料的结果。

  3、使用interrupt方法中断线程。

  下面我们开始了解线程停止的相关知识。

  4.1、interrupt、interrupted、isInterrupted方法及区别

    interrupt():在当前线程中打一个停止标记,并不是真正的停止线程。需要自己监视线程状态并处理。

    interrupted():判断当前线程是否已经中断,运行后会清除中断状态,是静态方法。

    isInterrupted():判断调用该方法的线程是否已经中断,是实例方法。

    interrupted和isInterrupted底层调用是同一个方法,只是参数不同。interrupted参数是true,代表要清除状态位,而isInterrupted是false,代表不清除状态位。

  4.2、异常法停止线程

public class MyThread3 {
    public static void main(String[] args) throws InterruptedException {
        Demo demo =new Demo();
        demo.start();
        Thread.sleep(1000);
        demo.interrupt();
        System.out.println("end!");
    }
}
class Demo extends Thread{
    @Override
    public void run(){
        super.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("继续进行");
        } catch (InterruptedException e) {
            System.out.println("进入catch");
            e.printStackTrace();
        }
    }
}
//如果在if中使用break,会出现线程停止后,如果for语句后还有语句就会继续执行。为了完全的停止线程,我们采用异常法。            

  4.3、在沉睡中停止线程

  当线程处于sleep状态时,停止线程会抛出异常,与之相反的操作也会抛出异常。

public class MyThread extends Thread{
    public static void main(String[] args) throws InterruptedException {
        Demo demo = new Demo();
        demo.start();
        Thread.sleep(2000);
        demo.interrupt();
        System.out.println("end");
    }
}
class Demo extends Thread{
    public void run() {
        super.run();
        try {
            System.out.println("run begin");
            Thread.sleep(2000000);
            System.out.println("run end");
        } catch (InterruptedException e) {
            System.out.println("在沉睡中被停止"+this.isInterrupted());
            e.printStackTrace();
        }
    }
}
//结果
run begin
end
在沉睡中被停止false
java.lang.InterruptedException: sleep interrupted

   4.4、暴力停止线程-stop方法的不可取

  使用stop方法停止线程,是非常暴力的。强制让线程停止可能使一些清理下工作得不到完成。另外可能对锁定对象进行解锁,导致数据不一致的问题。

  4.5、使用return停止线程

  使用interrupt()方法和return结合实现停止线程

public class MyThread extends Thread{
    public static void main(String[] args) throws InterruptedException{
        Demo d = new Demo();
        d.start();
        Thread.sleep(2000);
        d.interrupt();
    }
}
class Demo extends Thread{
    public void run() {
        while(true) {
            if (this.isInterrupted()) {
                System.out.println("停止了");
                return;
            }    
            System.out.println("timer=" + System.currentTimeMillis());
        }    
    }
}

  5、暂停线程

  暂停线程意味着可线程还可以恢复运行,在Java多线程中,可以使用suspend()方法暂停线程,使用resume()方法恢复线程执行

  但这两个方法不建议使用,suspend在使线程暂停时,不会释放任何锁资源。其他线程无法访问被它占用的锁。直到对应线程的resume方法后,被暂停的线程才能继续。其他被阻塞在这个锁的线程可以继续执行。但是,当resume操作在suspend之前执行,那么线程会一直处于挂起状态,同时一直占用锁。这会产生死锁,并且被挂起的线程状态是Runnable(运行)

public class MyThread3 {
    public static Object object = new Object();
    static TestThread t1 = new TestThread("线程1");
    static TestThread t2 = new TestThread("线程2");
    public static class TestThread extends Thread{
        public TestThread(String name){
            super.setName(name);
        }
        public void run(){
            synchronized (object){
                System.out.println(getName()+"占用中..");
                Thread.currentThread().suspend();
                System.out.println(getName()+"结束了执行..");
            }
        }
    }
    public static void main(String[] args) throws InterruptedException {
        t1.start();
        Thread.sleep(2000);
        t2.start();
        t1.resume();
        t2.resume();
        t1.join();
        t2.join();
    }
}
代码执行结果:
    线程1占用中..
    线程1结束了执行..
    线程2占用中..
代码执行流程:
    线程1占用后,被暂停,然后执行恢复。此时t2.start执行,但resume执行在suspend之前,导致线程被永久挂起,t2结束无法执行。从虚拟机观察线程状态,会是Runnable

  6、yield

  该方法作用是放弃当前的CPU资源,将它让给其他的任务去占用CPU执行时间。放弃时间不确定,有可能刚放弃马上又获得CPU时间片

public class MyThread3 {
    public static void main(String[] args) throws InterruptedException {
        TestThread t1 = new TestThread("线程A");
        TestThread t2 = new TestThread("线程B");
        t1.start();
        Thread.yield();
        t2.start();
    }
}

class TestThread extends Thread{
    public TestThread(String name){
        super.setName(name);
    }
    public void run(){
        for (int i = 0; i < 10; i++) {
            System.out.println(getName() + "在执行..");
        }
    }
}
运行结果:
线程B在执行..
线程B在执行..
线程B在执行..
线程B在执行..
线程B在执行..
线程B在执行..
线程B在执行..
线程A在执行..
线程A在执行..
线程A在执行..
线程B在执行..
线程B在执行..
线程B在执行..
线程A在执行..
线程A在执行..
线程A在执行..
线程A在执行..
线程A在执行..
线程A在执行..
线程A在执行..
从结果中看出B线程放弃CPU时间片后,线程A并没有立马去获得,而是B线程又获得了时间片

   7、线程优先级

  线程划分优先级、优先级较高的线程得到的CPU资源较多,CPU会优先执行优先级别较高的线程对象中的任务。设置优先级使用setPriority()方法。

  关于优先级有一下几点需要考虑:

  1、当前线程优先级未指定时,所有线程都携带普通优先级

  2、优先级范围1-10。10最高,1最低,5是普通优先级

  3、优先级最高的线程在执行时被给予优先,但不能保证线程在启动时就进入运行状态

  4、与线程池中等待运行机会的线程相比,当前正在运行的线程可能总是拥有更高优先级

  5、在线程开始方法被调用前,设定线程优先级

  6、优先级常量设置,MIN_PRIORITY(1)、MAX_PRIORITY(10)、NORM_PRIORITY(5)

  优先级的继承性:线程优先级具有继承性,A线程启动B线程,则B线程的优先级与A一致。

  优先级的规则性:CPU尽量将执行资源让给优先级较高的线程,但不代表高优先级的线程全部先执行完。

  优先级的随机性:优先级较高的线程并不一定每一次都先执行完run方法中的任务,线程优先级与打印顺序无关,两者之间具有随机性。

  8、守护线程

  java线程中有两种线程,一种是用户线程,一种是守护线程。当进程中不存在非守护线程了,则守护线程自动销毁。典型的守护线程就是垃圾回收线程。

  创建一个守护线程:

public class MyThread {
    public static void main(String[] args) throws InterruptedException{
        try {
            Demo d = new Demo();
            d.setDaemon(true);
            d.start();
            Thread.sleep(5000);
            System.out.println("离开了d对象也不再打印,也就是停止了");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
class Demo extends Thread{
    private int i = 0;
    public void run() {
        try {
            while(true) {
                i++;
                System.out.println("i="+i);
                Thread.sleep(1000);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
结果:
i=1
i=2
i=3
i=4
i=5
离开了d对象也不再打印,也就是停止了

  注意:

    1、设置守护线程,必须在start之前设置,否则会有异常。不能把正在运行的常规线程设置为守护线程

    2、把Daemon线程中产生的新线程也是Daemon的

    3、守护线程应该永远不去访问固有资源,如文件、数据库,因为它会在任何时候甚至在一个操作的中间发生中断

猜你喜欢

转载自www.cnblogs.com/zhangbLearn/p/9761603.html