TIP
本文主要是介绍 线程-基础操作和属性 。
一个线程的创建肯定是由另一个线程创建。
被创建线程的父线程就是创建他的线程。
private void init(ThreadGroup g, Runnable target, String name,
long stackSize, AccessControlContext acc,
boolean inheritThreadLocals) {
if (name == null) {
throw new NullPointerException("name cannot be null");
}
this.name = name;
Thread parent = currentThread();//父线程
SecurityManager security = System.getSecurityManager();
//……
}
# 一、线程命名和取得
线程的所有操作方法几乎都在Thread类中定义好了
从本质上上来讲,多线程的运行状态并不是固定的,唯一的区别就在于线程的名称上。
起名:尽可能避免重名,或者避免修改名称。
在thread类中提供如下方法:
构造方法:public Thread(Runnable target, String name)
设置名字:public final synchronized void setName(String name)
取得名字:public final String getName()
既然线程本身是不缺定的状态,所以如果要取得线程名字的话,那么唯一能做的就是取得当前线程的名字。
所以在Thread类中有提供如下方法:
public static native Thread currentThread();
示例1:
class MyThread6 implements Runnable {
@Override
public void run() {// 主方法
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName() + ",i:" + i);
}
}
}
main方法
MyThread6 myThread6 = new MyThread6();
new Thread(myThread6, "线程A").start();
new Thread(myThread6).start();
new Thread(myThread6).start();
如果在设置线程对象时没有设置具体名字,那么就采用一个默认的名字进行定义。
示例2
class MyThread7 implements Runnable {
@Override
public void run() {// 主方法
System.out.println("MyThread7线程类:" + Thread.currentThread().getName());
}
}
main方法
MyThread7 myThread7 = new MyThread7();
new Thread(myThread7, "线程A").start();
myThread7.run();
输出 MyThread7线程类:线程A【new Thread(myThread7, "线程A").start();】 MyThread7线程类:main【myThread7.run();】
结论
线程依附于进程,那么进程在哪里?
每当使用java命令在JVM上解释一个程序执行的时候,那么都会默认的启动一个JVM的进程,而主方法只是这进程中的一个线程,所以整个程序一直都泡在线程的运行机制上
每一个JVM至少会启动两个线程,主线程、GC线程
# 1.1、线程组
创建线程的时候可以显示的指定线程组
if (g == null) {
/* Determine if it's an applet or not */
/* If there is a security manager, ask the security manager
what to do. */
if (security != null) {
g = security.getThreadGroup();
}
/* If the security doesn't have a strong opinion of the matter
use the parent thread group. */
if (g == null) {
g = parent.getThreadGroup();
}
}
小结:1、main线程所在的ThreadGroup称为main;2、构造一个线程如果没有显示的指定ThreadGroup,那么它将会和创建他的父线程同属一个ThreadGroup。并且和父线程同属优先级、以及daemon。
# 二、休眠
如果想让某些线程延缓执行,那么就可以使用休眠的方式来进行处理,在Thread类里面提供如下休眠操作 。
休眠方法:public static native void sleep(long millis) throws InterruptedException;
InterruptedException:如果休眠时间没到就停止休眠了,那么就会产生中断异常
示例代码1:
class MyThread8 implements Runnable {
@Override
public void run() {// 主方法
for (int i = 0; i < 100; i++) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ",i=" + i);
}
}
}
main方法
MyThread8 mt = new MyThread8();
new Thread(mt, "线程A").start();
new Thread(mt, "线程B").start();
new Thread(mt, "线程C").start();
输出 线程C,i=0 线程A,i=0 线程B,i=0 线程A,i=1 线程B,i=1 线程C,i=1
结论:以上代码执行中感觉像是所有的线程对象都同时休眠了,但严格来讲不是同时,是有先后顺序的,只不过顺序小一点而已。
示例2 main方法
MyThread8 mt = new MyThread8();
Thread thread = new Thread(mt, "线程A");
thread.start();
Thread.sleep(3000);
thread.interrupt();
结论:要想中断必须在其他线程操作。
# 三、优先级
从理论上来讲优先级越高的线程越有可能先执行。而在Thread类里面定义有以下的优先级操作方法。
设置优先级:public final void setPriority(int newPriority)
对于优先级一共定义三种:
最低优先级:public final static int MIN_PRIORITY = 1;
中等优先级:public final static int NORM_PRIORITY = 5;
最高优先级:public final static int MAX_PRIORITY = 10;
取得优先级:public final int getPriority()
示例1,观察优先级
class MyThread8 implements Runnable {
@Override
public void run() {// 主方法
for (int i = 0; i < 100; i++) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ",i=" + i);
}
}
}
main 方法
MyThread8 mt = new MyThread8();
Thread threadA = new Thread(mt, "线程A");
Thread threadB = new Thread(mt, "线程B");
Thread threadC = new Thread(mt, "线程C");
threadA.setPriority(Thread.MAX_PRIORITY);
threadB.setPriority(Thread.MIN_PRIORITY);
threadC.setPriority(Thread.MIN_PRIORITY);
threadA.start();
threadB.start();
threadC.start();
理论上A最先
示例2、查看当前线程优先级
System.out.println(Thread.currentThread().getPriority());
结论:
1.线程要有名称,Thread.currentThread()取得当前线程; 2.线程休眠是有先后顺序的; 3.理论上线程的优先级越高,越先执行
# 四、深入理解线程状态
利用JDK提供的jstack工具,查看下thread dump文件中线程的状态。NEW、RUNNABLE、TERMINATED这3个状态很容易理解,主要说明其它3种状态。
# 1、显示BLOCKED状态
public class BlockedState {
private static Object object = new Object();
public static void main(String[] args) {
Runnable task = new Runnable() {
@Override
public void run() {
synchronized (object) {
long begin = System.currentTimeMillis();
long end = System.currentTimeMillis();
// 让线程运行5分钟,会一直持有object的监视器
while ((end - begin) <= 5 * 60 * 1000) {
}
}
}
};
new Thread(task, "t1").start();
new Thread(task, "t2").start();
}
}
先获取object的线程会执行5分钟,这5分钟内会一直持有object的监视器,另一个线程无法执行处在BLOCKED状态
通过thread dump可以看到:t2线程确实处在BLOCKED (on object monitor)。waiting for monitor entry 等待进入synchronized保护的区域。
# 2、显示WAITING状态
public class WaitingState {
private static Object object = new Object();
public static void main(String[] args) {
Runnable task = new Runnable() {
@Override
public void run() {
synchronized (object) {
long begin = System.currentTimeMillis();
long end = System.currentTimeMillis();
// 让线程运行5分钟,会一直持有object的监视器
while ((end - begin) <= 5 * 60 * 1000) {
try {
// 进入等待的同时,会进入释放监视器
object.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
};
new Thread(task, "t1").start();
new Thread(task, "t2").start();
}
}
可以发现t1和t2都处在WAITING (on object monitor),进入等待状态的原因是调用了in Object.wait()。通过J.U.C包下的锁和条件队列,也是这个效果,大家可以自己实践下。
# 3、显示TIMED_WAITING状态
public class TimedWaitingState {
// java的显示锁,类似java对象内置的监视器
private static Lock lock = new ReentrantLock();
// 锁关联的条件队列(类似于object.wait)
private static Condition condition = lock.newCondition();
public static void main(String[] args) {
Runnable task = new Runnable() {
@Override
public void run() {
// 加锁,进入临界区
lock.lock();
try {
condition.await(5, TimeUnit.MINUTES);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 解锁,退出临界区
lock.unlock();
}
};
new Thread(task, "t1").start();
new Thread(task, "t2").start();
}
}
可以看到t1和t2线程都处在java.lang.Thread.State: TIMED_WAITING (parking),这个parking代表是调用的JUC下的工具类,而不是java默认的监视器。
# 五、如何使用interupt方法中断线程
代码:
public class GeneralInterrupt extends Object implements Runnable {
public void run() {
try {
System.out.println("in run() - about to work2()");
work2();
System.out.println("in run() - back from work2()");
} catch (InterruptedException x) {
System.out.println("in run() - interrupted in work2()");
return;
}
System.out.println("in run() - doing stuff after nap");
System.out.println("in run() - leaving normally");
}
public void work2() throws InterruptedException {
while (true) {
if (Thread.currentThread().isInterrupted()) {
System.out.println("C isInterrupted()=" + Thread.currentThread().isInterrupted());
Thread.sleep(2000);
System.out.println("D isInterrupted()=" + Thread.currentThread().isInterrupted());
}
}
}
public void work() throws InterruptedException {
while (true) {
for (int i = 0; i < 100000; i++) {
int j = i * 2;
}
System.out.println("A isInterrupted()=" + Thread.currentThread().isInterrupted());
if (Thread.interrupted()) {
System.out.println("B isInterrupted()=" + Thread.currentThread().isInterrupted());
throw new InterruptedException();
}
}
}
public static void main(String[] args) {
GeneralInterrupt si = new GeneralInterrupt();
Thread t = new Thread(si);
t.start();
try {
Thread.sleep(2000);
}
catch (InterruptedException x) {
}
System.out.println("in main() - interrupting other thread");
t.interrupt();
System.out.println("in main() - leaving");
}
}
执行结果 in run() - about to work2() in main() - interrupting other thread in main() - leaving C isInterrupted()=true in run() - interrupted in work2()
方法:sleep、wait、join、等会使当前线程进入阻塞状态,而调用当前线程的interrupt,就可以打断阻塞。
主要是:interrupt0(); // Just to set the interrupt flag;设置线程内部的 flag。。
isInterrupted():判断是否被中断,interrupted:判断是否被中断,没有中断的话要擦除标记中断,
# 六、Thread与Jvm虚拟机栈
在Thread构造函数中,有个参数stackSize。
# 6.1、StackSize
一般情况下,创建线程的时候不会手动指定栈内存地址空间字节数组,统一通过xss参数设置即可。stackSize这个参数对平台依赖性比较高,比如操作系统、硬件等。
stackSize越大,代表着正在线程内方法调用递归的深度就越深。
stackSIze越小,代表着创建线程数量越多
示例
public class ThreadDemo02StackSize {
public static void main(String[] args) {
if (args.length < 1) {
System.out.println("please enter the stack size.");
System.exit(1);
}
ThreadGroup threadGroup = new ThreadGroup("testGroup");
Runnable runnable = new Runnable() {
final int MAX = Integer.MAX_VALUE;
@Override
public void run() {
int i = 0;
recurse(i);
}
private void recurse(int i) {
System.out.println(i);
if (i < MAX) {
recurse(i + 1);
}
}
};
Thread thread = new Thread(threadGroup, runnable, "Test", Integer.parseInt(args[0]));
thread.start();
}
}
配置启动jvm参数
java -Xmx512m -Xms64m ThreadDemo02StackSize 10
可以看到随着StackSize参数不断增加,递归的深度也变得越来越大,该参数一般不会主动设置,采用系统默认值即可。默认会设置成0.
# 6.2、JVM内存结构与线程创建
参看:https://www.cnblogs.com/bjlhx/category/980087.html以及https://www.cnblogs.com/bjlhx/p/8877862.html
虽然stackSize不用设置,但是线程与jvm栈内存紧密相关。通过-Xss1m配置,JDK5.0以后每个线程堆栈大小为1M
其中程序计数器是比较小的一块内存,而且该部分内存是不会出现任何溢出异常的, 与线程创建、运行销毁相关比较多的是虚拟机栈内存,而且栈内存划分的大小直接决定一个JVM进程中可以创建多少个线程。
测试代码
public class ThreadDemo05ThreadCounter extends Thread {
final static AtomicInteger counter = new AtomicInteger();
public static void main(String[] args) {
try {
while (true) {
new ThreadDemo05ThreadCounter().start();
}
} catch (Throwable e) {
System.out.println("counter:" + counter.get());
e.printStackTrace();
}
}
@Override
public void run() {
try {
System.out.println("counter:" + counter.getAndIncrement());
TimeUnit.SECONDS.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
首先查看默认线程栈大小:java -XX:+PrintFlagsFinal -version|grep ThreadStackSize
intx ThreadStackSize = 1024
即1m
注意一:mac上有一个单进程的最大线程数限制,可以输入下面命令查看。sysctl kern.num_taskthreads
sysctl kern.num_taskthreads
kern.num_taskthreads: 4096
注意二:在mac上-Xss1m不起作用,推荐-XX:ThreadStackSize=1m配置
jvm配置:-Xmx64m -Xms64m -XX:ThreadStackSize=1m;输出:
counter:4071
java.lang.OutOfMemoryError: unable to create new native thread
jvm配置:-Xmx64m -Xms64m -XX:ThreadStackSize=100m;输出:
counter:1304
java.lang.OutOfMemoryError: unable to create new native thread
jvm配置:-Xmx64m -Xms64m -XX:ThreadStackSize=256m;输出:
counter:505 java.lang.OutOfMemoryError: unable to create new native thread
小结:
一个进程内存大小:堆内存+线程数量*栈内存
线程数量=(最大地址空间-JVM堆内存-系统保留内存)/ThreadStackSize
最大地址空间:一般是最大进程内存MaxProcessMemory 在32位的 windows下是 2G,
这个公式还要注意进程最大线程数量比如有的mac系统为4096个
线程实际创建数量也与操作系统参数有关,如mac系统的单进程最大线程数;linux系统 三个:
/proc/sys/kernal/thread-max;
/proc/sys/kernal/pid_max;
/proc/sys/vm/max_map_count;
max_user_process(ulimit -u) 在64位Linux系统(CentOS 6, 3G内存)下测试,发现还有一个参数是会限制线程数量:max user process(可通过ulimit –a查看,默认值1024,通过ulimit –u可以修改此值),这个值在上面的32位Ubuntu测试环境下并无限制
# 参考文章
- https://www.cnblogs.com/bjlhx/p/7589162.html
← 线程-实现方式比较 线程-常用方法和修饰符 →