1、什么是进程
进程是程序再一个数据集合上运行的过程,它是系统进行资源分配和调度的一个独立单位,在计算机系统中,独立运行的一个程序,这个东西叫做进程。例如:QQ 、微信 、360。
独立性:
每个软件都是独立的,使用的资源都是系统分配的。CPU 内存 网络 磁盘使用
并发性:
操作系统中,可以同事运行多个程序,多个进程。
互斥性:
每个程序直接都是没有干扰,游戏助手,网游加速器,英雄联盟,绝地求生
动态性:
程序在运行的时候,使用的系统资源是随着当前程序运行所需在不断变化的
2、什么是线程
线程是基于进程,例如我们使用的电脑管家,可以同时运行,病毒查杀,一键加速,垃圾清理等,每一个功能都是一个单独的进程,个人理解的是,线程是进程中的某一个功能
线程的特征
资源抢占:
每个线程都是抢占进程从系统中分配的资源、CPU、内存、网络、硬盘等
每个线程都会在进程的执行时间以内,抢占执行资源
资源共享
进程中分配的系统资源是对于当前进程内的线程,是共享的。
3、线程的创建和启动
Java使用Thread类代表线程,所有线程对象都是Thread类或其子类的实例
3.1、继承Thread类创建并启动线程21
public class FirstThread extends Thread {
private int i;
@Override
public void run() {
for (int i=0 ; i<=10 ; i++){
//返回当前线程的名称
String threadName = getName()+"i";
System.out.println(threadName);
}
}
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
//获取当前线程
System.out.println(Thread.currentThread().getName()+"i");
//创建并启动第一个线程
FirstThread firstThread01 = new FirstThread();
firstThread01.start();
//创建并启动第二个线程
FirstThread firstThread02 = new FirstThread();
firstThread02.start();
}
}
}
继承Thread类,重写run()方法,也就是这个线程需要完成的任务,线程对象的start()方法来启动该线程
3.2、实现Runnable接口创建线程类(推荐方法)
public class FirstRunnable implements Runnable {
public void run() {
for (int i=0 ; i<=10 ; i++){
//返回当前线程的名称(使用Runnable接口创建线程的时候,不能直接使用getName)
String threadName = Thread.currentThread().getName()+""+i;
System.out.println(threadName);
}
}
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
//获取当前线程
System.out.println(Thread.currentThread().getName()+""+i);
FirstRunnable firstRunnable = new FirstRunnable();
//创建并启动二个单独的线程
new Thread(firstRunnable,"子线程名称0").start();
new Thread(firstRunnable,"子线程名称1").start();
}
}
}
实现Runnable接口,同意的在run()方法中编写需要线程实现的操作,采用线程对象的start()方法启动。
3.2.1、Thread类常用方法
Constructor 构造方法
Thread();
创建一个Thread类对象,没有任何的Runnable接口实现类对象和线程名,线程的名字是默认名
Thread(Runnable target);
创建一个Thread类对象,使用Runnable接口的实现类对象作为当前方法参数,也是执行的线程方法
Thread(String name);
创建一个Thread类对象,给予当前线程一个名字
Thread(Runnable target, String name);
创建一个Thread类对象,使用Runnable接口的实现类来作为线程方法,并且起一个线程的名字
Method 成员方法
void setName(String name);
设置当前线程的名字
String getName();
获取当前线程的名字
void setPriority(int newPriority);
设置线程的优先级,线程的优先级是从 1 ~ 10 1最低, 10最高,默认为5
线程优先级越高,执行的概率越高,但是不能保证一定执行!!!
int getPriority();
获取当前线程的优先级
static Thread currentThread();
获取当前方法所处代码块的线程类对象
static void sleep(int ms);
线程进入指定时间的休眠状态
你认为哪一个方式更好?
自定义线程类遵从Runnable接口更合适
因为Java语言是一门单继承语言,如果一个类遵从的Thread类,就无法继承于其他类,可能会增加项目逻辑复杂度,但是Java语言可以遵从多个接口,并不会影响其他逻辑。
3.3、实现Callable接口通过FutureTask包装器来创建Thread线程(不常用)
import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;
public class FirstCallable {
public static void main(String[] args) {
//创建Callable对象
FirstCallable rt = new FirstCallable();
//先使用Lambda表达式创建Callable<Integer>对象
//使用FutureTask来包装Callable对象
FutureTask<Integer> task = new FutureTask<>((Callable<Integer>)()->{
int i=0;
for(;i<100;i++) {
System.out.println(Thread.currentThread().getName()+"循环变量i的值:"+i);
}
return i;
});
for(int i=0;i<100;i++) {
System.out.println(Thread.currentThread().getName()+" 的循环变量i的值:"+i);
if(i==20) {
//实质还是以Callable对象来创建并启动的
new Thread(task,"有返回值的线程").start();
/*Thread t = new Thread(task);
t.start();*/
}
}
try {
System.out.println("子线程的返回值:"+task.get());
} catch (Exception e) {
e.printStackTrace();
}
}
}
4、synchronized 同步代码块
一般用于处理在一个程序中,存在不同线程之间的共享资源问题。
synchronized 关键字用于修饰函数和代码块,以实现同步,当多个线程在执行被synchronized 修饰的代码的时候,会等待上一个线程处理完毕之后,再执行被修饰的代码。加锁的代码块,被称为“互斥区”或者“临界区”。synchronized 加到static静态方法上是给Class类上锁。
注:此图来源 https://blog.csdn.net/qq_37438740/article/details/81109226
这里我们假设一个场景,例如电影院出售电影票。出售100张电影票,通过三个渠道去销售,淘票,猫眼,美团,共出售100张电影票,但是不能出现超卖或者一张票多次出售的情况。
我们先来看一下对抢票操作不加synchronized 的时候情况
public class SynchronizedCodeDemo implements Runnable {
//100张电影票
private static int tickets = 100;
@Override
public void run() {
for (int i = 0; i < 100; i++) {
getticket();
System.out.println(Thread.currentThread().getName()+"抢到了电影票____目前电影票的数量为"+tickets);
}
}
public static void main(String[] args) {
SynchronizedCodeDemo synchronizedCodeDemo = new SynchronizedCodeDemo();
new Thread(synchronizedCodeDemo,"淘票").start();
new Thread(synchronizedCodeDemo,"猫眼").start();
new Thread(synchronizedCodeDemo,"美团").start();
}
//抢票方法
public void getticket(){
tickets -=1;
}
}
我们可以看到出现了一票多卖,或者超卖的现象,这时候我们就需要对购买票的操作加上锁。
public class SynchronizedCodeDemo implements Runnable {
//100张电影票
private static int tickets = 100;
@Override
public void run() {
for (int i = 0; i < 100; i++) {
boolean isSuccessTicket = getticket();
if (isSuccessTicket){
System.out.println(Thread.currentThread().getName()+"抢到了电影票____目前电影票的数量为"+tickets);
}else {
System.out.println(Thread.currentThread().getName()+"没有抢到电影票____目前电影票的数量为"+tickets);
}
}
}
public static void main(String[] args) {
SynchronizedCodeDemo synchronizedCodeDemo = new SynchronizedCodeDemo();
new Thread(synchronizedCodeDemo,"淘票").start();
new Thread(synchronizedCodeDemo,"猫眼").start();
new Thread(synchronizedCodeDemo,"美团").start();
}
//抢票方法
public boolean getticket(){
synchronized("加锁"){
//判断电影票数量是否足够
if (tickets >0){
tickets -=1;
return true;
}
return false;
}
}
}
可以看到,我们使用synchronized加锁以后,可以看到避免了一票多卖,或者超卖的现象。
注意:
- 一旦有一个线程进入加锁的代码块,其他线程只能在锁对象之外等待,该线程执行完成,锁打开,所有执行该同步代码块的线程,再重新抢占
- Sleep方法不会打开锁对象
- 同步代码块包含的代码越少越好