JAVA并行-1.Java线程操作

1.进程和线程

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

2.Java中的线程操作

2.1新建线程

2.1.2Thread线程类

  • 关键字new创建一个线程对象,然后调用对象的start()方法:
Thread t1 = new Thread();
t1.start();
  • 线程对象Thread有一个run()方法,start()方法会新建一个线程并让这个线程执行run()方法。

  • 直接使用run()方法,会在当前线程中串行调用run()方法。

2.1.3Runnable接口

也可以用Runnable接口新建线程,它只有一个run()方法,而且默认的Thread.run()就是调用内部的Runnable接口,因此使用Runnable更合理。

public interface Runnable {
    public abstract void run();
}

Thread类有一个构造方法:

public Thread(Runnable target)

默认的Thread.start()方法调用的时候,新的线程就会执行Runnable.run()方法:

public void run(){
    if(target != null){
        target.run();
    }
}

以下代码实现Runnable接口,并将该实例传入Thread,避免重载Thread.run():

public class CreateThread implements Runnable{
    public static void main(String[] args){
        Thread t1 = new Thread(new CreateThread());
        t1.start();
    }

    @Override
    public void run(){
        System.out.println("Here is a Runnable!");
    }
}

2.2 终止线程

一般来说,线程在执行完成之后就会结束。但是也可以手动关闭线程。

2.2.1Thread.stop()

  • Thread.stop()方法可以结束线程,但是是直接终止线程,并立即释放这个线程所持有的锁。
  • 该方法会导致数据不一致的问题,因此已经被标注为废弃,不要使用。

例如,以下代码没有错误信息,但是结果不一致了:

public class StopThreadUnsafe {
    public static User u = new User();

    public static class User {
        int id;
        String name;

        public User() {
            id = 0;
            name = "0";
        }

        public int getId() {
            return id;
        }

        public void setId(int id) {
            this.id = id;
        }

        public String getName() {
            return name;
        }

        public void setName(String name) {
            this.name = name;
        }

        @Override
        public String toString() {
            return "User [id = " + id + ", name = " + name + "]";
        }
    }

    public static class ChangeObjectThread extends Thread {
        @Override
        public void run() {
            while (true) {
                synchronized (u) {
                    int v = (int) (System.currentTimeMillis() / 1000);
                    u.setId(v);
                    // do something else
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    u.setName(String.valueOf(v));
                }
                Thread.yield();
            }
        }
    }

    public static class ReadObjectThread extends Thread {
        @Override
        public void run() {
            while (true) {
                synchronized (u) {
                    if (u.getId() != Integer.parseInt(u.getName())) {
                        System.out.println(u.toString());
                    }
                }
                Thread.yield();
            }
        }
    }

    public static void main(String[] args) throws InterruptedException {
        new ReadObjectThread().start();
        while (true) {
            ChangeObjectThread t = new ChangeObjectThread();
            t.start();
            Thread.sleep(150);
            t.stop();
        }
    }
}

如果要安全的停止一个线程,可以自己决定停止线程的时机,例如将前例中的CreateObjectThread线程中增加一个stopMe()方法:

public static class ChangeObjectThread extends Thread {
        volatile boolean stopme = false;
        
        public void stopMe() {
            stopme = true;
        }
        
        @Override
        public void run() {
            while (true) {
                if(stopme) {
                    System.out.println("exit by stop me");
                    break;
                }
                synchronized (u) {
                    int v = (int) (System.currentTimeMillis() / 1000);
                    u.setId(v);
                    // do something else
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    u.setName(String.valueOf(v));
                }
                Thread.yield();
            }
        }
    }

2.3 中断线程

  • 中断线程并不会使线程立即退出,而是给线程发送通知,线程接到通知后的操作由线程执行决定。
  • JVM中于线程中断有关的方法如下:
    • Thread.interrupt(),是一个实例方法,通知线程中断,即设置中断标志位。中断标志位表示当前线程已经被中断了。
    • Thread.isInterrupted(),也是一个实例方法,判断当前线程是否有中断(通过判断中断标志位)
    • Thread.interrupted(),是一个静态方法,用来判断当前线程的中断状态,同时清除当前线程的中断状态。
  • 方法签名为:
