欢迎访问悦橙教程(wld5.com),关注java教程。悦橙教程  java问答|  每日更新
页面导航 : > > 文章正文

java学习之路---Java 多线程,

来源: javaer 分享于  点击 30635 次 点评:245

java学习之路---Java 多线程,


概念明确
  • 多线程:指的是这个程序(一个进程)运行时产生了不止一个线程
  • 并行与并发:

并行:多个cpu实例或者多台机器同时执行一段处理逻辑,是真正的同时。
并发:通过cpu调度算法,让用户看上去同时执行,实际上从cpu操作层面不是真正的同时。并发往往在场景中有公用的资源,那么针对这个公用的资源往往产生瓶颈,我们会用TPS或者QPS来反应这个系统的处理能力。

一、线程的生命周期及基本状态

1、生命周期


2、java线程五种基本状态

新建状态(New):当线程对象对创建后,即进入了新建状态。

就绪状态(Runnable):当调用线程对象的start()方法(t.start();),线程即进入就绪状态。处于就绪状态的线程, 只是说明此线程已经做好了准备,随时等待CPU调度执行,并不是说执行了t.start()此线程立即就会执行。

运行状态(Running):当CPU开始调度处于就绪状态的线程时,此线程才得以真正执行,即进入运行状态。就绪状 态是进入运行状态的唯一入口!

阻塞状态(Blocked):处于运行状态的线程由于某种原因,暂时放弃对CPU的使用权,停止执行,此时进入阻塞状 态,直到其进入到就绪状态,才有机会再次被CPU调用以进入到运行状态。

根据阻塞原因不同可分为:

1.等待阻塞:运行状态中的线程执行wait()方法,使本线程进入阻塞状态

2.同步阻塞:线程在获取sysnchronized同步锁失败(因为所被其他线程占用),使本线程进入阻塞状态

3.其他阻塞:通过调用线程的sleep()或join()或发出了I/O请求时,使本线程进入阻塞状态。当sleep()状态   超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新将转入就绪状态

死亡状态(Dead):线程执行完了或者异常退出了run()方法,该线程结束生命周期。


二、java多线程的实现方式

1、继承Thread类实现

继承Thread类的方法尽管被我们列为一种多线程实现方式,但Thread本质上也是实现了Runnable接口的一个实例, 它代表一个线程的实例,并且,启动线程唯一方法就是通过Thread类的start()实力方法。start()方法是一个native方法,它 将启动一个你线程,并执行run()方法。这种方式实现多线程很简单,通过自己的类直接extend Thread,并复写run()方 法,就可以启动新线程并执行自己定义地 run()方法。例如:

