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
Java多线程--线程及相关的Java API的更多相关文章
- java 多线程—— 线程让步
java 多线程 目录: Java 多线程——基础知识 Java 多线程 —— synchronized关键字 java 多线程——一个定时调度的例子 java 多线程——quartz 定时调度的例子 ...
- java 多线程—— 线程等待与唤醒
java 多线程 目录: Java 多线程——基础知识 Java 多线程 —— synchronized关键字 java 多线程——一个定时调度的例子 java 多线程——quartz 定时调度的例子 ...
- Java多线程-线程的生命周期
线程可以分为4个状态:New(新生),Runnable(可运行):为了方便分析,还可将其分为:Runnable与Running.blocked(被阻塞),Dead(死亡). 与人有生老病死一样,线程也 ...
- Java多线程-线程的同步(同步方法)
线程的同步是保证多线程安全访问竞争资源的一种手段.线程的同步是Java多线程编程的难点,往往开发者搞不清楚什么是竞争资源.什么时候需要考虑同步,怎么同步等等问题,当然,这些问题没有很明确的答案,但有些 ...
- Java多线程——线程的优先级和生命周期
Java多线程——线程的优先级和生命周期 摘要:本文主要介绍了线程的优先级以及线程有哪些生命周期. 部分内容来自以下博客: https://www.cnblogs.com/sunddenly/p/41 ...
- Java多线程——线程的创建方式
Java多线程——线程的创建方式 摘要:本文主要学习了线程的创建方式,线程的常用属性和方法,以及线程的几个基本状态. 部分内容来自以下博客: https://www.cnblogs.com/dolph ...
- Java多线程——线程之间的协作
Java多线程——线程之间的协作 摘要:本文主要学习多线程之间是如何协作的,以及如何使用wait()方法与notify()/notifyAll()方法. 部分内容来自以下博客: https://www ...
- Java多线程——线程的死锁
Java多线程——线程的死锁 摘要:本文主要介绍了Java多线程中遇到的死锁问题. 部分内容来自以下博客: https://www.cnblogs.com/wy697495/p/9757982.htm ...
- Java多线程——线程之间的同步
Java多线程——线程之间的同步 摘要:本文主要学习多线程之间是如何同步的,如何使用volatile关键字,如何使用synchronized修饰的同步代码块和同步方法解决线程安全问题. 部分内容来自以 ...
随机推荐
- VS2010的快捷键乱
vs2010的快捷键乱了,点击回车会出现属性窗口,点击退格键会相当于编辑里面的撤销功能 点击ctrl+s会出现sharepoint窗口,在网上找了一个解决方式(很难找),原问在这: http://q. ...
- WPF Layout 系统概述 MeasureOverride和ArrangeOverride
说的非常的好:多参考!!! https://blog.csdn.net/nncrystal/article/details/47416339 https://www.cnblogs.com/dingl ...
- ovs flow 命令集
流表可以有多个执行动作,是从左向右以此执行,常用动作如下: output:port: 输出数据包到指定的端口.port 是指端口的 OpenFlow 端口编号 group:group_id 输出数据包 ...
- Android开发 - 获取Android设备的唯一标识码(Android 6.0或更高)
在我们的APP开发中,通常需要获取到设备的唯一标识.在Android6.0之前,有很多方法我们可以方便获取到硬件的唯一标识,但是在Android6.0之后,Android系统大幅限制了我们获取设备的硬 ...
- [vuejs] 深入响应式原理
深入响应式原理 现在是时候深入一下了!Vue 最独特的特性之一,是其非侵入性的响应式系统.数据模型仅仅是普通的 JavaScript 对象.而当你修改它们时,视图会进行更新.这使得状态管理非常简单直接 ...
- 【多线程】:Synchronized和ReentrantLock的对比
相同点: 两者都是可重入锁,同一个线程每进入一次,锁的计数器都自增1,等到锁的计数器下降为0时才能释放锁. 底层实现对比: Synchronized是依赖于JVM实现的,而ReentrantLock是 ...
- C# SqlHelper类的数据库操作
#region 私有构造函数和方法 private SqlHelper() { } /// <summary> /// 将SqlParameter参数数组(参数值)分配给SqlComman ...
- cmd生成文件目录tree
一.生成 目录tree 到 控制台 有时候需要快速生成一个文件夹中所有成员的 目录tree,可以通过 cmd命令直接生成 命令:tree /f 二.生成 目录tree 到 指定文件 如果想讲目录树生成 ...
- 十分钟内在Ubuntu系统上搭建Mono开发环境(Mono软件Ubuntu系统国内镜像源、Mono国内镜像源)
Mono软件Ubuntu系统国内镜像源.Mono国内镜像源 http://download.githall.cn/repo 替换为国内源(非官方)有利于加快mono的安装速度,一般情况下,完成mono ...
- (转)使用 db2pd 命令进行监视和故障诊断
原文:https://www.ibm.com/support/knowledgecenter/zh/SSEPGG_9.7.0/com.ibm.db2.luw.admin.trb.doc/doc/c00 ...