Java:线程,多线程安全

线程

程序:进程,线程,就是一段静止的代码
进程:程序由静态转变为动态,一个正在运行的程序,具有生命周期
线程:进程的最小单元,线程是CPU的一条执行路径,一个进程中包含多个线程,且每个线程具有独立的栈和程序计数器,是CPU最小的执行单元
说明:多线程之间共享堆空间

Java中线程的体现:Thread类

Java中如何创建线程

  1. jdk1.5及之前:继承Thread类或者或者实现Runnable接口
  2. 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的资源切换:

  1. 时间片的方式
  2. 抢占式(高优先级的线程,在抢夺CPU的执行权时,概率较高)
线程的创建方式2:实现Runnable接口

继承Thread类和实现Runnable接口的区别:

  1. 实现Runnable接口的方式,避免了单继承的局限性
  2. 多线程之间共享同一个实现类,降低了耦合性,常用于处理共享资源
  3. 在实际开发中建议使用实现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的区别:

  1. sleep方法是定义声明Thread类当中的
  2. sleep方法会让线程处于堵塞状态,但是不会释放对象
  3. wait方法也会让线程处于阻塞状态,但是会释放对象
  4. 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类中

线程的声明周期

新建 —— 就绪 ——运行 —— 阻塞 ——永久等待

在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值