文章目录
- 一.进程和线程
- 1.概念
- 2.特点
- 二.Thread类和Runnable接口
- 1.继承Thread类
- 2.实现Runnable接口
- 三.实例变量与线程安全
一.进程和线程
1.概念
进程是操作系统结构的基础;是一次程序的执行;是一个程序及其数据在处理机上顺序执行时所发生的活动;是程序在一个数据集合上运行的过程,它是系统进行资源分配和调度的一个独立单位。操作系统中运行的exe程序就是一个进程。
线程可以理解为在进程中独立运行的子任务。一个进程可以只有一个线程,也可以包含多个线程。 所以,使用多线程后,可以在同一时间内运行更多不同种类型的任务。当一个进程开启多个线程后,其中有一个主线程要用来调用本进程中的其它线程。
2.特点
- 使用多线程时,可以把占据使用时间长的程序中的任务放到后台去处理。
- CPU运行效率大大提高。单任务的特点就是排队执行,也就是同步,需要完成任务1,才会开始任务2。这就是单任务环境下的缺点,导致CPU利用率大幅下降。而多任务的环境情况下,CPU完全可以在任务1和任务2之间来回切换,使得不用等任务1完成,任务2就可以开始,这就使得系统的运行效率大大得到提升。
- 多线程是异步的,而且线程被调用的时机是随机的。
二.Thread类和Runnable接口
在Java的JDK开发包中,已经自带了对多线程技术的支持,可以很方便地进行多线程编码。实现多线程编码的方式主要有两种,一种是继承Thread类,另一种是实现Runnable接口。Thread类的结构,如下:
public class Thread implements Runnable
从这句代码中可以看出,Thread类实现了Runnable接口,它们之间具有多态关系。
使用继承Thread类的方式创建新线程时,最大的局限就是不支持多态继承,因为Java与语言的特点就是单根继承,所以为了支持多继承,完全可以实现Runnable接口的方式,一边实现一边继承。这两种方式创建的线程在工作时的性质是一样的。
1.继承Thread类
先创建一个自定义的线程类MyThread.java,此类继承Thread类,并且重写run()方法。
package com.mythread.www;
public class MyThread extends Thread {
@Override
public void run() {
super.run();
System.out.println("MyThread");
}
}
package test;
import com.mythread.www.MyThread;
public class Run {
public static void main(String[] args) {
MyThread mythread = new MyThread();
mythread.start();
System.out.println("运行结束!");
}
}
运行结果如下图:
从运行结果来看,MyThread.java 类中的run方法执行的时间比较晚,这也说明在使用多线程技术时,代码的运行结果与代码执行顺序或调用顺序是无关的。
线程是一个子任务,CPU以不确定的方式,或者说是以随机的时间来调用线程中的run方法,所以就会出现先打印“运行结束!”后输出“MyThread”这样的结果了。这就是线程的调用的随机性。
2.实现Runnable接口
如果想创建的线程类已经有一个父类了,这时就不能再继承自Thread类,因为Java不支持多继承,所以就需要实现Runnable接口来应对这样的情况。创建一个项目,里面创建一个实现Runnable接口的类MyRunnable,代码如下:
package myrunnable;
public class MyRunnable implements Runnable {
@Override
public void run() {
System. out.println("运行中!");
}
}
而在Thread.java类的8个构造函数中,其中有两个构造函数可以传递Runnable接口,分别是
- Thread(Runnable target)
- Thread(Runnabletarget,String name)
说明构造函数支持传入一个Runnable接口的对象。运行类代码如下:
import myrunnable.MyRunnable;
public class Run {
public static void main(String[] args) {
Runnable runnable = new MyRunnable();
Thread thread = new Thread(runnable);
thread.start();
System.out.println("运行结束!");
}
}
运行结果如下:
使用继承Thread类的方式来开发多线程应用程序在设计上是有局限性的,因为Java是单根继承,不支持多继承,所以为了改变这种限制,可以使用实现Runnable接口的方式来实现多线程技术。
这也就意味着构造函数Thread(Runnable target) 不光可以传入Runnable接口的对象,还可以传入一个Thread类的对象,这样做完全可以将一个Thread对象中的run()方法交由其他的线程进行调用。
三.实例变量与线程安全
自定义线程类中的实例变量针对其他线程可以有共享与不共享之分,这在多个线程之间进行交互时是很重要的一个技术点。
(1)不共享数据的情况如图:
下面通过一个示例来演示不共享数据的情况:
先创建MyThread.java类代码如下:
public class MyThread extends Thread {
private int count = 5;
public MyThread (String name) {
super () ;
this.setName (name) ;//设置线程名称
}
@Override
public void run() {
super. run() ;
while (count > 0) {
count--;
System. out.println("由”+ this.currentThread() .getName () +”计算,count=" + count) ;
}
}
}
再创建Run.java代码如下:
public class Run {
public static void main(String[] args) {
MyThread a = new MyThread("A");
MyThread b = new MyThread("B");
MyThread c = new MyThread("C");
a.start();
b.start();
c.start();
}
}
由以上代码可以看到,一共创建了3个线程,每个线程都有各自的count变量,自己减少自己的count变量的值。这样的情况就是变量不共享,此示例并不存在多个线程访问同一个实例变量的情况。
(2)共享数据的情况共享数据的情况如图所示:
共享数据的情况就是多个线程可以访问同一个变量。
线程同时在对count进行处理时,会产生了“非线程安全”问题。非线程安全主要是指多个线程对同一个对象中的同一个实例变量进行操作时会出现值被更改、值不同步的情况,进而影响程序的执行流程。而我们想要得到的打印结果却不是重复的,而是依次递减的。因此在某些JVM中,i-- 的操作要分成如下3步:
- 取得原有i值。
- 计算i-1。
- 对i进行赋值。
在这3个步骤中,如果有多个线程同时访问,那么一定会出现非线程安全问题。所以这时就需要使多个线程之间进行同步,也就是用按顺序排队的方式进行减1操作。
下面通过一个示例来看一下数据共享情况。
先创建MyThread.java类代码如下:
public class MyThread extends Thread {
private int count=5;
@Override
synchronized public void run() {
super.run() ;
count-- ;
//此处不要用for语句,因为使用同步后其他线程就得不到运行的机会了
System. out .println("由"+this. currentThread() .getName()+"计算,count="+count) ;
}
}
再创建运行类Run.java代码如下:
public class Run {
public static void main(String[] args) {
MyThread mythread=new MyThread() ;
Thread a=new Thread (mythread, "A") ;
Thread b=new Thread (mythread, "B") ;
Thread c=new Thread (mythread, "C") ;
Thread d=new Thread (mythread, "D") ;
Thread e=new Thread (mythread, "E") ;
a.start() ;
b.start() ;
c.start() ;
d.start() ;
e.start () ;
}
}
通过在run方法前加入synchronized关键字,使多个线程在执行run方法时,以排队的方式进行处理。当一个线程调用run前,先判断run方法有没有被上锁,如果上锁,说明有其他线程正在调用run方法,必须等其他线程对run方法调用结束后才可以执行run方法。这样也就实现了排队调用run方法的目的,也就达到了按顺序对count变量减1的效果了。