多线程的概述。
什么是进程?
程序是静止的,运行中的程序就是进程。
进程的三个特点:
1.动态性:进程动态的占用主机中的内存,CPU , 网络 , 磁盘等处理自己的功能。
2.并发性:
假如一个服务器只有一个CPU,而CPU是单核的。
那么同一个时刻其实只有一个进程在被CPU执行,
CPU会分时轮询切换的为每个进程服务,给我们的感觉好像这些进程在同
时执行一样 这就是进程的并发。(抢占式并发)。
进程的执行会出现随机性。
并发:在同一个时刻只有一个进程在执行(针对单核)
并行: 同一个时刻多个进程在同时执行!
3.独立性:
进程都有自己独立的内存区域,互相之间的执行是不会干扰的。
什么是线程?
线程属于进程的。
一个进程可以包含多个线程,这就是多线程。
线程的特点:
动态性:线程也要动态的占用内存,CPU…
并发性:多个线程会并发抢占CPU执行自己。
多线程的优势:
线程消耗的资源比进程小,开销小于进程。
多线程可以提高程序的效率。
多线程可以设计很好的业务模型,有时候还必须要用多线程。
小结:
线程属于进程
多线程是并发执行的,所以多个线程抢占CPU执行会出现随机性执行!!
线程的创建有三种方式;
方式一:
步骤:通过继承Thread,重写run()方法!调用start方法
优点:编码简单。
缺点:线程类已经继承了Thread类,无法再继承其他类 ,本身的功能就不够强大。
不适合做线程池,不能得到线程执行的结果。
public class ThreadDemo01 {
// 进程:ThreadDemo01
// 主线程:main方法。
public static void main(String[] args) {
// 创建一个子线程
MyThread t = new MyThread();
// 线程启动必须用start方法,如果用run方法,就失去了线程特征。
t.start();
for (int i = 0 ; i < 5 ; i++ ){
System.out.println("main线程输出:"+i);
}
}
}
// 线程类,线程类适用于创建线程的
class MyThread extends Thread{
@Override
public void run() {
for (int i = 0 ; i < 5 ; i++ ){
System.out.println("子线程输出:"+i);
}
}
}
方式二:Runnable接口
步骤:
(1)定义一个线程任务类实现Runnable接口。
(2)重写run()方法。
(3)创建线程任务类对象,创建一个Thread线程对象包装线程任务对象。
(4)启动线程对象。
创建方式二的优缺点:
缺点:编码相对复杂,不能得到线程执行的结果。
优点:线程任务对象只是实现了Runnable接口,可以继续继承其他类,
可以继续实现其他接口。
线程任务对象的本身的功能可以增强,
非常适合做线程池。
非常适合做共享资源的访问。
public class ThreadDemo01 {
// 进程:ThreadDemo01
// 主线程:main方法。
public static void main(String[] args) {
// 创建一个子线程
MyThread t = new MyThread();
// 线程启动必须用start方法,如果用run方法,就失去了线程特征。
t.start();
for (int i = 0 ; i < 5 ; i++ ){
System.out.println("main线程输出:"+i);
}
}
}
// 线程类,线程类适用于创建线程的
class MyThread extends Thread{
@Override
public void run() {
for (int i = 0 ; i < 5 ; i++ ){
System.out.println("子线程输出:"+i);
}
方式三:Callable接口
步骤:
(1)创建一个线程任务对象,实现Callable接口,申明线程执行的结果类型。
重写call方法。
(2)创建一个未来任务对象FutureTask对象,包装Callable实现类对象。
(3)创建一个线程对象Thread来包装FutureTask对象
(4)启动线程。
(5)获取线程执行的结果。
创建方式三的优缺点:
缺点:编码复杂。
优点:
线程任务对象只是实现了Callable接口,可以继续继承其他类,
可以继续实现其他接口。
线程任务对象的本身的功能可以增强,
非常适合做线程池。
非常适合做共享资源的访问。
可以得到线程执行的结果!
public class ThreadDemo01 {
public static void main(String[] args) {
// (2)创建一个未来任务对象FutureTask对象,包装Callable实现类对象。
CallableTarger targer = new CallableTarger();
// 未来任务对象的功能:可以在线程执行完毕以后得到线程的执行结果。
// 未来任务对象实际上就是一个Runnable对象
FutureTask<String> task = new FutureTask<>(targer);
// public Thread(Runnable task)
// (3)创建一个线程对象Thread来包装FutureTask对象
Thread t = new Thread(task);
// (4)启动线程。
t.start();
for(int i = 1 ; i <= 5 ; i++ ){
System.out.println(Thread.currentThread().getName()+"=>输出:"+i);
}
// (5)获取线程执行的结果。
try {
String rs = task.get();
System.out.println(rs);
} catch (Exception e) {
e.printStackTrace();
}
}
}
// (1)创建一个线程任务对象,实现Callable接口,申明线程执行的结果类型。
// 需求:让线程求和,求 1-5的和返回。
class CallableTarger implements Callable<String>{
@Override
public String call() throws Exception {
int sum = 0 ;
for(int i = 1 ; i <= 5 ; i++ ){
sum+=i;
System.out.println(Thread.currentThread().getName()+"=>输出:"+i);
}
return Thread.currentThread().getName()+"求和返回:"+sum;
}
}
线程同步:为了解决线程安全问题
首先了解一下线程安全,在多个线程在操作同一个共享资源的时候,可能出现线程安全问题。 线程同步的思想是:让共享资源的访问实现先后访问。
核心点:把出现线程安全问题的代码给锁起来,每次只能有一个线程访问
其他线程必须等待这个线程执行完毕以后才可以进来访问。
线程同步的方式:
(1)同步代码块:synchronized关键字 按照规范:建议实例方法用this作为锁。
静态方法用字节码作为锁:类名.class。
(2)同步方法:在方法名上加synchronized关键字修饰
(3)lock锁:lock锁:
自己写一个锁:创建ReentrantLock对象
- public void lock() `:加同步锁。
- `public void unlock()`:释放同步锁。一般可以配合try(){}catch{}finally()使用。
线程控制
一般有start、run、sleep方法还有isAlive、currentThread、interrupt等方法。
线程状态:
线程一般有六个状态:新建—运行-阻塞-定时等待-等待-结束