【Java多线程编程核心技术】第一章(多线程技能 currentThread, isAlive,sleep,getId)

Thread类的核心方法较多,读者应该着重掌握如下关键技术点:

□线程的启动

□如果使线程暂停

□如何使线程停止

□线程的优先级

□线程安全相关的问题

1.1 进程与多线程的概念及线程的优点

     本节主要介绍在Java语言中使用多线程技术。但是讲到多线程不得不提到进程这个概念:

进程(Process)是计算机中的程序关于某数据集合上的一次运行活动,
是系统进行资源分配和调度的基本单位,是操作系统结构的基础。在早
期面向进程设计的计算机结构中,进程是程序的基本执行实体;在当代
面向线程设计的计算机结构中,进程是线程的容器。程序是指令、数据
及其组织形式的描述,进程是程序的实体。

    将一个正在操作系统中运行的exe程序理解成一个进程!通过查看任务管理器中的列表,完全可以将运行在内存中的exe文件理解成进程,进程是受操作系统管理的基本运行单元。

   那什么是线程呢?线程可以理解成是进程中独立运行的子任务。比如,QQ.exe运行时就有很多的子任务在同时运行。再如,好友视频线程,下载文件线程,传输数据线程等等,这些不同的任务或者说功能都可以同时运行,其中每一项任务完全可以理解成是线程在工作。使用多任务操作系统后,可以最大限度地利用CPU的空闲时间来处理其它的任务,比如一边打印,一边使用word编辑。而CPU在这些任务之间不停地切换,由于切换速度非常快,给使用者的感觉就是这些任务似乎在同时运行。所以在使用多线程技术后,可以在同一时间内运行更多不同种类的任务。

1.2 使用多线程

  一个进程正在运行时至少会有一个线程在运行,这种情况在java中也是存在的。这些线程在后台默默地执行:

	public static void main(String[] args) {
		System.out.println(Thread.currentThread().getName());// main
	}

  在控制台输出的main其实就是一个名称叫做main的线程在执行main方法中的代码。另外需要说明一下,在控制台输出的main和main方法没有任何的关系,仅仅是名字相同而已

1.2.1 继承Thread类

         实现多线程编程的方式有两种,一种是继承Thread类,另一种是实现Runnable接口。先来看看Thread类的结构:

public class Thread implements Runnable

        从源代码可以发现,Thread类实现了Runnable接口,它们之间具有多态关系。其实,使用继承Thread类的方式创建线程时,最大的局限就是不支持多继承,因为Java语言的特点就是单根继承,所以为了支持多继承,完全可以实现Runnable接口的方式,一边实现一边继承。

public class MyThread extends Thread {
	@Override
	public void run() {
		super.run();
		System.out.println("My Thread!");
	}	
	public static void main(String[] args) {
		MyThread my = new MyThread();
		my.start();
		System.out.println("运行结束");// 运行结束/n My Thread!
	}
}

        以上代码的打印顺序可以看出来,run方法执行的时间比较晚,这也说明在使用多线程技术时,代码的运行结果与代码执行顺序或调用顺序无关

       上面代码介绍了线程的调用的随机性,下面将演示线程的随机性。

public class MyThread extends Thread {