public class MyRhread extends Thread {
	public void run() {
		System.out.println("MyThread.run()"
	}
}
在合适的地方启动线程如下:

MyThread mythread1 = new MyThread();
MyThread mythread2 = new MyThread();
myThread1.start();
myThread2.start();
2、实现Runnable接口方式实现多线程

如果自己的类已经extends另一个类,就无法直接extends Thread,此时,必须实现一个Runnable接口。如下:

public class MyThread extends OtherClass implements Runnable {
	public void run() {
		System.out.println("MyThread.run()");
	}
}
为了启动MyThread,需要首先实例化一个Thread,并传入自己的MyThread实例:

MyThread myThread = new MyThread();
Thread thread = new Thread(myThread);
thread.start();
事实上,当传入一个Runnable target参数给Thread后,Thread的run()方法就会调用target.run(),参考JDK源码:

public void run() {
	if (target != null) {
		target.run();
	}
}
3. 使用ExecutorService、Callable、Future实现有返回结果的多线程ExecutorService、Callable、Future这个对象实 际上 都是属于Executor框架中的功能类。想要详细了解Executor框架的可以访问http://www.javaeye.com/topic/36659 1 ,这里面对该框架做了很详细的解释。返回结果的线程是在JDK1.5中引入的新特征,确实很实用,有了这种特征我就不需 要再 为了得到返回值而大费周折了,而且即便实现了也可能漏洞百出。
可返回值的任务必须实现Callable接口,类似的,无返回值的任务必须Runnable接口。执行Callable任务后,可以获 取一个Future的对象,在该对象上调用get就可以获取到Callable任务返回的Object了,再结合线程池接口ExecutorService 就可以实现传说中有返回结果的多线程了。下面提供了一个完整的有返回结果的多线程测试例子,在JDK1.5下验证过没问 题可以直接使用。代码如下:

import java.util.concurrent.*;
import java.util.Date;
import java.util.List;
import java.util.ArrayList;

/**
* 有返回值的线程
*/
@SuppressWarnings("unchecked")
public class Test {
	public static void main(String[] args) throws ExecutionException,
    	InterruptedException {
   		System.out.println("----程序开始运行----");
   		Date date1 = new Date();

   		int taskSize = 5;
   		// 创建一个线程池
   		ExecutorService pool = Executors.newFixedThreadPool(taskSize);
   		// 创建多个有返回值的任务
   		List<Future> list = new ArrayList<Future>();
   		for (int i = 0; i < taskSize; i++) {
    			Callable c = new MyCallable(i + " ");
    			// 执行任务并获取Future对象
    			Future f = pool.submit(c);
    			// System.out.println(">>>" + f.get().toString());
   			 list.add(f);
   		}
   		// 关闭线程池
   		pool.shutdown();

   		// 获取所有并发任务的运行结果
   		for (Future f : list) {
    			// 从Future对象上获取任务的返回值,并输出到控制台
    			System.out.println(">>>" + f.get().toString());
   		}

   		Date date2 = new Date();
   		System.out.println("----程序结束运行----,程序运行时间【"
     		+ (date2.getTime() - date1.getTime()) + "毫秒】");
	}
}

class MyCallable implements Callable<Object> {
	private String taskNum;

	MyCallable(String taskNum) {
   		this.taskNum = taskNum;
	}

	public Object call() throws Exception {
   		System.out.println(">>>" + taskNum + "任务启动");
   		Date dateTmp1 = new Date();
   		Thread.sleep(1000);
   		Date dateTmp2 = new Date();
   		long time = dateTmp2.getTime() - dateTmp1.getTime();
   		System.out.println(">>>" + taskNum + "任务终止");
   		return taskNum + "任务返回运行结果,当前任务时间【" + time + "毫秒】";
	}
}
代码说明:
上述代码中Executors类,提供了一系列工厂方法用于创先线程池,返回的线程池都实现了ExecutorService接口。
public static ExecutorService newFixedThreadPool(int nThreads) 创建固定数目线程的线程池。
public static ExecutorService newCachedThreadPool() 创建一个可缓存的线程池,调用execute 将重用以前构造的线程 (如果线程可用)。如果现有线程没有可用的,则创建一个新线程并添加到池中。终止并从缓存中移除那些已有 60 秒钟未 被使用的线程。
public static ExecutorService newSingleThreadExecutor() 创建一个单线程化的Executor。
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) 创建一个支持定时及周期性的任 务执行的线程池,多数情况下可用来替代Timer类。

ExecutoreService提供了submit()方法,传递一个Callable,或Runnable,返回Future。如果Executor后台线程池还没有完 成Callable的计算,这调用返回Future对象的get()方法,会阻塞直到计算完成。


三、Java多线程的阻塞与线程控制

引起java线程阻塞的主要方法

1、join()

join —— 让一个线程等待另一个线程完成才继续执行。如A线程线程执行体中调用B线程的join()方法,则A线程被阻 塞,知道B线程执行完为止,A才能得以继续执行。

public class ThreadTest {

