一、什么是线程和进程
- 进程:1.操作系统结构的基础 2.操作系统进行资源分配和调度的独立单位
- 线程:进程中独立运行的子任务,程序的执行单元。
例如:在启动jvm 之后,相当于启动一个系统进程。而jvm除了在执行java程序的main方法的同时,肯定还在执行着垃圾回收。这里的main方法以及后面的垃圾回收都相当于多个线程。
二、多线程技术目的
cpu是运算和控制的中心,在cpu运行过程中,同一时刻只能运行单个进程任务。
先假定这样一个场景:
- 在单线程的情况下,cpu在运行部分计算任务过后,还需要再进行IO写入硬盘操作。而大家都知道同等情况下硬盘或者网络资源的IO速度是远远低于cpu的运行计算速度的。所以这时很可能出现这种场景,整个进程阻塞在IO这里,同时cpu还在这里等待IO操作结束,并且其间其他后续进程也无法进入cpu运行计算。
- 多线程的情况下,同一个进程可以分为多个线程同时进行。利用cpu等待IO操作的闲置的时间段,去处理其他的线程,尽可能的提高cpu的利用率(换言之:就是不让cpu闲下来)。
【ps:但是需要注意的是,多线程并不一定比单线程快。因为在多线程的场景下,cpu需要在不同的多线程之间进行切换,而切换占用一定的资源】
三、线程安全
线程安全指的是在多线程操作同一实例对象的同一变量是不会产生值不同步、数据不一致的情况。例如:常使用的集合类HashTable 常用的字符串变量 StringBuffer等都能够保证线程安全。
例:存在非线程安全问题
public class TestThreadSafe implements Runnable {
private static int safeNum=10;
//private static volatile int safeNum=10;
@Override
synchronized public void run() {
while (safeNum>0){
safeNum--;
System.out.println(Thread.currentThread().getName()+safeNum);
}
}
public static void main(String[] args) {
TestThreadSafe thread=new TestThreadSafe();
Thread thread1=new Thread(thread,"a");
Thread thread2=new Thread(thread,"b");
Thread thread3=new Thread(thread,"c");
Thread thread4=new Thread(thread,"d");
thread1.start();
thread2.start();
thread3.start();
thread4.start();
}
}
通过执行结果得出这样几个结论
1.多线程在操作同一对象实例的同一变量存在非线程安全问题
2.并且多线程在执行时,执行顺序是随机的。如图,在每个线程启用start方法开启之后,而控制台显示的则不是按照start启动的顺序执行的。
四、线程的基本操作
1.实现多线程的几种方式
i:继承Thread类
public class TestThread extends Thread{
@Override
public void run() {
int i=9;
while (true && i>0){
System.out.println(Thread.currentThread().getName()+(i--));
}
}
public static void main(String[] args) {
TestThread testThread=new TestThread();
Thread thread=new Thread(testThread,"a");
thread.start();
}
ii:实现Runnable接口
代码参见:——->参见对于线程安全问题解释的代码块。
这两种方式基本上没有什么区别,如果java类已经有一个父类。由于java不支持多继承的原因,这时候通过实现Runnable可以解决这个问题。
小注:run/start
1.调用thread的start方法相当于在当前线程中新起一个线程,执行run方法。等到cpu时间片轮转到该线程,则该线程开始执行run方法。
2.相当于执行一个实例的方法一样,还是在当前线程中执行,不会另起线程去做。
换言之,如果用run方法来启动。其实实际上还是一个只有当前线程,不会产生当前线程之外的线程。
如下演示,通过查看当前线程名称可以验证以上结论。
public class TestRunStart extends Thread{
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
public static void main(String[] args) {
TestRunStart testRunStartThread=new TestRunStart();
Thread thread=new Thread(testRunStartThread,"a");
thread.run();
System.out.println("--------------------------------------");
thread.start();
}
}
i:当执行run方法之后,直接顺序执行了run方法,打印了当前线程的名称:main,也就是我们当前执行的这个main方法
ii:当执行start方法之后,打印的当前线程而是名称为新建线程的名称:a。
2.暂停/重启
i:sleep方法
通过Thread的静态方法使得当前线程暂停的时间,有两个静态方法提供功能。
- sleep(long millis) 毫秒
- sleep(long millis,intnanos) 毫秒,纳秒
ii:suspend/resume
suspend:暂停当前线程。
resume:恢复当前线程。
@Data
public class TestSuspendResume extends Thread {
private long i=0;
@Override
public void run() {
while (1>0){
i++;
}
}
public static void main(String[] args) throws Exception{
TestSuspendResume testSuspendResume=new TestSuspendResume();
Thread thread=new Thread(testSuspendResume,"a");
thread.start();
Thread.sleep(1000);
//暂停,测试i++停止,i的值不变
thread.suspend();
System.out.println("a="+System.currentTimeMillis()+" i="+testSuspendResume.getI());
Thread.sleep(1000);
System.out.println("a="+System.currentTimeMillis()+" i="+testSuspendResume.getI());
//恢复,i++,i的值继续增加
thread.resume();
Thread.sleep(1000);
System.out.println("a="+System.currentTimeMillis()+" i="+testSuspendResume.getI());
}
}
ps:线程暂停之后,即使将当前线程暂停10s之后,再次获取i的值,依然是上次的i的值。说明,通过suspend方法已然将TestSuspendResume线程暂停。
而后通过恢复当前线程,再次将main方法停止10s。这时候再次获取i的值,这时候就已经发现这个i的值出现增加。说明,通过resume方法已然将TestSuspendResume方法恢复执行。
iii:yield
public class TestYieldThread extends Thread {
@Override
public void run() {
try {
long beginTime=System.currentTimeMillis();
Thread.yield();
Thread.sleep(1000);
long endTime=System.currentTimeMillis();
System.out.println("yield 耗时:"+(endTime-beginTime));
beginTime=System.currentTimeMillis();
Thread.sleep(1000);
endTime=System.currentTimeMillis();
System.out.println("正常耗时:"+(endTime-beginTime));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
Thread thread=new Thread(new TestYieldThread(),"a");
thread.start();
}
}
ps:yield方法不定时让出当前cpu资源。通过上图可以得出,调用yield方法之后,程序的运行速度变慢。
3.停止线程
public class TestStopThread extends Thread{
private int i=0;
@Override
public void run() {
try {
while (true){
System.out.println("i="+i++);
if(this.interrupted()){
throw new InterruptedException();
}
}
} catch (InterruptedException e) {
System.out.println("当前线程已经退出!");
e.printStackTrace();
}
}
public static void main(String[] args) throws Exception{
TestStopThread testStopThread=new TestStopThread();
Thread thread=new Thread(testStopThread,"a");
thread.start();
Thread.sleep(50);
thread.interrupt();
}
}
ps:通过interrupt是的thread线程中断,在线程run方法里面判断interrupted来判断是否该线程已经被中断,从而抛出异常使得
5.线程退出。
interrupted()与isInterrupted()
从上面的代码就可以判断出interrupted()方法是用来判断当前线程是否被中断,而isInterrupted()方法又是来做什么的。
同样来看一个例子。
public class TestInterrupted extends Thread {
@Override
public void run() {
System.out.println("test interrupted ");
}
public static void main(String[] args) {
TestInterrupted testInterrupted=new TestInterrupted();
testInterrupted.start();
testInterrupted.interrupt();
System.out.println("当前main线程状态0:"+testInterrupted.interrupted());
System.out.println("当前testInterrupted线程状态1:"+testInterrupted.isInterrupted());
System.out.println("当前testInterrupted线程状态2:"+testInterrupted.isInterrupted());
Thread.currentThread().interrupt();
System.out.println("当前main线程状态1:"+testInterrupted.interrupted());
System.out.println("当前main线程状态2:"+testInterrupted.interrupted());
}
}
通过结果可以得出:
- interrupted():测试的当前线程是否中断状态,也就是上代码中的main线程的中断状态。
- isInterrupted():测试当前线程对象是否中断状态,也就是上代码中的testInterrupted对象的中断状态。
从java 的源码中更能印证这个结论,
interruped是一个Thread类的静态方法,所以只要是Thread的都可以调用,但是其实最后调用的都是当前线程的isInterrupted方法。而且第二次调用时,直接返回false。
从源码中可以看到isInterrupted是一个native的对象方法,判断是对象的是否中断状态。
6.线程的优先级
在Thread源码中可以发现最大的线程优先级为10,最小的为1.默认的优先级为5.有这样两个规则
- 在运行过程中,子线程的优先级等于父线程的优先级。
- 在运行过程中,优先级大的大部分总是先执行完。
@Data
public class TestThreadPriority extends Thread {
private volatile int aCount=0;
private volatile int bCount=0;
private volatile int cCount=0;
@Override
public void run() {
while(true){
String name=Thread.currentThread().getName();
switch (name){
case "a":
aCount++;
break;
case "b":
bCount++;
break;
case "c":
cCount++;
break;
}
}
}
public static void main(String[] args) throws Exception{
TestThreadPriority testThreadPriority=new TestThreadPriority();
Thread threada=new Thread(testThreadPriority,"a");
Thread threadb=new Thread(testThreadPriority,"b");
Thread threadc=new Thread(testThreadPriority,"c");
threada.setPriority(1);
threadb.setPriority(2);
threadc.setPriority(3);
threada.start();
threadb.start();
threadc.start();
Thread.sleep(10000);
threada.stop();
threadb.stop();
threadc.stop();
System.out.println("aCount="+testThreadPriority.getACount());
System.out.println("bCount="+testThreadPriority.getBCount());
System.out.println("cCount="+testThreadPriority.getCCount());
}
}
如图在执行了很多次之后可以发现,大部分情况下优先级高的还是优先得到执行。