java线程
进程
进程是程序的一次执行,进程是一个程序及其数据在处理机上顺序执行时所发生的活动,进程是具有独立功能的程序在一个数据集合上运行的过程,它是系统进行资源分配和调度的一个独立单位
·进程是系统进行资源分配和调度的独立单位。每一个进程都有它自己的内存空间和系统资源
线程
为了程序并发运行,系统必须进行以下的一系列操作:
- 创建进程,系统在创建一个进程时,必须为它分配其所必须的,除处理机以外的所有资源,如内存空间,I/O设备,以及建立相应的PCB。
- 撤销进程,系统在撤销进程的时候,需要对其所占用的资源执行回收操作,然后撤销PCB
- 进程切换,对进程进行上下文切换时,需要保留当前进程的CPU环境,设置新选中进程的CPU环境,因而须花费不少的处理机时间。
引入线程主要是为了提高系统的执行效率,减少处理机的空转时间和调度切换(保护现场信息)的时间,以及便于系统管理。,使OS具有更好的并发性
进程实现多处理非常耗费CPU的资源,引入线程是作为调度和分派的基本单位(取代进程的部分基本功能【调度】)
-进程作为资源分配的基本单位
-线程作为资源调度的基本单位,是程序的执行单元,执行路径,是程序使用CPU的基本单位。
-线程有3种基本状态:执行,就绪,阻塞
-线程有5种基本操作:派生,阻塞,激活,调度,结束
-线程有2个基本类型:
- 用户级线程:管理过程全部由用户程序完成,操作系统内核心只对进程进行管理。
- 系统级线程(核心级线程):由操作系统内核进行管理。操作系统内核给应用程序提供相应的系统调用和应用程序接口API,以使用户程序可以创建、执行以及撤消线程。
并行与并发
并行:
- 并行性是指同一时刻内发生两个或多个事件。
- 并行是在不同实体上的多个事件
并发:
- 并发性是指同一时间间隔内发生两个或多个事件。
- 并发是在同一实体上的多个事件
并行是针对进程的,并发是针对线程的。
java实现多线程
Thread类的文档注释:
/**
* JVM允许一个应用程序有多个线程并发执行
* <p>
*线程有优先级,优先级高的先执行
线程也有守护进程,线程初始化的时候,所有优先级都是平等的
* <p>
* 当JVM启动是,通常有一个没有守护线程的,名字叫做main的线程启动
线程结束的情况:
1.执行退去exit方法
2.run方法执行完或者抛出了异常
* <p>
* 这里有两个方法创建新线程
* 一个是继承Thread类,重写run方法,例子:
* class PrimeThread extends Thread {
* long minPrime;
* PrimeThread(long minPrime) {
* this.minPrime = minPrime;
* }
*
* public void run() {
* // compute primes larger than minPrime
* . . .
* }
* }
* </pre></blockquote><hr>
* <p>
* 启动线程的方法
* <blockquote><pre>
* PrimeThread p = new PrimeThread(143);
* p.start();
* </pre></blockquote>
* <p>
* 另外一个多线程的方法是实现Runnable接口,重写run方法.
* <hr><blockquote><pre>
* class PrimeRun implements Runnable {
* long minPrime;
* PrimeRun(long minPrime) {
* this.minPrime = minPrime;
* }
*
* public void run() {
* // compute primes larger than minPrime
* . . .
* }
* }
* </pre></blockquote><hr>
* <p>
* 启动线程的方法
* <blockquote><pre>
* PrimeRun p = new PrimeRun(143);
* new Thread(p).start();
* </pre></blockquote>
* <p>
* 每个线程都有自己的名字,在创建的时候如果没有指定,会自动给他一个名字;
* <p>
* 参数不能设置为null
*
* @author unascribed
* @see Runnable
* @see Runtime#exit(int)
* @see #run()
* @see #stop()
* @since 1.0
*/
方法一:继承Tread,重写run方法。
class MyThread extends Thread{
@Override
public void run(){
for(int i=0;i<5;i++){
System.out.println(Thread.currentThread().getName()+":"+i)
}
}
}
测试方法:
public static void main(String[] args) {
MyThread m1 = new MyThread();
MyThread m2 = new MyThread();
m1.start();
m2.start();
}
结果:
Thread-1:0
Thread-0:0
Thread-1:1
Thread-0:1
Thread-1:2
Thread-0:2
Thread-1:3
Thread-0:3
Thread-1:4
Thread-0:4
方法二:实现Runnable接口,重写run方法。
class MyRunnable implements Runnable{
@Override
public void run(){
for(int i=0;i<5;i++){
System.out.println(Thread.currentThread().getName()+":"+i)
}
}
}
测试方法:
public static void main(String[] args) {
MyRunnable my = new MyRunnable();
Thread t1 = new Thread(my);
Thread t2 = new Thread(my);
t1.start();
t2.start();
}
测试结果:
Thread-0:0
Thread-1:0
Thread-0:1
Thread-1:1
Thread-0:2
Thread-1:2
Thread-0:3
Thread-1:3
Thread-0:4
Thread-1:4
一些需要注意的细节:
run()和start()方法区别:
-
run()
:仅仅是封装被线程执行的代码,直接调用是普通方法 -
start()
:首先启动了线程,然后再由jvm去调用该线程的run()方法。
一般我们使用实现Runnable接口
- 可以避免java中的单继承的限制
- 应该将并发运行任务和运行机制解耦,因此我们选择实现Runnable接口这种方式!
Thread类源代码
如何查看线程的名字:调用Thread.currentThread().getName()即可
/**
*无参构造方法
*/
public Thread() {
this(null, null, "Thread-" + nextThreadNum(), 0);
}
public Thread(ThreadGroup group, Runnable target, String name, long stackSize) {
this(group, target, name, stackSize, null, true);
}
无参构造方法调用了另外一个构造方法,传入了一个"Thread-" + nextThreadNum()作为参数的name。
private static synchronized int nextThreadNum() {
return threadInitNumber++;
}
//线程初始化的数量
private static int threadInitNumber;
//如何进行自定义命名
public Thread(ThreadGroup group, Runnable target, String name,
long stackSize) {
this(group, target, name, stackSize, null, true);
}
//最后调用的构造方法
private Thread(ThreadGroup g, Runnable target, String name,
long stackSize, AccessControlContext acc,
boolean inheritThreadLocals) {
//如果姓名为空 则抛出空指针异常
if (name == null) {
throw new NullPointerException("name cannot be null");
}
//否则对全局变量name进行赋值
this.name = name;
}
//或者通过方法调用
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);
//private native void setNativeName(String name)
}
}
守护线程
-守护线程时为其他线程服务。
-垃圾回收线程就是守护线程
守护线程有一个特点:
- 当别的用户线程执行完了,虚拟机就会退出,守护线程也就会被停止掉了。
- 也就是说:守护线程作为一个服务线程,没有服务对象就没有必要继续运行了
使用守护线程的时候要注意的地方
-
在线程启动前设置为守护线程,方法是
setDaemon(boolean on)
- 使用守护线程不要访问共享资源(数据库、文件等),因为它可能会在任何时候就挂掉了。
- 守护线程中产生的新线程也是守护线程
测试
public static void main(String[] args) {
MyThread myThread = new MyThread();
//带参构造方法给线程起名字
Thread thread1 = new Thread(myThread, "我是正常线程");
Thread thread2 = new Thread(myThread, "我是守护线程鸭!!");
// 设置为守护线程
thread2.setDaemon(true);
thread1.start();
thread2.start();
}
//理想情况:线程1先结束线程2中断执行
//另外不能线程运行时设置为守护线程 会抛出异常:IllegalThreadStateException
线程的优先级
线程优先级高仅仅表示线程获取的CPU时间片的几率高,但这不是一个确定的因素!
线程的优先级是高度依赖于操作系统的,Windows和Linux就有所区别(Linux下优先级可能就被忽略了)~
Java提供的优先级默认是5,最低是1,最高是10:
public static final int MIN_PRIORITY = 1;
public static final int NORM_PRIORITY = 5;
public static final int MAX_PRIORITY = 10;
//设置线程优先级
public final void setPriority(int newPriority) {
ThreadGroup g;
checkAccess();
//如果优先级超出1~10 抛出异常IllegalArgumentException
if (newPriority > MAX_PRIORITY || newPriority < MIN_PRIORITY) {
throw new IllegalArgumentException();
}
if((g = getThreadGroup()) != null) {
if (newPriority > g.getMaxPriority()) {
newPriority = g.getMaxPriority();
}
//调用底层源代码赋值
setPriority0(priority = newPriority);
}
}
线程的生命周期
线程有3个基本状态:执行,就绪,阻塞
sleep();
调用sleep方法会进入计时等待状态,等时间到了,进入的是就绪状态而并非是运行状态
public static native void sleep(long millis) throws InterruptedException;
yield()方法
调用yield方法会让别的线程执行,但不确保一定让出权限。很少用
public static native void yield();
join()方法,
调用join方法会等待该线程执行完毕后才执行别的线程
public final synchronized void join(final long millis)
throws InterruptedException {
//如果秒数大于0
if (millis > 0) {
//如果线程正在运行
if (isAlive()) {
final long startTime = System.nanoTime();
long delay = millis;
do {
//调用Object中的等待方法
wait(delay);
} while (isAlive() && (delay = millis -
TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startTime)) > 0);
}
} else if (millis == 0) {
while (isAlive()) {
wait(0);
}
} else {
throw new IllegalArgumentException("timeout value is negative");
}
}
interrupt方法
一般使用interrupt方法请求终止线程。
这是一个标记,不会真正停止一个线程。具体中断还是继续运行,应该由被通知的线程自己处理
public void interrupt() {
//如果不是当前线程
if (this != Thread.currentThread()) {
//检查权限
checkAccess();
synchronized (blockerLock) {
//查看是否阻塞的线程调用
Interruptible b = blocker;
if (b != null) {
interrupt0(); // set interrupt status
//修改状态,并抛出异常
b.interrupt(this);
return;
}
}
}
// set interrupt status
interrupt0();
}
interrupt线程中断还有另外两个方法(检查该线程是否被中断):
-静态方法interrupted()-->会清除中断标志位
-实例方法isInterrupted()-->不会清除中断标志位
例子:
public static void main(String[] args) {
Thread t = new Thread(()-> {
int i = 0;
try {
while (i < 1000) {
// 睡个半秒钟我们再执行
Thread.sleep(500);
System.out.println(i++);
}
} catch (InterruptedException e) {
// 判断该阻塞线程是否还在
System.out.println(Thread.currentThread().isAlive());
// 判断该线程的中断标志位状态
System.out.println(Thread.currentThread().isInterrupted());
System.out.println("In Runnable");
e.printStackTrace();
}
});
System.out.println("This is main ");
t.start();
try {
// 在 main线程睡个3秒钟
Thread.sleep(3000);
} catch (InterruptedException e) {
System.out.println("In main");
e.printStackTrace();
}
// 设置中断
t.interrupt();
}
结果:
This is main
0
1
2
3
4
true
false
In Runnable
java.lang.InterruptedException:
参考博客