java 多线程
进程即为程序,一个程序可以同时拥有多个操作,多个操作即为此进程的线程;
线程是比进程效率更快的处理单元,且资源占有较少;
线程运行在进程的基础之上,main()是主进程,所有线程的起点为run方法;
线程的状态 (http://www.importnew.com/21136.html)
在正式学习Thread类中的具体方法之前,我们先来了解一下线程有哪些状态,这个将会有助于后面对Thread类中的方法的理解。
创建(new)状态: 准备好了一个多线程的对象
就绪(runnable)状态: 调用了start()方法, 等待CPU进行调度
运行(running)状态: 执行run()方法
阻塞(blocked)状态: 暂时停止执行, 可能将资源交给其它线程使用
终止(dead)状态: 线程销毁
当需要新起一个线程来执行某个子任务时,就创建了一个线程。但是线程创建之后,不会立即进入就绪状态,因为线程的运行需要一些条件(比如内存资源,在前面的JVM内存区域划分一篇博文中知道程序计数器、Java栈、本地方法栈都是线程私有的,所以需要为线程分配一定的内存空间),只有线程运行需要的所有条件满足了,才进入就绪状态。
当线程进入就绪状态后,不代表立刻就能获取CPU执行时间,也许此时CPU正在执行其他的事情,因此它要等待。当得到CPU执行时间之后,线程便真正进入运行状态。
线程在运行状态过程中,可能有多个原因导致当前线程不继续运行下去,比如用户主动让线程睡眠(睡眠一定的时间之后再重新执行)、用户主动让线程等待,或者被同步块给阻塞,此时就对应着多个状态:time waiting(睡眠或等待一定的事件)、waiting(等待被唤醒)、blocked(阻塞)。
当由于突然中断或者子任务执行完毕,线程就会被消亡。
下面这副图描述了线程从创建到消亡之间的状态:
在有些教程上将blocked、waiting、time waiting统称为阻塞状态,这个也是可以的,只不过这里我想将线程的状态和Java中的方法调用联系起来,所以将waiting和time waiting两个状态分离出来。
多线程的实现
Java 拥有三种途径实现多线程操作;
继承Thread类
Thread 实现了 Runnable 接口
要复写run()方法,调用时为start()方法,而且所有线程只能调用一次;
public
class Thread implements Runnable {
/* Make sure registerNatives is the first thing <clinit> does. */
private static native void registerNatives();
static {
registerNatives();
}
...
public synchronized void start() {
if (threadStatus != 0)
throw new IllegalThreadStateException(); //抛出异常
// IllegalThreadStateException if the thread was already started.
group.add(this);
boolean started = false;
try {
start0();
started = true;
} finally {
try {
if (!started) {
group.threadStartFailed(this);
}
} catch (Throwable ignore) {
/* do nothing. If start0 threw a Throwable then
it will be passed up the call stack */
}
}
}
private native void start0(); //native JNI技术
// 线程能够执行,需要操作系统进行资源分配,故由JVM根据操作方式实现的
public void run() {
if (target != null) {
target.run();
}
}
为何要调用start()方法,而不是run()方法,下面看一个例子:
package StringBase;
import java.util.LinkedList;
public class Base extends Thread{
private String name;
public Base(String name){
this.name=name;
}
public void run(){
System.out.println("name"+this.name+" "+Thread.currentThread().getId());
}
}
package StringBase;
public class Test {
public static void main(String[] args){
System.out.println(Thread.currentThread().getId());
Base a=new Base("线程1");
Base b=new Base("线程2");
a.start();
b.start();
}
}
----
1 //主线程的ID
name线程2 14 分线程
name线程1 13 分线程
从输出结果可以得出以下结论:
1)线程1和线程2的线程ID不同,和主线程ID不相同,说明通过run方法调用并不会创建新的线程,而是在主线程中直接运行run方法,跟普通的方法调用没有任何区别,其次主线程的名字为 main();
2)虽然线程1的start方法调用在线程2的run方法前面调用,但是先输出的是线程2的run方法调用的相关信息,说明新线程创建的过程不会阻塞主线程的后续执行。
实现Runnable接口
Thread存在一个问题,java是单继承问题,故通过继承多接口来实现;
写一个类继承Runnable接口,然后复写run()方法;
注意:调用多线程必须使用Thread的start()方法,其内部构造函数可以来调用start()
-----
public Thread(Runnable target) {
init(null, target, "Thread-" + nextThreadNum(), 0);
}
------
public interface Runnable {
/**
* When an object implementing interface <code>Runnable</code> is used
* to create a thread, starting the thread causes the object's
* <code>run</code> method to be called in that separately executing
* thread.
* <p>
* The general contract of the method <code>run</code> is that it may
* take any action whatsoever.
*
* @see java.lang.Thread#run()
*/
public abstract void run();
}
package StringBase;
import java.util.LinkedList;
public class Base implements Runnable {
private String num;
public Base(String num){
this.num=num;
}
public void run(){
for(int i=0;i<12;i++)
System.out.println(this.num+"主动创建的第"+i+"个线程");
}
public static void main(String[] args){
Base a=new Base("线程1");
Base b=new Base("线程2");
Base c=new Base("线程3");
new Thread(a).start();
new Thread(b).start();
new Thread(c).start();
}
}
Thread 和 Runnable 的区别:
1、Thread类有单继承的定义局限,且Thread实现了Runnable接口;
2、Runnable 接口实现多线程,可以更加清楚的描述数据共享;
Callable接口
使用ExecutorService、Callable、Future实现有返回结果的多线程;
可返回值的任务必须实现Callable接口,类似的,无返回值的任务必须Runnable接口。执行Callable任务后,可以获取一个Future的对象,在该对象上调用get就可以获取到Callable任务返回的Object了,再结合线程池接口ExecutorService就可以实现传说中有返回结果的多线程了。
@FunctionalInterface //函数式接口
public interface Callable<V> {
/**
* Computes a result, or throws an exception if unable to do so.
*
* @return computed result
* @throws Exception if unable to compute a result
*/
V call() throws Exception;
}
package StringBase;
import java.util.LinkedList;
import java.util.concurrent.Callable;
public class Base implements Callable<String> {
private int num=10;
public String call(){
while (num>0){
System.out.println("买票"+num--);
}
return "买完了";
}
}
package StringBase;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
public class Test {
public static void main(String[] args) throws Exception{
Base a=new Base();
Base b= new Base();
FutureTask<String> s=new FutureTask<String>(a); //为了获取call()对象
FutureTask<String> t=new FutureTask<String>(b); //为了获取call()对象
// FutureTask 是Runnable 的接口子类;
new Thread(s).start(); //开始多线程
new Thread(t).start();
System.out.println("a"+s.get()); //返回操作结果
System.out.println("b"+s.get());
}
}
多线程的常用方法
1、线程的命名与获取
线程的运行结果顺序是不可预测,因为它会抢占系统运行资源;
线程的命名可以通过以下方法:
构造方法:
public Thread(Runnable target, String name) {
init(null, target, name, 0);
}
setName()方法
public final synchronized void setName(String name) {
checkAccess();
if (name == null) {
throw new NullPointerException("name cannot be null");
}
this.name = name;
if (threadStatus != 0) {
setNativeName(name);
}
}
每一个JVM进程启动的时候,至少启动几个线程:
1、main() 主线程,负责程序的主线程以及启动子线程;
2、GC线程,负责垃圾清理;
线程的休眠
休眠,是指让线程的执行速度稍微慢一点;
public static native void sleep(long millis) throws InterruptedException;
//打断休眠,会抛出一个异常
package StringBase;
import java.util.LinkedList;
import java.util.concurrent.Callable;
public class Base implements Runnable {
private int num=10;
public void run(){
while (num>0){
try {
Thread.sleep(1000);
}
catch (InterruptedException e){
e.printStackTrace();
}
System.out.println("买票"+num--);
}
}
public static void main(String args[]){
Base a=new Base();
new Thread(a).start();
}
}
默认情况下,若在休眠的状态下,设置多线程将一起进入(按照先后顺序)休眠状态;
package StringBase;
import java.util.LinkedList;
import java.util.concurrent.Callable;
public class Base implements Runnable {
private int num=10;
public void run(){
while (num>0){
try {
Thread.sleep(1000);
}
catch (InterruptedException e){
e.printStackTrace();
}
System.out.println("买票"+num--);
}
}
public static void main(String args[]){
Base a=new Base();
Base b=new Base();
Base c=new Base();
Base d=new Base();
new Thread(a).start();
new Thread(b).start();
new Thread(c).start();
new Thread(d).start();
}
}
-----
买票10
买票10
买票10
买票10
//休眠。。。
买票9
买票9
买票9
买票9
买票8
买票8
买票8
...
线程优先级
所谓的优先级为,优先级越高的线程,越有可能先执行;
优先级的设置和取得:
public final void setPriority(int newPriority) {
ThreadGroup g;
checkAccess();
if (newPriority > MAX_PRIORITY || newPriority < MIN_PRIORITY) {
throw new IllegalArgumentException();
}
if((g = getThreadGroup()) != null) {
if (newPriority > g.getMaxPriority()) {
newPriority = g.getMaxPriority();
}
setPriority0(priority = newPriority);
}
}
---
public final int getPriority() {
return priority;
}
优先级的级别
public static final int MIN_PRIORITY = 1;
/**
* The default priority that is assigned to a thread.
*/
public static final int NORM_PRIORITY = 5;
/**
* The maximum priority that a thread can have.
*/
public static final int MAX_PRIORITY = 10;
线程优先级特性:
继承性
比如A线程启动B线程,则B线程的优先级与A是一样的。
规则性
高优先级的线程总是大部分先执行完,但不代表高优先级线程全部先执行完。
随机性
优先级较高的线程不一定每一次都先执行完。
线程的同步与死锁
不同的线程操作了同一个资源,这就需要线程的同步;(比如两个人同时取一张卡的存钱);
package StringBase;
import java.util.LinkedList;
import java.util.concurrent.Callable;
public class Base implements Runnable {
private int num=6;
public void run(){
while (num>0){
System.out.println(Thread.currentThread().getName()+"买票"+num--);
}
}
public static void main(String args[]){
Base a=new Base();
new Thread(a,"票贩子A").start();
new Thread(a,"票贩子B").start();
new Thread(a,"票贩子C").start();
new Thread(a,"票贩子D").start();
}
}
-----
票贩子B买票5
票贩子B买票2
票贩子B买票1
票贩子C买票4
票贩子D买票3
票贩子A买票6
-----------------------------------
package StringBase;
import java.util.LinkedList;
import java.util.concurrent.Callable;
public class Base implements Runnable {
private int num=6;
public void run(){
while (num>0){
try {
Thread.sleep(100);
}catch (InterruptedException e){
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"买票"+num--);
}
}
public static void main(String args[]){
Base a=new Base();
new Thread(a,"票贩子A").start();
new Thread(a,"票贩子B").start();
new Thread(a,"票贩子C").start();
new Thread(a,"票贩子D").start();
}
}
------
票贩子C买票5
票贩子A买票6
票贩子D买票4
票贩子B买票5
票贩子C买票3
票贩子A买票2
票贩子D买票1
票贩子B买票0
票贩子C买票-1
票贩子A买票-2
同步的操作:
问题所在点:判断与修改使异步完成的,不同线程都可以进行操作;
故:
源码的实现:
使用 synchronized 关键字:
同步代码块
同步方法
(代码块(构造代码块,普通代码库,静态代码块,同步代码块))
package StringBase;
import java.util.LinkedList;
import java.util.concurrent.Callable;
public class Base implements Runnable {
private int num=10;
public void run(){
for(int i=0;i<20;i++){
this.sale();
}
}
public synchronized void sale(){
if (num>0){
try {
Thread.sleep(150);
}catch (InterruptedException e){
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"买票"+num--);
}
}
public static void main(String args[]){
Base a=new Base();
new Thread(a,"票贩子A").start();
new Thread(a,"票贩子B").start();
new Thread(a,"票贩子C").start();
new Thread(a,"票贩子D").start();
}
}
-----
票贩子A买票9
票贩子D买票8
票贩子C买票7
票贩子C买票6
票贩子C买票5
票贩子B买票4
票贩子B买票3
票贩子B买票2
票贩子C买票1
同步操作的执行速度要低于异步操作,但安全性较高;
但线程同步过多,就会造成死锁;
Object 类
等待:wait()
public final native void wait(long timeout) throws InterruptedException;
唤醒第一个等待:
public final native void notify();
唤醒所有等待:
public final native void notifyAll();
注:sleep和wait的区别:
sleep是Thread类的方法,wait是Object类中定义的方法.
sleep可以设置休眠时间,wait类不用,但是需要唤醒机制notify()