Java多线程--线程及相关的Java API

Java多线程--线程及相关的Java API

线程与进程

进程是线程的容器,程序是指令、数据的组织形式,进程是程序的实体。

一个进程中可以容纳若干个线程,线程是轻量级的进程,是程序执行的最小单位。我们研究多线程而不是多进程,因为线程之间的切换与调度的成本远小于进程。

线程的生命周期

public enum State {
    NEW,
    RUNNABLE,
    BLOCKED,
    WAITING,
    TIMED_WATING,
    TERMINATED;
}

线程的所有状态都在枚举类State中定义了,其中:

NEW表示刚刚创建的线程,此时线程还没有开始执行。调用start()后线程进入RUNNABLE状态,线程在执行过程中遇到synchronized同步块,就进入BLOCKED阻塞状态,此时线程暂停执行,直到获得请求的锁。WAITING和TIMED_WAITING都表示等待,区别是前者是无时间限制的等待,后者是有时限的等待。等待可以是执行wait()方法后等待notify()方法将其唤醒,也可以是通过join()方法等待的线程等待目标线程的执行结束。一旦等待了期望事件,线程再次执行,从等待状态变成RUNNABLE状态。线程执行结束后,进入TERMINATED状态。

Java中的线程API

线程的新建

Thread t = new Thread();
t.start();

如上就创建了一个线程,要想这个线程创建后做点什么,需要覆写Thread类中的run()方法。

Thread t = new Thread();
t.run();

不能使用上面的方式启动线程,这样调用不会产生任何新的线程,只是一个单纯的方法调用。要想开启线程,不能使用run(),而应该用start()

还可以通过实现Runnable接口,覆写接口中的run()方法,然后在Thread的构造函数中传入Runnale,此方法更为常用。

new Thread(new Runnale() {
    @Override
    public void run() {}
}).start();

由于run()是Runnable接口中的唯一方法,在Java8种可以简单写成:

new Thread(() -> {}).start();

线程的终止

Thread类中有个stop()方法,但是已经被废弃。该方法太过暴力,强行把执行到一半的线程终止掉,可能会引起数据不一致的问题。stop()方法会直接终止线程,并立即释放这个线程持有的锁,而锁恰好是用来维持对象的一致性的。

举个例子User类中有id和name两个字段,一开始id = 1, name = "ming", 我们要set数据id = 2, name = "jun"更新user,若刚写入了id = 2,name还没来得及写入就stop()的话,立即释放锁,因此其他线程得到锁读取这个对象的话,就会得到id = 2, name = "ming",这不是我们想要的结果,数据已经被写坏了!

那么要如何终止线程呢?可以自定义退出线程的方法,比如设置标志位

volatile boolean stoped;
public void myStop() {
    stoped = true;
}

在线程合适的地方, 通过判断stoped变量来手动退出线程。

@Override
public void run() {
    while (true) {
        if (stoped) {
            break;
        }
    }
    // synchronized (object) {do sth.}
}

线程的中断

线程中断不会使线程立即退出,而是通知目标线程“你应该退出了”,退不退出是由目标线程来决定的。

Thread类有个实例interrupt()方法,它通知目标线程中断,也就是设置中断标志位

另一个实例方法isInterrupt()用于判断当前线程是否被中断(通过检查中断标志位);

静态方法Thread.interrupted()也可以判断当前线程的中断状态,但同时会清除当前线程的中断标志位

wait()和notify()

wait()是Object的方法,也就是说每个对象都能调用。在当前线程中某个对象调用了wait()方法则该线程就停止执行,转为等待状态。举个例子,在线程T中,调用了obj.wait(),线程T就进入了对象obj的等待队列,则线程T就在对象obj上等待,一直等到其他线程调用了obj.notify(),这时obj从它的等待队列中随机唤醒一个线程(不一定是刚才的线程T)。所以notify()方法唤醒的选择不是公平的,不是说先等待的线程就一定会先被唤醒。

Object类还有一个notifyAll()方法,会唤醒某对象等待队列中的所有线程

notify()wait()方法在执行前必须获得对象的锁。为此在synchronized (obj)中先获得了锁,才能调用wait()或者notify()方法,wait()会立刻释放锁,而notify()不会立刻释放锁,wait()状态的线程也不能立刻获得锁;等到执行notify()的线程退出同步块后,才释放锁,此时其他处于wait()状态的线程才能获得该锁。

线程的挂起和继续执行

Thread类中还有两个已经被废弃的方法,分别是suspend()resume()

suspend()在导致线程暂停的同时,不会释放任何锁,而且线程状态还是Runnable,其他任何线程都得不到被该线程占用的锁,直到在线程上执行了resume(),被挂起的线程才能继续,其他阻塞在相关锁上的线程也才可以继续执行。但是如如resume()不小心在suspend()之前调用了,可能造成线程被永久挂起,从而永久占用锁。

注意和notify()wait()方法的区别。由于方法已被废弃,不再讨论更多。

