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(阻塞)。
当由于突然中断或者子任务执行完毕,线程就会被消亡。
下面这副图描述了线程从创建到消亡之间的状态:

2

在有些教程上将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()

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,457评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,837评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,696评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,183评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,057评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,105评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,520评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,211评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,482评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,574评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,353评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,213评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,576评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,897评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,174评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,489评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,683评论 2 335

推荐阅读更多精彩内容