开篇
毕业三四年了,一直没怎么用过多线程,一直停留在继承Thread 和 实现Runnable接口上,最近开始写多线程了发现没我想象那么简单,今天开始从头过一遍java的多线程的知识包括最基础的细节,希望对后面的开发以及以后的面试有所帮助。
进程与线程
- 说到进程,打开我们操作系统的任务管理器->进程,这里应该不用多说了。看看官方解释:进程是操作系统结构的基础;是一次程序的运行;它是系统进行资源分配和调度的一个单位。
- 线程是进程中独立运行的子任务。
- 一个进程在运行时至少有一个线程,一个mian方法就是一个进程,同时默认有一个线程。
- 多线程是异步的,不是代码上下执行的顺序。
使用多线程
这里我就不多说了,看看Thread的源代码,它也是实现了Runnable的接口,java不能多继承,因此两种方式类似,我就直接贴代码了。
继承Thread
package thread.t001;
public class MyThread extends Thread {
@Override
public void run() {
System.out.println("MyThread"+Thread.currentThread().getName());
}
public static void main(String[] args) {
MyThread m = new MyThread();
m.start();
}
}
实现Runnable接口
package thread.t001;
public class MyThread2 implements Runnable{
public void run() {
System.out.println("Runnable: "+Thread.currentThread().getName());
}
public static void main(String[] args) {
MyThread2 m = new MyThread2();
Thread t = new Thread(m);
t.start();
}
}
以上两种的调用方法看似有点区别,其实实质是一样的。调用Thread的start方法,执行多线程类的run()方法
注意几点,同一个线程不能多次start会报
Exception in thread "main" java.lang.IllegalThreadStateException
异常
实例变量与线程安全
不共享数据
package thread.t001;
public class MyTask extends Thread{
int count = 5;
@Override
public void run() {
while(count > 0){
count--;
System.out.println(Thread.currentThread().getName()+":"+count);
};
}
public static void main(String[] args) {
MyTask t1 = new MyTask();
t1.start();
MyTask t2 = new MyTask();
t2.start();
}
}
结果:
Thread-0:4
Thread-0:3
Thread-0:2
Thread-0:1
Thread-0:0
Thread-1:4
Thread-1:3
Thread-1:2
Thread-1:1
Thread-1:0
共享数据
package thread.t001;
public class MyTask extends Thread{
int count = 5;
@Override
synchronized public void run() {
while(count > 0){
count--;
System.out.println(Thread.currentThread().getName()+":"+count);
};
}
public static void main(String[] args) {
MyTask t1 = new MyTask();
Thread t = new Thread(t1);
t.start();
Thread t2 = new Thread(t1);
t2.start();
}
}
结果:
Thread-1:4
Thread-2:3
Thread-1:2
Thread-2:1
Thread-1:0
通过上面两段代码可以发现,共享数据只创建了一次资源,用两个线程去跑,结果是两个线程共同消费了count,但是细心地你发现了共享数据的方法前加了synchronized关键字这个稍后说;
但是不共享数据的没有加,这是因为共享资源的多线程中,线程是不安全的,因为多个线程操作的同一个资源count,如果count变量做为该方法的局部变量,则是线程安全的。
多线程常用方法
currentThread()方法可返回代码段正在被哪个线程调用的信息;
isAlive()方法判断当前的线程是否处于活动状态;
sleep()的作用是在制定的毫秒书内让当前正在执行的线程休眠;
getId()方法的作用是取得线程的唯一标示;
以上方法就不一一且代码测试了,重点看一下停止一个线程interrupt(0方法
停止一个线程
简单粗暴的方法是是stop();
调用stop()会报出java.lang.ThreadDeath异常,此异常不需要显示的扑捉,stop()方法jdk已不再建议使用,
使用stop()方法如果强制让线程停止侧有可能使一些清理性的工作得不到完成
对锁定的对象进行了“解锁”,导致数据得不到同步的处理,出现数据不一致的问题
停止一个线程有以下几种办法:
interrupt -> isterrupt -> return
interrupt -> isterrupt -> break
interrupt -> isterrupt -> throw exception
需要用到isterrupt()方法配合使用或者interrupted()方法,判断是否为true
我举一个例子说明下,其它方法类似:
package thread.t001;
public class MyInterrupt extends Thread {
@Override
public void run() {
try {
for (int i=0; i<100; i++){
if (i == 50){
Thread.currentThread().interrupt();
if (Thread.currentThread().interrupted()){
throw new InterruptedException();
}
}
System.out.println(i);
}
System.out.println("我是run的结尾!");
} catch (Exception e) {
}
}
}
加粗两三句代码可结束一个线程,
Thread.currentThread().interrupt();
将此线程标记为停止状态为true
Thread.currentThread().interrupted()
如果此线程为停止状态
throw new InterruptedException();
抛出一个异常跳出;这一步可以用break(此方法看逻辑,虽然跳出了循环,但是for下面的还会执行)
通过以上可以得出 interrupt()方法并不是将线程停掉,而是做了一个标记,然后使用interrupted判断此线程的标记,如果标记了则终止;
interrupt()与isterrupt
都可以判断线程当前的状态
interrupt() 判断之后会清空此线程的状态,如果第一次判断为true,则之后再调用则为false
isterrupt()不清空状态
在sleep()状态下停止线程
在sleep()中interrupt 和 interrupt再sleep()都会进入catch
yield()作用是放弃当前的CPU资源,将它让给其他的任务去占用CPU执行时间,但放弃时间不确定,有可能刚刚放弃,马上又获得CPU是时间片
suspend()暂停,resume()继续
这两个方法有个缺点就是独占资源,会导致共享的数据其它线程无法访问,也会导致数据不同步(比如给两个值赋值,第一个赋完值暂停了还没有继续,然后其它线程调用了get方法获取这两个值)
线程的优先级别
在操作系统中,线程可以划分优先级,优先级较高的线程得到的CPU资源较多,也就是CPU、优先执行优先级较高的线程对象中的任务
setPriority();优先级级别为1-10
如果小于1或者大于10则会抛出异常
优先级高不一定就先执行完
线程优先级的继承特性
在Java中,线程的优先级具有继承性,比如A线程启动B线程,则B线程的优先级与A线程是一样的
守护线程(Daemon)
Java线程中有两种线程,一种是用户线程,一种是守护线程
守护线程是一种特殊的线程,它的特性有“陪伴”的含义,当进程不存在非守护线程了,则守护线程自动销毁。典型的守护线程就是垃圾回收线程
今天就先学习整理到这里,下一篇重点学习一下 多线程的并发。