进程是一个具有一定独立功能的程序在一个数据集上的一次动态执行的过程,是操作系统进行资源分配和调度的一个独立单位,是应用程序运行的载体。进程是一种抽象的概念,从来没有统一的标准定义。进程一般由程序,数据集合和进程控制块三部分组成。程序用于描述进程要完成的功能,是控制进程执行的指令集;数据集合是程序在执行时所需要的数据和工作区;程序控制块包含进程的描述信息和控制信息是进程存在的唯一标志
进程具有的特征:
在早期的操作系统中并没有线程的概念,进程是拥有资源和独立运行的最小单位,也是程序执行的最小单位。任务调度采用的是时间片轮转的抢占式调度方式,而进程是任务调度的最小单位,每个进程有各自独立的一块内存,使得各个进程之间内存地址相互隔离。
后来,随着计算机的发展,对CPU的要求越来越高,进程之间的切换开销较大,已经无法满足越来越复杂的程序的要求了。于是就发明了线程,线程是程序执行中一个单一的顺序控制流程,是程序执行流的最小单元,是处理器调度和分派的基本单位。一个进程可以有一个或多个线程,各个线程之间共享程序的内存空间(也就是所在进程的内存空间)。一个标准的线程由线程ID,当前指令指针PC,寄存器和堆栈组成。而进程由内存空间(代码,数据,进程空间,打开的文件)和一个或多个线程组成。
点此传送:创建线程的几种详细方法可以参考我之前写的博客
public class MyThread extends Thread{
@Override
public void run() {
//第一种:直接获取线程名称
String name=getName();
System.out.println(name);//Thread-0
//第二种:获取当前正在执行的线程对象的引用
Thread t=Thread.currentThread();
System.out.println(t);//Thread[Thread-0,5,main]
//再用getName获取线程名
String name1=t.getName();
System.out.println(name1);//Thread-0
//或用链式编程一步到位
System.out.println(Thread.currentThread().getName());//Thread-0
}
}
public class Main {
public static void main(String[] args) {
MyThread thread=new MyThread();
thread.start();
System.out.println(Thread.currentThread().getName());//main
//也可直接获取主线程名称
}
}
public class MyThread extends Thread{
public MyThread(){
//创建无参构造方法
}
public MyThread(String name) {
super(name);//创建有参构造方法并传给父类
}
@Override
public void run(){
System.out.println(Thread.currentThread().getName());
}
}
public class Main {
public static void main(String[] args) {
//开启多线程
MyThread thread=new MyThread();
thread.setName("张三");
thread.start();//张三
new MyThread("李四").start();//李四
}
}
使正在执行的线程以指定毫秒数暂停,计时结束后继续执行
public static void sleep(long millis)
public class Main {
public static void main(String[] args){
for(int i=0;i<10;i++){
//需要用try/catch或者throws进行异常处理
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(i);//每过一秒打印一个数字
}
}
}
若是多个线程对象同时执行,且这几个线程对象执行的方法体是相同的,则执行的时间有先有后,睡眠的时间也有先有后,其中有适当的延迟操作
Thread类里提供了两种中断执行处理方法
判断线程是否被中断:public boolean isInterrupted
中断线程执行:public void interrupted
所有正在执行的线程是可以被中断的,中断线程必须要进行异常的处理
在强制执行时必须要获取强制执行对象才可以进行join()的调用
public class Main {
public static void main(String[] args) {
Thread mainthread = Thread.currentThread();
new Thread(() -> {
for (int i = 0; i < 5; i++) {
if (i == 2) {
try {
mainthread.join();//强制执行主线程,执行完后才会执行次线程
} catch (InterruptedException e) {
e.printStackTrace();
}
}
try {
Thread.sleep(500);//休眠便于观察结果
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("次线程" + i);
}
}).start();
for (int i = 0; i < 5; i++) {
try {
Thread.sleep(500);//休眠便于观察结果
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("主线程" + i);
}
}
}
/*输出结果为:
主线程0
次线程0
次线程1
主线程1
主线程2
主线程3
主线程4
次线程2
次线程3
次线程4 */
举个例子:StringBuilder与StringBuffer构造方法基本相同,但为什么要分为两个呢,那是因为StringBuffer中所有构造方法都加上了synchronized关键字,所以StringBuilder是非线程安全的,StringBuffer是线程安全的
synchronized锁的是括号里的对象,而不是代码,其次,对于非静态的synchronized方法,锁的是对象本身也就是this。当synchronized锁住一个对象之后,别的线程如果想要获取锁对象,那么就必须等这个线程执行完释放锁对象之后才可以,否则会一直处于等待状态。
加入同步锁之前:
public class Ticket{
public static void main(String[] args) {
MyThread run = new MyThread();
Thread t0 = new Thread(run);
Thread t1 = new Thread(run);
Thread t2 = new Thread(run);
t0.start();
t1.start();
t2.start();
}
}
class MyThread implements Runnable {
private int ticket = 50;
Object obj = new Object();
public void run() {
while (ticket > 0) {
if (ticket > 0) {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "正在卖" + ticket + "张票");
ticket--;
}
}
}
}
/*输出:
Thread-2正在卖50张票
Thread-0正在卖50张票
Thread-1正在卖50张票
Thread-2正在卖47张票
Thread-0正在卖47张票
Thread-1正在卖47张票
Thread-0正在卖44张票
....为方便演示,已省略中间结果
Thread-2正在卖5张票
Thread-0正在卖2张票
Thread-2正在卖2张票
Thread-1正在卖2张票
Thread-0正在卖-1张票
*/
通过观察输出结果可以发现,同一张票可能会被多次售卖,还有可能出现-1结果
为防止该现象发生,可以加入同步锁:
public class Ticket
{
public static void main(String[] args) {
MyThread run=new MyThread();
Thread t0=new Thread(run);
Thread t1=new Thread(run);
Thread t2=new Thread(run);
t0.start();//开启3个线程
t1.start();
t2.start();
}
}
class MyThread implements Runnable {
private int ticket=50;//开始有30张票
Object obj=new Object();
public void run() {
while(ticket > 0){
synchronized (obj){
//加入同步锁
if(ticket>0){
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"正在卖"+ticket+"张票");
ticket--;
}
}
}
}
}
/*输出:
Thread-0正在卖50张票
Thread-0正在卖49张票
Thread-0正在卖48张票
Thread-0正在卖47张票
Thread-0正在卖46张票
Thread-0正在卖45张票
.......此次省略中间结果
Thread-0正在卖5张票
Thread-0正在卖4张票
Thread-0正在卖3张票
Thread-0正在卖2张票
Thread-2正在卖1张票
*/
Java中的线程分为以下几个状态:
新建:
创建一个Java线程常见的有两种方式:
继承Thread类和实现Runnable接口这两种方式。
运行:
线程创建后仅仅占有了内存资源,在JVM管理的线程中还没有该线程,该线程必须调用start方法通知JVM,这样JVM
就会知道又有一个新的线程排队等候了。如果当前线程轮到了CPU的使用权限的话,当前线程就会继续执行。
阻塞状态:
阻塞状态与等待状态的区别是阻塞状态在等着获取一个排他锁,这个时间在另外一个线程放弃这个锁时发生,而等待状态是在等待时间或等待被唤醒动作时发生。在程序等待进入同步区时,线程进入阻塞状态。
休眠:
处于这种状态的线程也不会被CPU分配执行时间,不过无须等待被显示唤醒,而是在一定时间过后会由系统自动唤醒,以下方法会让线程陷入限期等待状态:sleep,设置了Timeout的wait和join等。
无限等待:
处于这种状态的线程不会被分配CPU执行时间,它们要等待其他线程显示唤醒。以下方法会让线程陷入无限等待状态:没有设置Timeout参数的wait()方法,没有设置Timeout参数的join()方法等。
死亡:
死亡的线程不再具有执行能力,有两种可能进入死亡状态
当进程已分配到除CPU以外的所有必要资源后,只要在获得CPU,便可立即执行,进程这时的状态就称为就绪状态。在一个系统中处于就绪状态的进程可能有多个,通常将他们排成一个队列,称为就绪队列。
执行状态:
进程已获得CPU,其程序正在执行。在单处理机系统中,只有一个进程处于执行状态;再多处理机系统中,则有多个进程处于执行状态。
阻塞状态:
正在执行的进程由于发生某事件而暂时无法继续执行时,便放弃处理机而处于暂停状态,亦即程序的执行受到阻塞,把这种暂停状态称为阻塞状态,有时也称为等待状态或封锁状态。
1.volatile 关键字
基于 volatile 关键字来实现线程间相互通信是使用共享内存的思想,大致意思就是多个线程同时监听一个变量,当这个变量发生变化的时候 ,线程能够感知并执行相应的业务。这也是最简单的一种实现方式
2.使用Object类的wait() 和 notify() 方法
众所周知Object类提供了线程间通信的方法:wait()、notify()、notifyaAl(),它们是多线程通信的基础,而这种实现方式的思想自然是线程间通信。
注意: wait和 notify必须配合synchronized使用,wait方法释放锁,notify方法不释放锁
3.使用JUC工具类 CountDownLatch
jdk1.5之后在java.util.concurrent包下提供了很多并发编程相关的工具类,简化了我们的并发编程代码的写,***CountDownLatch***基于AQS框架,相当于也是维护了一个线程间共享变量state
4.使用 ReentrantLock 结合 Condition
5.基本LockSupport实现线程间的阻塞和唤醒
LockSupport 是一种非常灵活的实现线程间阻塞和唤醒的工具,使用它不用关注是等待线程先进行还是唤醒线程先运行,但是得知道线程的名字。
参考自–>线程间通信的几种实现方式
参考自–> 进程间通讯的7种方式