一、进程和线程
1、进程
(1)概念
进程 (Process) 是计算机中的程序关于某数据集合上的一次运行活动
是系统进行资源分配的基本单位
简单理解:程序的执行过程(正在运行的应用程序)
(2)特性
独立性:每一个进程都有自己的空间,在没有经过进程本身允许的情况下,一个进程不可以直接访问其它的的进程空间
动态性:进程是动态产生,动态消亡的
并发性:任何进程都可以同其它进程一起并发执行
Tips:
并行:在同一时刻,有多个指令在多个CPU上【同时】执行
并发:在同一时刻,有多个指令在单个CPU上【交替】执行
多进程同时工作:对于一个CPU(单核),它是在多个进程间轮换执行的
2、线程
(1)概念
线程(Thread):进程可以同时执行多个任务,每个任务就是线程
(2)多线程的意义
提高执行效率;同时处理多个任务
随着处理器上的核心数量越来越多,现在大多数计算机都比以往更加擅长并行计算
但是,一个线程,在一个时刻,只能运行在一个处理器核心上
Java程序也是一个进程,如果是一个单线程程序,则无法调动处理器的多个核心
二、Java中开启线程的方式
Tips:Java程序默认是多线程的,一条主线程,一条垃圾回收线程
1、法一:继承Thread类
步骤:
(1)编写一个类继承Thread类
(2)重写run方法
(3)将线程任务写在run方法中
(4)创建线程对象
(5)调用start方法开启线程
注意:直接调用run方法并不能开启线程
2、法二:实现Runnable接口
(扩展性更好)
步骤:
(1)编写一个类实现Runnable接口
(2)重写run方法
(3)将线程任务写在run方法中
(4)创建线程任务资源对象
(5)创建线程对象,将资源传入
(6)使用线程对象调用start方法开启线程
public class ThreadDemo2 {
public static void main(String[] args) {
// (4)创建线程任务资源对象
MyRunnable mr = new MyRunnable();
// (5)创建线程对象,将资源传入
Thread t1 = new Thread(mr);
// (6)使用线程对象调用start方法开启线程
t1.start();
for (int i = 0; i < 100; i++) {
System.out.println("main" + i);
}
}
}
//(1)编写一个类实现Runnable接口
class MyRunnable implements Runnable {
// (2)重写run方法
@Override
public void run() {
// (3)将线程任务写在run方法中
for (int i = 0; i < 100; i++) {
System.out.println("MyRunnable" + i);
}
}
}
3、法三:实现Callable接口
(线程任务有返回值)
步骤:
(1)编写一个类实现Callable接口
(2)重写call方法
(3)将线程任务写在call方法中
(4)创建线程任务资源对象
(5)创建线程任务对象,封装线程资源
(6)创建线程对象,传入线程任务
(7)使用线程对象调用start方法开启线程
public class ThreadDemo3 {
public static void main(String[] args) throws ExecutionException, InterruptedException {
MyCallable mc = new MyCallable();
FutureTask<Integer> task = new FutureTask<>(mc);
Thread thread = new Thread(task);
thread.start();
Integer result = task.get(); // 获取线程任务的返回值
System.out.println(result);
}
}
class MyCallable implements Callable<Integer> {
@Override
public Integer call() throws Exception {
int sum = 0;
for (int i = 0; i <= 100; i++) {
sum += i;
}
return sum;
}
}
三、线程的相关方法
String getName() | |
void setName(String name) | |
static Thread currentThread() | |
static void sleep(long time) | |
setPriority(int newPriority) | |
final int getPriority() | |
final void setDaemon(boolean on) |
Tips:线程的调度方式分为抢占式调度(随机)和非抢占式调度(轮流)
Java 采用的方式是抢占式调度
提高线程的优先级可以提高该线程抢到CPU的概率
四、线程安全和同步
1、安全问题出现的条件
是多线程环境
有共享数据
有多条语句操作共享数据
2、同步技术
将多条语句操作共享数据的代码给锁起来,让任意时刻只能有一个线程可以执行
(1)同步代码块
格式:
synchronized(锁对象) {
多条语句操作共享数据的代码
}
示例:
public class TicketDemo {
public static void main(String[] args) {
// 只new了一个TicketTask对象,三个线程共享一份数据
TicketTask ticket = new TicketTask();
Thread t1 = new Thread(ticket);
Thread t2 = new Thread(ticket);
Thread t3 = new Thread(ticket);
t1.start();
t2.start();
t3.start();
}
}
class TicketTask implements Runnable {
private int tickets = 2000;
@Override
public void run() {
while (true) {
// 建议使用字节码文件作为锁对象
synchronized (TicketTask.class) {
if (tickets <= 0) {
break;
}
System.out.println(Thread.currentThread().getName() + " sold " + tickets);
tickets--;
}
}
}
}
Tips:锁对象可以是任意对象,但是需要保证多条线程的锁对象,是同一把锁
同步可以解决多线程的数据安全问题,但是也会降低程序的运行效率
(2)同步方法
在方法的返回值类型前面加入 synchronized 关键字
该方法里的代码就变成同步的
静态方法的锁对象是字节码对象,非静态方法的锁对象是 this
(3)Lock 锁
使用 Lock 锁,可以更清晰地看到哪里加了锁,哪里释放了锁
Lock 是接口,无法直接创建对象
public ReentrantLock() | |
void lock() | |
void unlock(); |
3、死锁
两个或者多个线程互相持有对方所需要的资源
导致这些线程处于等待状态,无法前往执行
产生死锁的情况:同步嵌套