线程
程序:进程,线程,就是一段静止的代码
进程:程序由静态转变为动态,一个正在运行的程序,具有生命周期
线程:进程的最小单元,线程是CPU的一条执行路径,一个进程中包含多个线程,且每个线程具有独立的栈和程序计数器,是CPU最小的执行单元
说明:多线程之间共享堆空间
Java中线程的体现:Thread类
Java中如何创建线程:
- jdk1.5及之前:继承Thread类或者或者实现Runnable接口
- jdk1.5之后的方式:实现Callable接口,以及线程池的方式
并行和并发:
并行:单核CPU同一时间段内,执行多个线程任务
并发:多个核CPU同一时间,多个CPU执行多个线程任务
线程的创建方式1:继承Thread类
public class ThreadTest{
public static void main(String[] args){
//3.创建线程类的实例
Thread thread1 = new Thread1();
//4.调用线程类中的start方法让线程处于继续状态
thread1.start();
for(int i = 0; i < 20; i++){
System.out.println("run: "+i);
}
}
}
//1.继承Thread类
class Thread1 extends Thread{
//2. 重写Thread类当中的run方法
@Override
public void run(){
for(int i = 0; i < 10; i++){
System.out.println(i)
}
}
}
CPU的资源切换:
- 时间片的方式
- 抢占式(高优先级的线程,在抢夺CPU的执行权时,概率较高)
线程的创建方式2:实现Runnable接口
继承Thread类和实现Runnable接口的区别:
- 实现Runnable接口的方式,避免了单继承的局限性
- 多线程之间共享同一个实现类,降低了耦合性,常用于处理共享资源
- 在实际开发中建议使用实现Runnable接口的方式去创建多线程
public class RunnableImplTest {
public static void main(String[] args) {
//3.创建实现类对象
RunnableImpl rb1 = new RunnableImpl();
//4.创建线程
Thread t1 = new Thread(rb1);
Thread t2 = new Thread(rb1);
//修改线程名称
t1.setName("线程1");
t2.setName("线程2");
//5.调用start方法让线程处于就绪状态
t1.start();
t2.start();
}
}
//1.自定义线程类,实现Runnable接口
class RunnableImpl implements Runnable{
//2.重写Runnable接口中的抽象方法
@Override
public void run() {
// TODO Auto-generated method stub
for (int i = 0; i < 20; i++) {
try {
//接口中的抽象方法没有抛异常,重写之后也不能抛异常
//Thread.sleep(long millis);使当前线程处于堵塞方法,sleep方法是不会释放CPU的执行权
Thread.sleep(1000);
//获取线程名称:Thread类当中提供了一个静态方法,CurrentThread()返回当前线程对象
//System.out.println(i);
System.out.println(Thread.currentThread().getName()+" : "+i);
}catch(InterruptedException e) {
e.printStackTrace();
}
}
}
}
多线程共享
同步锁
模拟多线程环境下的售票:
多个窗口共同出售100张车票,此时如果不进行控制会出现重票(几个窗口卖出同一张票)和错票(漏卖一张车票)
【出现的重票和错票的原因是多窗口操作了相同数据 多线程安全问题:多线程操作了共享数据】
解决线程安全问题:加同步锁(对象锁,同步监听器)
同步锁的适用方式:同步锁可以为任意一个对象,但是必须保证多个线程之间使用的同步锁是唯一的【实现Runnable接口的方式中通常我们将同步锁设置为this】
1.同步代码块
synchronized (对象){}
同步代码在使用中包裹的都是共享数据的代码
class SaleTicket implements Runnable{
//总票数
private int ticket = 100;
//private Object obj = new Object();
@Override
public void run() {
// TODO Auto-generated method stub
while(true) {
//this相当于当前实例
synchronized (this) {
if (ticket > 0) {
try {
Thread.sleep(100);
System.out.println(Thread.currentThread().getName() + "正在买第 " + ticket-- + " 票");
} catch (InterruptedException e) { // TODO Auto-generated catch block
e.printStackTrace();
}
} else {
break;
}
}
}
}
}
2.同步方法:
可以保证线程的同步:静态同步方法的锁对象就是当前对象(运行时类对象)
【类名. class】 方式
public synchronized 返回值 返回方法名(参数列表){}
同步方法中的代码都是操作共享数据的代码,同步方法的锁对象是this
注意:只要操作了共享数据的代码,都要实现包裹,不能包少(出现线程问题),也不能包多(一个线程执行全部功能)
public synchronized void saleTicket() {
while(true) {
if(ticket > 0) {
try {
Thread.sleep(100);
System.out.println(Thread.currentThread().getName()+"正在买第 "+ticket--+" 票");
}catch (InterruptedException e) { // TODO Auto-generated catch block
e.printStackTrace();
}
}else {
break;
}
}
}
双重检查:
补充:工厂模型 --> 只能在内部创建实例,不能在外部创建实例
//用于提供该类的对象
public static Singleton getInstance() {
//双重检查
if(singleton == null) {
synchronized(Singleton.class) {
if(singleton == null) {
singleton = new Singleton();
}
}
}
return singleton;
}
Lock锁
java中除过对象锁之外还有显示锁lock可以用于保证线程安全
lock 加锁 unlock 解锁
【相较于synchronized 来说更加灵活,并且synchronized有相同的并发处理能力, 开发中推荐使用显示锁处理线程安全问题】
class SaleTicket1 implements Runnable{
//总票数
private int ticket = 100;
//1.以成员变量的形式声明ReentrantLock对象
private ReentrantLock rt = new ReentrantLock();
@Override
public void run() {
while(true) {
//开启锁
rt.lock();
if (ticket > 0) {
try {
Thread.sleep(100);
System.out.println(Thread.currentThread().getName() + "正在买第 " + ticket-- + " 票");
} catch (InterruptedException e) { // TODO Auto-generated catch block
e.printStackTrace();
}
} else {
break;
}
//释放锁,不管是否有异常,锁一定需要释放
rt.unlock();
}
}
}
线程间的通讯
实现线程间的通讯我们主要依托Object类当中的几个方法:
wait()方法:使当前线程进入等待状态,直到另一条发起notify()/notifyAll方法来唤醒等待中的线程
notify()/notifyAll方法:一个线程处于堵塞状态只用, notifyAll();唤醒全部等待中的线程,notify();
唤醒一个等待中的线程
sleep和wait的区别:
- sleep方法是定义声明Thread类当中的
- sleep方法会让线程处于堵塞状态,但是不会释放对象
- wait方法也会让线程处于阻塞状态,但是会释放对象
- wait方式只能由对象锁来调用(只能在同步代码块中调用)
//通知口罩厂商生产口罩的方法
public synchronized void producer() {
if(Mask >= 100) {
//使用wait方法,让当前线程处于等待状态
try {
wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}else {
System.out.println("厂家在生产第 "+(++Mask)+"个口罩");
//告诉消费者可以消费了:生产者线程来唤醒消费者线程
notify();
}
}
为什么wait,notify,notifyAll,方法定义在Object类中而不在Thread类中?
答: 这三个方法只能由对象锁来调用的,对象锁有可以是任意的唯一的对象,
Thread类只能代表线程类,那么它就不能声明wait,notify,notifyAll
Object类是所有类的父类,且对象锁才是这三个的实际调用者,所以只能声明在object类中
线程的声明周期
新建 —— 就绪 ——运行 —— 阻塞 ——永久等待