进程和线程
进程是程序的一次动态执行过程,它需要经历从代码加载,代码执行到执行完毕的一个完整的过程,这个过程也是进程本身从产生,发展到最终消亡的过程。多进程操作系统能同时达运行多个进程(程序),由于 CPU 具备分时机制,所以每个进程都能循环获得自己的CPU 时间片。由于 CPU 执行速度非常快,使得所有程序好像是在同时运行一样。
多线程是实现并发机制的一种有效手段。进程和线程一样,都是实现并发的一个基本单位。线程是比进程更小的执行单位,线程是进程的基础之上进行进一步的划分。所谓多线程是指一个进程在执行过程中可以产生多个更小的程序单元,这些更小的单元称为线程,这些线程可以同时存在,同时运行,一个进程可能包含多个同时执行的线程。
如何玩转多线程
在Java的JDK开发包中,已经自带了对多线程技术的支持,可以很方便地进行多线程编程。实现多线程编程的方式有两种,一种是继承 Thread 类,另一种是实现 Runnable 接口。使用继承 Thread 类创建线程,最大的局限就是不能多继承,所以为了支持多继承,完全可以实现 Runnable 接口的方式。需要说明的是,这两种方式在工作时的性质都是一样的,没有本质的区别。
实现 Runnable 接口
/**
* @author kismet
* @date 2020/8/10 10:56 上午
*/
public class MyThread implements Runnable{ //上线Runnable接口,作为线程的实现类
private String name; //标识线程名称
public MyThread(String name) { //通过构造方法配置name属性
this.name = name;
}
public void run() { //重写run方法,作为线程的操作主体
for (int i = 0; i < 10; i++) {
System.out.println(name+" 运行,i="+i);
}
}
}
class RunnableText{
public static void main(String[] args) {
MyThread myThread1 = new MyThread("线程1"); //实例化对象
MyThread myThread2 = new MyThread("线程2");
MyThread myThread3 = new MyThread("线程3");
Thread thread1 = new Thread(myThread1); //实例化thread类对象
Thread thread2 = new Thread(myThread2);
Thread thread3 = new Thread(myThread3);
thread1.start(); //启动多线程
thread2.start();
thread3.start();
}
}
继承 Thread 类
/**
* @author kismet
* @date 2020/8/10 11:10 上午
*/
public class MyThread1 extends Thread{ //基础thread类,作为线程的实现类
private String name; //标识线程名称
public MyThread1(String name) { //通过构造方法配置name属性
this.name = name;
}
public void run() { //重写run方法,作为线程的操作主体
for (int i = 0; i < 10; i++) {
System.out.println(name+" 运行,i="+i);
}
}
}
class ThreadText{
public static void main(String[] args) {
MyThread1 myThread1 = new MyThread1("线程1");
MyThread1 myThread2 = new MyThread1("线程2");
MyThread1 myThread3 = new MyThread1("线程3");
myThread1.start();
myThread2.start();
myThread3.start();
}
}
从程序可以看出,现在的两个线程对象是交错运行的,哪个线程对象抢到了 CPU 资源,哪个线程就可以运行,所以程序每次的运行结果肯定是不一样的。
start()方法被用来启动新创建的线程,而且start()内部 调用了run()方法,这和直接调用run()方法的效果不一样。当你调用run()方法的时候,只会是在原来的线程中调用,没有新的线程启 动,start()方法才会启动新线程。
线程的状态变化
要想实现多线程,必须在主线程中创建新的线程对象。任何线程一般具有5种状态,即创建,就绪,运行,阻塞,终止。
-
创建状态
在程序中用构造方法创建了一个线程对象后,新的线程对象便处于新建状态,此时它已经有了相应的内存空间和其他资源,但还处于不可运行状态。新建一个线程对象可采用Thread 类的构造方法来实现,例如 “Thread thread=new Thread()”。
-
就绪状态
新建线程对象后,调用该线程的 start() 方法就可以启动线程。当线程启动时,线程进入就绪状态。此时,线程将进入线程队列排队,等待 CPU 服务,这表明它已经具备了运行条件。
-
运行状态
当就绪状态被调用并获得处理器资源时,线程就进入了运行状态。此时,自动调用该线程对象的 run() 方法。run() 方法定义该线程的操作和功能。
-
阻塞状态
一个正在执行的线程在某些特殊情况下,如被人为挂起或需要执行耗时的输入/输出操作,会让 CPU 暂时中止自己的执行,进入阻塞状态。在可执行状态下,如果调用sleep(),suspend(),wait() 等方法,线程都将进入阻塞状态,发生阻塞时线程不能进入排队队列,只有当引起阻塞的原因被消除后,线程才可以转入就绪状态。
-
死亡状态
线程调用 stop() 方法时或 run() 方法执行结束后,即处于死亡状态。处于死亡状态的线程不具有继续运行的能力。
Java 程序每次运行至少启动两个线程,每当使用 Java 命令执行一个类时,实际上都会启动一个 JVM,每一个JVM实际上就是在操作系统中启动一个线程,Java 本身具备了垃圾的收集机制。所以在 Java 运行时至少会启动两个线程,一个是 main 线程,另外一个是垃圾收集线程。
线程的操作方法
线程的强制运行
在线程操作中,可以使用 join() 方法让一个线程强制运行,线程强制运行期间,其他线程无法运行,必须等待此线程完成之后才可以继续执行。
线程的休眠
在程序中允许一个线程进行暂时的休眠,直接使用 Thread.sleep() 即可实现休眠。
中断线程
当一个线程运行时,另外一个线程可以直接通过interrupt()方法中断其运行状态。
后台线程
在 Java 程序中,只要前台有一个线程在运行,则整个 Java 进程都不会消失,所以此时可以设置一个后台线程,这样即使 Java 线程结束了,此后台线程依然会继续执行,要想实现这样的操作,直接使用 setDaemon() 方法即可。
线程的优先级
在 Java 的线程操作中,所有的线程在运行前都会保持在就绪状态,那么此时,哪个线程的优先级高,哪个线程就有可能会先被执行。
t1.setPriority(Thread.MIN_PRIORITY) ; // 优先级最低
t2.setPriority(Thread.MAX_PRIORITY) ; // 优先级最高
t3.setPriority(Thread.NORM_PRIORITY) ; // 优先级最中等
从程序的运行结果中可以观察到,线程将根据其优先级的大小来决定哪个线程会先运行,但是需要注意并非优先级越高就一定会先执行,哪个线程先执行将由 CPU 的调度决定。
线程的礼让
在线程操作中,也可以使用 yield() 方法将一个线程的操作暂时让给其他线程执行
同步以及死锁
一个多线程的程序如果是通过 Runnable 接口实现的,则意味着类中的属性被多个线程共享,那么这样就会造成一种问题,如果这多个线程要操作同一个资源时就有可能出现资源同步问题。
代码块同步
synchronized(同步对象){
需要同步的代码
}
方法同步
synchronized 方法返回值 方法名称(参数列表){
}
/**
* @author kismet
* @date 2020/8/10 11:10 上午
*/
public class MyThread1 extends Thread { //基础thread类,作为线程的实现类
private int ticket = 100; // 假设一共有5张票
@Override
public void run() {
for (int i = 0; i < 1000; i++) {
synchronized (this) { // 要对当前对象进行同步
if (ticket > 0) { // 还有票
try {
Thread.sleep(300); // 加入延迟
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("卖票:ticket = " + ticket--);
}
}
}
}
}
class ThreadText {
public static void main(String[] args) {
MyThread1 myThread1 = new MyThread1();
Thread thread1 = new Thread(myThread1);
Thread thread2 = new Thread(myThread1);
Thread thread3 = new Thread(myThread1);
thread1.start();
thread2.start();
thread3.start();
}
}
public class MyThread1 extends Thread { //基础thread类,作为线程的实现类
private int ticket = 100; // 假设一共有5张票
@Override
public void run() {
for (int i = 0; i < 100; i++) {
this.sale(); // 调用同步方法
}
}
public synchronized void sale() { // 声明同步方法
if (ticket > 0) { // 还有票
try {
Thread.sleep(300); // 加入延迟
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("卖票:ticket = " + ticket--);
}
}
}
死锁
同步可以保证资源共享操作的正确性,但是过多同步也会产生问题。多个线程相互等待,等待对方执行完,结果都没执行,这实际上就是死锁的概念。