	@Override
	public void run() {
		try {
			for(int i=0; i<10; i++) {
				int time = (int)(Math.random()*1000);
				Thread.sleep(time);
				System.out.println("run="+Thread.currentThread().getName());
			}
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}
	public static void main(String[] args) {
		MyThread my = new MyThread();
		my.setName("myThread");
		my.start();
		
		try {
			for(int i=0; i<10; i++) {
				int time = (int)(Math.random()*1000);
				Thread.sleep(time);
				System.out.println("main="+Thread.currentThread().getName());
			}
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}
}

       在代码中,为了展现出线程具有随机特性,所以使用随机数的形式来使线程得到挂起的效果,从而表现出CPU执行哪个线程具有不确定性。Thread.java类中的start方法通过‘线程规划器‘此线程已经准备就绪,等待调用线程对象的run方法。这个过程其实就是让系统安排一个时间来调用Thread中的run方法,也就是线程得到运行,启动线程,具有异步执行的效果。如果直接调用代码thread.run就不是异步执行。 而是同步,那么此线程对象并不会交给"线程规划器"来进行处理,而是由main主线程来调用run方法,也就是必须等run方法中的代码执行完成后才能执行后面的代码。(看下面代码中的注释)

    public static void main(String[] args) {
		MyThread my = new MyThread();
		my.setName("myThread");
		my.run();// 由start替换成run,运行结果发生变化,先执行完线程里面的,然后再执行main方法里面的	
		try {
			for(int i=0; i<10; i++) {
				int time = (int)(Math.random()*1000);
				Thread.sleep(time);
				System.out.println("main="+Thread.currentThread().getName());
			}
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}

   另外还需要注意一点,执行start()方法的顺序不代表线程启动的顺序.

 

1.2.2 实现Runnable接口

    如果欲创建的线程类已经有一个父类了,这时就不能再继承自Thread类了,因为Java不支持多继承,所以就需要实现Runnable接口来应对这样的情况.

public class MyRunnable implements Runnable{

    @Override
    public void run() {
        System.out.println("运行中!");
    }

    public static void main(String[] args) {
        Runnable runnable = new MyRunnable();
        Thread thread = new Thread(runnable);
        thread.start();
        System.out.println("运行结束");// 运行结束/n 运行中!
    }
}

   使用继承Thread类的方式来开发多线程应用程序在设计上是有局限性的,因为Java是单根继承,不支持多继承.另外需要说明的是,Thread类也实现了Runnable接口,那也就意味着构造函数Thread(Runnable target)不光可以传入Runnable接口的对象,还可以传入一个Thread类的对象,这样做完全可以将一个Thread对象中的run()方法交由其他的线程进行调用.

1.2.3 实例变量与线程安全

    自定义线程类中的实例变量针对其他线程可以有共享与不共享之分,这在多个线程之间进行交互时是很重要的一个技术点.

    (1)不共享数据的情况

public class MyThread extends Thread {
    private int count = 5;
    public MyThread(String name) {
        super();
        this.setName(name);
    }

    @Override
    public void run() {
        super.run();
        while(count > 0) {
            count --;
            System.out.println("由"+this.currentThread().getName()+"计算,count=" + count);
        }
    }

    public static void main(String[] args) {
        Thread t1 = new MyThread("A");
        Thread t2 = new MyThread("B");
        Thread t3 = new MyThread("C");

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

打印结果为:


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

(1)共享数据的情况,意思就是多个线程访问同一个变量

public class MyThread extends Thread {
    private int count = 5;


    public MyThread() {
        super();
    }
    public MyThread(String name) {
        super();
        this.setName(name);
    }

    @Override
    public void run() {
        super.run();
        count--;
        System.out.println("由" + this.currentThread().getName() + "计算,count=" + count);
    }

    public static void main(String[] args) {
        Thread thread = new MyThread();

        Thread t1 = new Thread(thread, "A");
        Thread t2 = new Thread(thread, "B");
        Thread t3 = new Thread(thread, "C");
        Thread t4 = new Thread(thread, "D");
        Thread t5 = new Thread(thread, "E");


        t1.start();
        t2.start();
        t3.start();
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        t4.start();
        t5.start();

    }
}

结果如下:由此可以得知产生了"非线程安全"问题.

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

如果要结果正确,代码改动如下(新添加一个synchronize关键字)

@Override
    public synchronized void run() {
        super.run();
        count--;
        System.out.println("由" + this.currentThread().getName() + "计算,count=" + count);
    }

通过在run方法前加入synchronize关键字,使多个线程在执行run方法时,以排队的方式进行处理.当一个线程调用run前,先判断run方法有没有上锁,如果上锁,说明有其它线程正在调用run方法,必须等待其它线程对run方法调用结束后才可以执行run方法.这样也就实现了排队调用run方法的目的,synchronize可以在任意对象及方法上加锁,而加锁的这段代码称为"互斥区"或"临界区".

    当一个线程想要执行同步方法里面的代码时,线程首先尝试去拿这把锁,如果能拿到这把锁,那么这个线程就可以执行synchronize里面的代码.如果不能拿到这把锁,那么这个线程就会不断地去长度拿这把锁,知道能拿到为止.而且是多线程同时去争抢这把锁.

1.3 currentThread()方法

currentThread()方法可返回代码正在被那个线程调用的信息.

public class CurrentThreadTest extends Thread{
    public CurrentThreadTest() {
        System.out.println("constructor is :" + currentThread().getName());
    }

    @Override
    public void run() {
        super.run();
        System.out.println("run is :" + currentThread().getName());
    }

    public static void main(String[] args) {
        Thread thread = new CurrentThreadTest();
        thread.start();// run is :thread-0
//        thread.run(); // run is :main
    }
}

稍微复杂一点的:

public class CountOperate extends Thread {
    public CountOperate() {
        System.out.println("CountOperate--begin");
        System.out.println("Thread.currentThread().getName()="+Thread.currentThread().getName());
        System.out.println("this.getName()="+this.getName());
        System.out.println("CountOperate--end");
    }

    @Override
    public void run() {
        System.out.println("run--begin");
        System.out.println("Thread.currentThread().getName()="+Thread.currentThread().getName());
        System.out.println("this.getName()="+this.getName());
        System.out.println("run--end");
    }

    public static void main(String[] args) {
        Thread thread = new Thread(new CountOperate());
        thread.setName("Demo");
        thread.start();
    }
}

打印结果如下:

CountOperate--begin
Thread.currentThread().getName()=main
this.getName()=Thread-0
CountOperate--end
run--begin
Thread.currentThread().getName()=Demo
this.getName()=Thread-0
run--end

1.4 isAlive()

    isAlive()的功能是判断该线程是否处于活动状态.活动状态就是线程未停止.当线程处于正在运行或者准备开始运行的状态,就认为线程是存活的.   

public class IsAliveTest extends Thread{

    @Override
    public void run() {
        System.out.println("run: isAlive=" + this.isAlive());// true
    }

    public static void main(String[] args) {
        Thread thread = new IsAliveTest();
        System.out.println("begin: isAlive=" + thread.isAlive());// false
        thread.start();
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
        }
        System.out.println("end: isAlive=" + thread.isAlive());// false

    }
}

1.5 sleep()方法

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

public class MyThread1 extends Thread{

    @Override
    public void run() {
        try {
            System.out.println("run threadName = " + this.currentThread().getName() + " begin");// 1
            Thread.sleep(2000);
            System.out.println("run threadName = " + this.currentThread().getName() + " end");// 2
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {
        MyThread1 myThread = new MyThread1();
        System.out.println("begin = " + System.currentTimeMillis());// 3
//        myThread.start();// 顺序 3,4,1,2
        myThread.run();// 顺序 3,1,2,4
        System.out.println("end = " + System.currentTimeMillis());// 4
    }
}

1.6 getId()方法 

    getId()方法的作用是取得线程的唯一标识.

    

    

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值