1、什么是进程?
程序是静止的,运行中的程序就是进程。
进程的三个特征:
1.动态性 : 进程是运行中的程序,要动态的占用内存,CPU和网络等资源。
2.独立性 : 进程与进程之间是相互独立的,彼此有自己的独立内存区域。
3.并发性 : 假如CPU是单核,同一个时刻其实内存中只有一个进程在被执行。CPU会分时轮询切换依次为每个进程服务,因为切换的速度非常快,给我们的感觉这些进程在同时执行,这就是并发性。
并行:同一个时刻同时有多个在执行。
2、什么是线程?
线程是属于进程的。一个进程可以包含多个线程,这就是多线程。
线程是进程中的一个独立执行单元。
线程创建开销相对于进程来说比较小。
线程也支持“并发性”。
3、线程的作用:
可以提高程序的效率,线程也支持并发性,可以有更多机会得到CPU。
多线程可以解决很多业务模型。
大型高并发技术的核心技术。
设计到多线程的开发可能都比较难理解。
4、相关API
1.public void setName(String name):给当前线程取名字。
2.public void getName():获取当前线程的名字。
-- 线程存在默认名称,子线程的默认名称是:Thread-索引。
-- 主线程的默认名称就是:main
3.public static Thread currentThread()
-- 获取当前线程对象,这个代码在哪个线程中,就得到哪个线程对象。
4、public static void sleep(long time): 让当前线程休眠多少毫秒再继续执行。
5、通过Thread类的有参数构造器为当前线程对象取名字。(不是API,但写到这了)
-- public Thread()
-- public Thread(String name):创建线程对象并取名字。
5、线程的创建方式
(1)直接定义一个类继承线程类Thread,重写run()方法,创建线程对象
调用线程对象的start()方法启动线程。
-- 1.定义一个线程类继承Thread类。
-- 2.重写run()方法
-- 3.创建一个新的线程对象。
-- 4.调用线程对象的start()方法启动线程。
继承Thread类的优缺点:
优点:编码简单。
缺点:线程类已经继承了Thread类无法继承其他类了,功能不能通过继承拓展(单继承的局限性)
public class _2022112Demo {
public static void main(String[] args) throws Exception {
Thread myThread = new MyThread();
myThread.start();
for (int i = 0; i < 100; i++) {
System.out.println("main线程输出" + i);
}
}
}
class MyThread extends Thread {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println("子线程输出" + i);
}
}
}
注意事项:1.线程的启动必须调用start()方法。否则当成普通类处理。
-- 如果线程直接调用run()方法,相当于变成了普通类的执行,此时将只有主线程在执行他们!
-- start()方法底层其实是给CPU注册当前线程,并且触发run()方法执行
2.建议线程先创建子线程,主线程的任务放在之后。否则主线程永远是先执行完!
(2)定义一个线程任务类实现Runnable接口,重写run()方法,创建线程任务对象,把线程任务对象包装成线程对象, 调用线程对象的start()方法启动线程。
-- 1.创建一个线程任务类实现Runnable接口。
-- 2.重写run()方法
-- 3.创建一个线程任务对象。
-- 4.把线程任务对象包装成线程对象
-- 5.调用线程对象的start()方法启动线程。
Thread的构造器:
-- public Thread(){}
-- public Thread(String name){}
-- public Thread(Runnable target){}
-- public Thread(Runnable target,String name){}
实现Runnable接口创建线程的优缺点:
缺点:代码复杂一点。
优点:
-- 线程任务类只是实现了Runnable接口,可以继续继承其他类,而且可以继续实现其他接口(避免了单继承的局限性)
-- 同一个线程任务对象可以被包装成多个线程对象
-- 适合多个多个线程去共享同一个资源(后面内容)
-- 实现解耦操作,线程任务代码可以被多个线程共享,线程任务代码和线程独立。
-- 线程池可以放入实现Runable或Callable线程任务对象。(后面了解)
注意:其实Thread类本身也是实现了Runnable接口的。
-- 不能直接得到线程执行的结果!
public class _2022112Demo {
public static void main(String[] args) throws Exception {
// Runnable myRunnable = new MyRunnable();
// Thread t1 = new Thread(myRunnable, "许嵩");
// t1.start();
// 上下这两种方法都行,只不过一种是另一种的简化罢了
Runnable myRunnable = new Runnable() {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName() + "子线程输出" + i);
}
}
};
Thread t2 = new Thread(myRunnable, "许嵩");
t2.start();
Thread.currentThread().setName("主线程");
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName() + "线程输出" + i);
}
}
}
class MyRunnable implements Runnable {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName() + "子线程输出" + i);
}
}
}
(3)实现Callable接口(要是需要得到线程的返回结果,可以用这个)
-- 1,定义一个线程任务类实现Callable接口 , 申明线程执行的结果类型。
-- 2,重写线程任务类的call方法,这个方法可以直接返回执行的结果。
-- 3,创建一个Callable的线程任务对象。
-- 4,把Callable的线程任务对象包装成一个未来任务对象。
-- 5.把未来任务对象包装成线程对象。
-- 6.调用线程的start()方法启动线程
优缺点:
优点:全是优点。
-- 线程任务类只是实现了Callable接口,可以继续继承其他类,而且可以继续实现其他接口(避免了单继承的局限性)
-- 同一个线程任务对象可以被包装成多个线程对象
-- 适合多个多个线程去共享同一个资源(后面内容)
-- 实现解耦操作,线程任务代码可以被多个线程共享,线程任务代码和线程独立。
-- 线程池可以放入实现Runable或Callable线程任务对象。(后面了解)
-- 能直接得到线程执行的结果!
缺点:编码复杂。
import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;
public class _2022112Demo {
public static void main(String[] args) throws Exception {
Callable<String> callable = new MyCallable();
FutureTask<String> task = new FutureTask<>(callable);
Thread t = new Thread(task);
t.start();
Thread.currentThread().setName("主线程");
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName() + "线程输出" + i);
}
try {
System.out.println(task.get());
} catch (Exception e) {
e.printStackTrace();
}
}
}
class MyCallable implements Callable<String> {
@Override
public String call() throws Exception {
return "我可以得到返回结果";
}
}
6、线程安全问题:多个线程同时操作同一个共享资源的时候可能会出现线程安全问题。
线程同步的作用:就是为了解决线程安全问题的方案。
线程同步解决线程安全问题的核心思想:
让多个线程实现先后依次访问共享资源,这样就解决了安全问题。
线程同步的做法:加锁
是把共享资源进行上锁,每次只能一个线程进入访问完毕以后,其他线程才能进来。
线程同步的方式有三种:
(1)同步代码块。
作用:把出现线程安全问题的核心代码给上锁,每次只能一个线程进入
执行完毕以后自动解锁,其他线程才可以进来执行。
格式:
synchronized(锁对象){
// 访问共享资源的核心代码
}
锁对象:理论上可以是任意的“唯一”对象即可。
原则上:锁对象建议使用共享资源。
-- 在实例方法中建议用this作为锁对象。此时this正好是共享资源!必须代码高度面向对象
-- 在静态方法中建议用类名.class字节码作为锁对象。
(2)同步方法。
作用:把出现线程安全问题的核心方法给锁起来,
每次只能一个线程进入访问,其他线程必须在方法外面等待。
用法:直接给方法加上一个修饰符 synchronized.
原理: 同步方法的原理和同步代码块的底层原理其实是完全一样的,只是
同步方法是把整个方法的代码都锁起来的。
同步方法其实底层也是有锁对象的:
如果方法是实例方法:同步方法默认用this作为的锁对象。
如果方法是静态方法:同步方法默认用类名.class作为的锁对象。
(3)lock显示锁。
java.util.concurrent.locks.Lock机制提供了比synchronized代码块和synchronized方法更广泛的锁定操作,
同步代码块/同步方法具有的功能Lock都有,除此之外更强大
Lock锁也称同步锁,加锁与释放锁方法化了,如下:
- `public void lock() `:加同步锁。
- `public void unlock()`:释放同步锁。