一、基本概念
程序:为完成特定任务、用某种语言编写的一组指令的集合。通俗来讲,一段静态的代码就是一个程序
进程:程序的一次执行过程,或是正在运行的一个程序,我们将代码运行起来,代码就变成了一个进程
线程:进程可进一步细化为线程,是一个程序内部的一条执行路径,若一个进程同一时间并行执行就变成了多线程 (线程作为调度和执行的单位,每个线程拥有独立的运行栈和程序计数器(pc))
并行:多个CPU同时执行多个任务。比如:多个人同时做不同的事
并发:一个CPU(采用时间片)同时执行多个任务。比如:秒杀、多个人做同一件事
二、创建的多线程的方法
java.lang.Thread类
1、方法一:继承于Thread类
1)重写Thread的run方法
2)创建Thread类的子类的对象
3)通过次此对象调用start() 方法
注意:方法一中我们不能直接调用run() 方法的方式启动线程,需要通过此线程对象调用start() 方法,对于一个已经调用过start() 方法的线程不可以再次调用start() ,如果需要一个新的线程,需要重新创建一个线程对象;
2、线程中的常用方法:
1)start(): 启动当前线程,调用当前线程的run() 方法
2) run(): 通常需要重写该方法,将线程中需要执行的方法定义到该方法内部
3) currentThread() : 获取当前线程的名字
4)setName : 设置当前线程的名字
5)getName : 获取当前线程的名字
6)yield() : 线程让步
7)join() : 当程序执行中调用其他线程的join方法,调用线程将被阻塞,直到join() 方法加入的join线程执行完为止
8)sleep() :当前线程睡眠
3、线程的调度
1) 抢占式: 高优先级的线程抢占CPU,同优先级的先进先出,采用时间片的方式
2) 线程的优先级: MAX_PRIORITY:10 MIN _PRIORITY:1 NORM_PRIORITY:5
2-1、getPriority() :返回线程优先值 setPriority(int newPriority) :改变线程的优先
4、方法二、实现Runnable接口
1)创建一个实现了Runnable接口的类
2)实现类去实现Runnable中的抽象方法:run()
3)创建实现类的对象
4)将此对象作为参数传递到Thread类的构造器中,创建Thread类的对象
5)通过Thread类的对象调用start()
比较: 优先选择实现Runnable接口的方式,
1)实现的方式避免了单继承的局限性
2)实现的方式更适合来处理多个线程有共享数据的情况
联系:两种方式都需要重写run方法
5、线程的生命周期
6、线程的同步
问题的引入: 多个线程执行的不确定性会导致执行结果的不稳定,例如多个用户操作同一份数据,会导致数据的破坏,故我们通过同步机制,来解决线程的安全问题
1)、方式一: 同步代码块 { synchronized(同步监视器) }
说明:操作共同数据的代码,就是需要被同步的代码
/*
synchronized(同步监视器) {
//同步监视器就是一把锁 ,任何一个类的对象,都可以充当锁,但是所有的线程必须使用同一把锁
// 实现的方式可以使用this充当锁或者 类.class
//操作同步数据的代码块
}
*/
Object obj = new Object();
synchronized(obj) {
//操作同步数据的代码块
}
局限性:同步代码块的方式,解决了线程的安全问题,但是操作同步代码时,只能一个线程参与,其他线程等待,这样就相当于单线程了,效率低
2)、方式二: 同步方法
将同步代码块的代码提取到一个方法中,给方法添加synchronized
死锁问题:不同的线程分别占用对方需要的同步资源不放弃,都在等待对方放弃自己需要的同步资源,就形成了线程的死锁问题
尽量减少同步资源的定义,避免嵌套同步
3)、方法三:Lock锁 -- > JDK5.0 提供 ,为解决线程安全问题的一种定义方式
private ReentrantLock lock = new ReentranLock();
try {
lock.lock();
// 操作同步数据的代码
} finally {
lock.unlock();
}
Lock锁和synchronized 的不同:Lock锁是显示锁,我们需要手动的打开锁,手动的关闭锁,synchronized 相当于是一个隐式锁,出了作用域会自动释放锁
4)、线程的通信
例子: 使用两个线程打印1-100,线程一和线程二交替打印
涉及到的三个方法( 必须使用再同步代码块和同步方法中 ): wait():一旦执行此方法,当前线程就会进入阻塞状态,并释放同步监视器
notify(): 一旦执行此方法,就会唤醒被wait的一个线程,按照优先级的高低来唤醒
notifyAll(): 一旦执行此方法,就会唤醒所有被wai的线程
class MyThread implements Runnable {
private int number = 1;
//notify(),notifyAll(),wait() 三个方法的调用者必须是同步代码块或者同步方法中的同步监视器
@Override
public void run() {
/*打印1-100,两个线程交替打印*/
while (true) {
synchronized (this) {
this.notify();
if (number <= 100) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ":" + number);
number++;
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
} else {
break;
}
}
}
}
}
public class ThreadTest {
public static void main(String[] args) {
MyThread myThread1 = new MyThread();
Thread t1 = new Thread(myThread1);
Thread t2 = new Thread(myThread1);
t1.setName("线程1");
t2.setName("线程2");
t1.start();
t2.start();
}
}
sleep() 和 wait() 方法的异同:① 两者被执行后都会使当前线程进入阻塞状态; ② sleep() 可以在任何需要的地方调用,而wait() 必须使用在同步代码块中调用 ③ 都在同步代码块中使用,sleep() 不会释放同步监视器, wait() 会释放同步监视器
7、方式三: 实现Callable接口 JDK 5.0 新增
1) 创建一个Callable的实现类
2) 实现call方法,将需要执行的操作声明在call中
3)创建Callable的实现类的对象
4)将Callable实现类的对象传递到FutureTask构造器中,创建FutureTask的对象
5)将FutureTask的对象作为参数传递到Thread的构造器中,创建Thread对象,并调用start()
6) 如果想要cal方法的返回值,可以通过FutureTask的对象调用get()
为什么实现Callable 接口 比 实现Runnable 接口 方式强大: ① 很显然实现Callable接口重写的 call() 可以拿到返回值 ② call() 方法可以抛异常,被外面的操作捕获,获取异常信息 ③ Callable 支持泛型
8、方式四: 使用线程池
线程池为我们提前创建了多个线程,放入线程中,使用时直接获取,使用完放回池中,可以避免频繁创建销毁、实现重复利用
//提供指定线程数量的线程池
ExecutorService service = Executors.newFixedThreadPool(10);
//执行
service.execute(Runnable接口实现类对象);
//service.submit() Callable接口实现类
//关闭连接池
service.shutdown();