join()和yield()

join()表示将线程“加入”到当前线程中,会一直阻塞当前线程,直到该线程执行完毕,或者说:当前线程一直等待join入的线程执行完毕后,才能继续执行。

public class Abc {
    public static volatile int i;
    public static void main(String[] args) throws InterruptedException {
        Thread t = new Thread(() -> {for (i = 0; i < 100; i++);});
        t.start();
        t.join();
        System.out.println(i);
    }
}

举个例子,上面的代码中有main线程和t线程,由于线程t的join,使得main线程中System.out.println(i)的一定是等待t线程执行完毕后才能执行,因此打印值一定是100.

join()的实现依赖于wait()A线程中调用了B线程的join方法,则相当于A线程调用了B线程的wait方法,在调用了B线程的wait方法后,A线程就会进入阻塞状态。所以A线程会等待B执行完毕后才能继续执行。

yield()表示主动礼让当前线程的CPU使用权,让出不是会不会回来参与CPU资源的争夺,下次还能被分配到。调用该方法,好比是当前线程说:我的工作暂时做完,给其他线程一些工作机会。

volatile关键字

volatile可以确保变量的可见性、有序性,不保证复杂操作的原子性。所以volatite对于原子性操作只能说有一定的帮助,但不能取代锁

当用volatile修饰一个变量时,相当于告诉虚拟机,这个变成极有可能被某些线程修改,确保该变量在被修改后,其他线程能“看到”该变量已经被改变。

synchronized

上面说了volatile不能真正保证线程安全,只能保证一个线程修改了数据后,其他线程能看到这个改变。两个线程同时修改同一个共享变量时,仍然会产生冲突。

为了保证操作的原子性,可以使用同步块,synchronized

被synchronized修饰的方法,或被其包起来的代码段,任何时候只有一个线程可以执行。这就避免了多个线程修改同一个数据的危险。synchronized可以实现线程间的同步,主要是对被同步的代码加锁。

Dog dog = new Dog();

  • 指定的加锁对象,如synchronized (dog) {}或者synchronized (Dog.class) {}
  • 作用于实例方法上,如public synchronized void fun() {},此时使用的锁是当前实例
  • 作用于静态方法,如public static synchronized void fun() {},此时使用的锁是当前

注意:为了保证线程间同步,使得在多个线程间修改同一个数据时,保证任何时候只有一个线程能修改。多个线程需要使用同一把锁

因为synchronized中的代码任何时候只能有一个线程在执行它,所以可以说被synchronized限制的多个线程是串行执行的。

线程的其他概念

线程组

ThreadGroup类可以容纳多个线程,可以将多个功能相同的线程放入线程组中,便于管理。

守护线程(Daemon)

守护线程用于守护用户进程。守护进程在后台默默完成一些系统性的服务,比如垃圾回收线程、JIT线程就可理解成守护线程。而用户线程是用于完成程序的业务操作的,当程序中的所有用户线程执行完毕,守护线程没有了可守护的对象,程序自然就退出。即:在一个Java程序中只有守护线程时,虚拟机就自然退出。

public static void main(String[] args) {
    Thread t = new Thread(() -> {
        while (true) {
        }
    });
    t.setDaemon(true);
    t.start();
    System.out.println("over");
}

在上面的代码中,线程t被设置成守护线程(必须在start()之前调用),用户线程main在打印"over"后执行完毕,t没有要守护的线程了,所以程序会出,如果不设置t为守护线程,我们知道由于while (true)程序永远不会退出。

线程的优先级

Java的线程有优先级,高优先级线程在竞争资源时更有优势,但这也是个概率问题,高优先级也可能抢占失败,低优先级线程也不是说一定会饥饿。

Java中的线程有1~10的优先级,数字越大,优先级越高。Thread类中内置了三个默认的优先级,如下。

    public final static int MIN_PRIORITY = 1;
    public final static int NORM_PRIORITY = 5;
    public final static int MAX_PRIORITY = 10;

by @sunhaiyu

2018.4.13

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

推荐阅读更多精彩内容

  • 该文章转自:http://blog.csdn.net/evankaka/article/details/44153...
    加来依蓝阅读 7,328评论 3 87
  • 本文主要讲了java中多线程的使用方法、线程同步、线程数据传递、线程状态及相应的一些线程函数用法、概述等。 首先讲...
    李欣阳阅读 2,436评论 1 15
  • Java多线程学习 [-] 一扩展javalangThread类 二实现javalangRunnable接口 三T...
    影驰阅读 2,946评论 1 18
  • 写在前面的话: 这篇博客是我从这里“转载”的,为什么转载两个字加“”呢?因为这绝不是简单的复制粘贴,我花了五六个小...
    SmartSean阅读 4,708评论 12 45
  • 画了两个周末才完成的草莓芝士蛋糕,哈哈 远看有点像那么回事,当然啦,小白发现还是有很多的待改进地方,继续加油! 自...
    排骨要增肥__阅读 435评论 10 15