    public static void main(String[] args) {

        MyRunnable myRunnable = new MyRunnable();
        Thread thread = new Thread(myRunnable);

        for (int i = 0; i < 100; i++) {
            System.out.println(Thread.currentThread().getName() + " " + i);
            if (i == 30) {
                thread.start();
                try {
                    thread.join();    // main线程需要等待thread线程执行完后才能继续执行
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

class MyRunnable implements Runnable {

    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println(Thread.currentThread().getName() + " " + i);
        }
    }
}
2、sleep()

sleep —— 让当前的正在执行的线程暂停指定的时间,并进入阻塞状态。在其睡眠的时间段内,该线程由于不是处于 就绪状态,因此不会得到执行的机会。即使此时系统中没有任何其他可执行的线程,出于sleep()中的线程也不会执行。因 此sleep()方法常用来暂停线程执行。

当调用了新建的线程的start()方法后,线程进入到就绪状态,可能会在接下来的某个时间获取CPU时间片得以执行, 如果希望这个新线程必然性的立即执行,直接调用原来线程的sleep(1)即可

public class ThreadTest {

    public static void main(String[] args) {

        MyRunnable myRunnable = new MyRunnable();
        Thread thread = new Thread(myRunnable);

        for (int i = 0; i < 100; i++) {
            System.out.println(Thread.currentThread().getName() + " " + i);
            if (i == 30) {
                thread.start();
                try {
                    Thread.sleep(1);   // 使得thread必然能够马上得以执行
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

class MyRunnable implements Runnable {

    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println(Thread.currentThread().getName() + " " + i);
        }
    }
}
注:睡一个毫秒级就够了,因为CPU不会空闲,会切换到新建的线程。

3、后台线程(Deamon Thread)

概念/目的:后台线程主要是为其他线程(相对可以称之为前台线程)提供服务,或“守护线程”。如JVM中的垃圾回收线程。

生命周期:后台线程的生命周期与前台线程生命周期有一定关联。主要体现在:当所有的前台线程都进入死亡状态时,后台线程会自动死亡

设置后台线程:调用Thread对象的setDaemon(true)方法可以将指定的线程设置为后台线程。

public class ThreadTest {

    public static void main(String[] args) {
        Thread myThread = new MyThread();
        for (int i = 0; i < 100; i++) {
            System.out.println("main thread i = " + i);
            if (i == 20) {
                myThread.setDaemon(true);
                myThread.start();
            }
        }
    }

}

class MyThread extends Thread {

    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println("i = " + i);
            try {
                Thread.sleep(1);
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
    }
}

判断线程是否是后台线程:调用thread对象的isDeamon()方法。

注:main线程默认是前台线程,前台线程创建中创建的子线程默认是前台线程,后台线程中创建的线程默认是后台线程。调用

setDeamon(true)方法将前台线程设置为后台线程时,需要在start()方法调用之前。前天线程都死亡后,JVM通知后台线程死亡 ,但从 接收指令到作出响应,需要一定的时间。

4、改变线程优先级/setPriority():

每个线程在执行时都具有一定的优先级,优先级高的线程具有较多的执行机会。每个线程默认的优先级都与创建它的线程的优先级相同 。main线程默认具有普通优先级。

设置线程优先级:setPriority(int priorityLevel)。参数priorityLevel范围在1-10之间,常用的有如下三个静态常量值:

MAX_PRIORITY:10

MIN_PRIORITY:1

NORM_PRIORITY:5

获取线程优先级:getPriority()。

注:具有较高线程优先级的线程对象仅表示此线程具有较多的执行机会,而非优先执行。

public class ThreadTest {

    public static void main(String[] args) {
        Thread myThread = new MyThread();
        for (int i = 0; i < 100; i++) {
            System.out.println("main thread i = " + i);
            if (i == 20) {
                myThread.setPriority(Thread.MAX_PRIORITY);
                myThread.start();
            }
        }
    }

}

class MyThread extends Thread {

    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println("i = " + i);
        }
    }
}
5、线程让步:yield()

yield()方法还与线程优先级有关,当某个线程调用yiled()方法从运行状态转换到就绪状态后,CPU从就绪状态线程队列中只会选择与 该线程优先级相同或优先级更高的线程去执行。

public class ThreadTest {

    public static void main(String[] args) {
        Thread myThread1 = new MyThread1();
        Thread myThread2 = new MyThread2();
        myThread1.setPriority(Thread.MAX_PRIORITY);
        myThread2.setPriority(Thread.MIN_PRIORITY);
        for (int i = 0; i < 100; i++) {
            System.out.println("main thread i = " + i);
            if (i == 20) {
                myThread1.start();
                myThread2.start();
                Thread.yield();
            }
        }
    }

}

class MyThread1 extends Thread {

    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println("myThread 1 --  i = " + i);
        }
    }
}

class MyThread2 extends Thread {

    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println("myThread 2 --  i = " + i);
        }
    }
}


相关文章

    暂无相关文章
相关栏目:

用户点评