public void Thread.interrupt()  //中断线程
public boolean Thread.isInterrupted()  //判断线程是否中断
public static boolean Thread.interrupted() //判断是否中断,并清除当前中断状态

注意:中断后需要增加中断处理代码,不然中断不会发生作用。

Thread.sleep()函数

在循环体中,出现了sleep()或者wait()等操作,需要通过中断来识别。wait()在下一小节介绍,这里介绍sleep()方法。
Thread.sleep()函数的作用是让当前线程休眠若干时间,其函数签名为:

public static native void sleep(long millis) throws InterruptedException

它会抛出一个InterruptedException异常,不是运行时异常,程序必须捕获并处理他。当线程在sleep()休眠中被中断,这个异常就会产生。
注意:Thread.sleep()方法因为中断抛出异常时,会清除中断标记,如果不加处理,在下一次循环开始时,就无法捕捉这个中断,所以在异常处理中需要再次设置中断标记位。

2.4 等待(wait)和通知(notify)

  • 等待(wait)方法和通知(notify)方法是为了支持多线程的协作而存在的。
  • 这两个方法是在Object类中,即任何对象都能调用这两个方法。
  • Object.wait()方法不能随便调用,需要包含在对应的synchronzied语句中。
  • wait()和notify()方法都需要首先获得目标对象的一个监视器
  • Object.wait()和Thread.sleep()方法都能让线程等待若干时间,区别为:
    • wait()方法可以被唤醒,sleep()方法需要等待时间结束
    • wait()方法会释放目标对象的锁,而sleep()方法不会释放任何资源
  • 其方法签名为:
public final void wait() throws InterruptedException
public final native void notify()

线程调用object.wait()方法,它会进入到object的等待队列。当object.notify()方法被调用时,对象会在线程队列中,随机选择一个线程,将其唤醒。
注意:这个选择是非公平的,完全随机。
使用例子:

package temp;

public class SimpleWN {
    final static Object object = new Object();
    public static class T1 extends Thread{
        public void run() {
            //获得object对象锁
            synchronized(object) {
                System.out.println(System.currentTimeMillis() + ": T1 Start!");
                try {
                    System.out.println(System.currentTimeMillis() + ": T1 is wait for obejct!");
                    // 释放object的对象锁
                    object.wait();
                }
                catch(InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(System.currentTimeMillis() + ": T1 is end");
            }
        }
    }
    
    public static class T2 extends Thread {
        public void run() {
            synchronized(object) {
                System.out.println(System.currentTimeMillis() + ": T2 Start!");
                System.out.println("notify one thread!");
                // 释放object对象锁
                object.notify();
                System.out.println(System.currentTimeMillis() + ": T2 end!");
                try {
                    // 休眠结束之后,才释放对象锁,T1才能继续执行
                    Thread.sleep(2000);
                }
                catch(InterruptedException e) {
                    e.printStackTrace();
                }
                
            }
        }
    }
    
    public static void main(String[] args) {
        Thread t1 = new T1();
        Thread t2 = new T2();
        t1.start();
        t2.start();
    }
}

2.5 挂起(suspend)和继续执行(resume)线程

  • 被挂起(suspend)的线程,需要等到resume()操作后才能继续执行
  • 这两个方法已经标注为废弃,不建议使用
  • 废弃的原因是suspend()方法在导致线程暂停的同时,并不会释放任何锁资源,其他任何要范围被它暂时使用的锁,都无法正常运行。而被挂起的线程状态仍然是Runnable,影响对系统状态的判断。

以下例子会导致系统锁死:

public class BadSuspend {
    public static Object u = new Object();
    static ChangeObjectThread t1 = new ChangeObjectThread("t1");
    static ChangeObjectThread t2 = new ChangeObjectThread("t2");
    
    public static class ChangeObjectThread extends Thread{
        public ChangeObjectThread(String name) {
            super.setName(name);
        }
        
        @Override
        public void run() {
            synchronized(u) {
                System.out.println("in " + getName());
                Thread.currentThread().suspend();
            }
        }
    }
    
    public static void main(String[] args) throws InterruptedException{
        // t1
        t1.start();
        Thread.sleep(100);
        t2.start();
        t1.resume();
        // 此时t2的状态依然是RUNNABLE
        t2.resume();
        t1.join();
        t2.join();
    }
}

为了达到相同的目的,可以用如下方法(使用wait()和notify()):

public class GoodSuspend {
    public static Object u = new Object();
    
    public static class ChangeObjectThread extends Thread{
        // 标记变量,表明线程是否被挂起
        volatile boolean suspendme = false;

        public void suspendMe() {
            suspendme = true;
        }
        
        public void resumeMe() {
            suspendme = false;
            synchronized (this){
                notify();
            }
        }
        
        @Override
        public void run() {
            while(true) {
                synchronized(this) {
                    // 检查线程是否被挂起
                    while(suspendme) {
                        try{
                            wait();
                        }
                        catch(InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }
                synchronized(u) {
                    System.out.println("in ChangeObjectThread");
                }
                Thread.yield();
            }
        }
    }
    
    public static class ReadObjectThread extends Thread{
        @Override
        public void run() {
            while(true) {
                synchronized(u) {
                    System.out.println("in ReadObjectThread");
                }
                Thread.yield();
            }
        }
    }
    
    public static void main(String[] args) throws InterruptedException{
        // 实例化线程对象
        ChangeObjectThread t1 = new ChangeObjectThread();
        ReadObjectThread t2 = new ReadObjectThread();
        // 运行线程对象
        t1.start();
        t2.start();
        Thread.sleep(1000);
        // 挂起t1线程
        t1.suspendMe();
        System.out.println("suspend t1 2 sec");
        Thread.sleep(2000);
        System.out.println("resume t1");
        t1.resumeMe();
    }
}

2.6 等待线程结束(join)和谦让(yield)

  • join签名如下,有两种:
// 阻塞当前线程,直到目标线程执行完毕
public final void join() throws InterruptedException

 // 最多等待millies毫秒,之后继续执行
public final synchronised void join(long millis) throws InterruptedException
  • join 的本质是调用线程wait()方法在当前线程对象实例上,它使得调用线程在当前线程对象上等待,被等待的线程会在调用结束前调用notifyAll()通知所有等待线程继续执行。JDK中join()实现的核心代码为:
while(isAlive()){
  wati(0);
}

基础join()例子:

package temp;

public class JoinMain {
    public volatile static int i = 0;

    public static class AddThread extends Thread {
        @Override
        public void run() {
            for (; i < 1000000; i++)
                ;
        }
    }

    public static void main(String[] args) throws InterruptedException {
        AddThread at = new AddThread();
        System.out.println(i);
        at.start();
        System.out.println(i);
        at.join();
        // join()保证最后输出的是1000000
        System.out.println(i);
    }
}
  • yield()方法会使当前线程让出CPU,重新进行资源分配。
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,402评论 6 499
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,377评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,483评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,165评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,176评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,146评论 1 297
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,032评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,896评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,311评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,536评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,696评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,413评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,008评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,659评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,815评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,698评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,592评论 2 353

推荐阅读更多精彩内容

  • 本文主要讲了java中多线程的使用方法、线程同步、线程数据传递、线程状态及相应的一些线程函数用法、概述等。 首先讲...
    李欣阳阅读 2,454评论 1 15
  • Java多线程学习 [-] 一扩展javalangThread类 二实现javalangRunnable接口 三T...
    影驰阅读 2,957评论 1 18
  • 该文章转自:http://blog.csdn.net/evankaka/article/details/44153...
    加来依蓝阅读 7,350评论 3 87
  • 写在前面的话: 这篇博客是我从这里“转载”的,为什么转载两个字加“”呢?因为这绝不是简单的复制粘贴,我花了五六个小...
    SmartSean阅读 4,730评论 12 45
  • 春风一夜入校园, 黄叶万千侵古道。 秋天的节奏啊
    云中波阅读 242评论 0 0