多线程-线程池的学习

学习

线程

  • 线程是独立调度和分派的基本单位
  • 线程是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。
  • 同一进程中的多条线程将共享该进程中的全部系统资源

线程生命周期
在这里插入图片描述
什么是并发与并行?
并行:指两个或多个事件在同一时刻发生(同时发生)。
并发:指两个或多个事件在同一个时间段内发生。

单线程

线程的创建:

  • 继承Thread类
public class CreateThreadDemo extends Thread{
	public void run(){
	//线程执行 
	System.out.println("thread");
	}
}
//---------另一个类-----------------------------
public  class Demo {
	public static void  main (String[] args){
		CreateThreadDemo demo=new CreateThreadDemo();
		demo.start();
	}
}
//控制台输出thread
  • 实现Runnable接口
public class MyRunnable implements Runnable {

	public void run() {
		for(int i=0;i<10;i++) {
			System.out.println(Thread.currentThread().getName()+",执行时间:"+new Date().getTime()+"第"+i+"次执行");
		}
	}
}


///-------------------执行 ------------------------
public  class Demo {
	public static void  main (String[] args){
		
	Thread t1=new Thread(new MyRunnable(),"自定义名:1号");
	t1.start();
	}
}
  • 实现Callable接口
    Callable需要使用FutureTask类帮助执行,FutureTask类结构如下:
    在这里插入图片描述
    Future接口:
    判断任务是否完成:isDone()
    能够中断任务:cancel()
    能够获取任务执行结果:get()
public class MyCallable implements Callable<String> {

	@Override
	public String call() throws Exception {
		//与run不同,call有返回值
		
		for(int i=1;i<11;i++) {
			System.out.println(Thread.currentThread().getName()+"执行时间:"+new Date().getTime()+"第"+i+"次");
		}
		return " success ";
	}

}

callable 实现


		/*
		 * 实现callable
		 * isDone 是否完成
		 * get()获取返回值
		 * 
		 */
		//1 创建FutureTask实例,创建MyCallable实例
 		FutureTask<String> task=new FutureTask<String>(new MyCallable());
		//2  创建Thread实例,执行FutureTask
 		Thread t=new Thread(task);
		t.start(); 
		//3 在主线程打印信息
 	 	for(int i=0;i<10;i++) {
		 		System.out.println("主线程:"+i+"tinme:"+new Date().getTime());
		 	} 
		//4 获取并打印返回结果
 	 	try {
				System.out.println("callable执行结果:"+task.get()); 
			} catch (InterruptedException | ExecutionException e) {
				e.printStackTrace();
			}
 	
	
  • 线程池
//使用线程池创建线程
	//使用Executors获取线程池对象
		ExecutorService es= Executors.newFixedThreadPool(10);//获取一个固定线程数的线程池对象
	//通过线程池对象获取线程并执行Runnable实例
		es.execute(new MyRunnable());
	//打印主线程
		for(int i=0;i<10;i++) {
	 		System.out.println("主线程:"+i+"tinme:"+new Date().getTime());
	 	}
	    es.shutdown();//关闭线程
	}

具体看后面的线程池
//-----------------------------------------------------------------------------------//
实现接口与继承Thread比较
接口更适合多个相同的程序代码的线程去共享同一个资源。
接口可以避免java中的单继承的局限性。
接口代码可以被多个线程共享,代码和线程独立。
线程池只能放入实现Runable或Callable接口的线程,不能直接放入继承Thread的类

callable和runnable的比较
相同点:
两者都是接口;
两者都可用来编写多线程程序;
两者都需要调用Thread.start()启动线程;
不同点:
实现Callable接口的线程能返回执行结果;而实现Runnable接口的线程不能返回结果;
Callable接口的call()方法允许抛出异常;而Runnable接口的run()方法的不允许抛异常;
实现Callable接口的线程可以调用Future.cancel取消执行 ,而实现Runnable接口的线程不能
注意点:
Callable接口支持返回执行结果,此时需要调用FutureTask.get()方法实现,此方法会阻塞主线程直到获取‘将来’结果;当不调用此方法时,主线程不会阻塞!

线程安全问题 and 如何解决

什么是线程安全
如果有多个线程同时运行同一个实现了Runnable接口的类,程序每次运行结果和单线程运行的结果是一样的,而且其他的变量的值也和预期的是一样的,就是线程安全的;反之,则是线程不安全的。
产生线程安全问题的原因
多个线程在操作共享的数据;
操作共享数据的线程代码有多条;
多个线程对共享数据有写操作;
如何解决
线程同步:要解决以上线程问题,只要在某个线程修改共享资源的时候,其他线程不能修改该资源,等待修改完毕同步之后,才能去抢夺CPU资源,完成对应的操作,保证了数据的同步性,解决了线程不安全的现象。
常用有七种方法:

  1. 同步代码块(synchronized)
  2. 同步方法(synchronized)
  3. 同步锁(ReenreantLock)
  4. 特殊域变量(volatile)
  5. 局部变量(ThreadLocal)
  6. 阻塞队列(LinkedBlockingQueue)
  7. 原子变量(Atomic*)

同步代码块:
synchronized(同步锁){
需要同步操作的代码
}
同步锁:
对象的同步锁只是一个概念,可以想象为在对象上标记了一个锁.
1 锁对象可以是任意类型。
2 多个线程要使用同一把锁。
注意:在任何时候,最多允许一个线程拥有同步锁,谁拿到锁就进入代码块,其他的线程只能在外等着(BLOCKED)。
同步方法:
使用synchronized修饰的方法,就叫做同步方法,保证A线程执行该方法的时候,其他线程只能在方法外等着。
public synchronized void method(){
可能会产生线程安全问题的代码
}
锁是谁?
1对于非static方法,同步锁就是this。
2 对于static方法,同步锁是当前方法所在类的字节码对象(类名.class)。

同步锁(ReenreantLock):

与上面那个对象锁不是同一个东西!!!!!

java.util.concurrent.locks.Lock 机制提供了比synchronized代码块和synchronized方法更广泛的锁定操作,同步代码块/同步方法具有的功能Lock都有,除此之外更强大,更体现面向对象。
同步锁方法:
public void lock() :加同步锁。
public void unlock() :释放同步锁。
如果实现线程安全
进入线程不安全的方法
lock() //线程进来,锁住
执行方法体
unlock() //线程执行完成,放开锁

Synchronized和Lock区别

  1. synchronized是java内置关键字,在jvm层面,Lock是个java类;
  2. synchronized无法判断是否获取锁的状态,Lock可以判断是否获取到锁;
  3. synchronized会自动释放锁(a 线程执行完同步代码会释放锁 ;b 线程执行过程中发生异常会释放锁),
  4. Lock需在finally中手工释放锁(unlock()方法释放锁),否则容易造成线程死锁;
  5. 用synchronized关键字的两个线程1和线程2,如果当前线程1获得锁,线程2线程等待。如果线程1阻塞,线程2则会一直等待下去,而Lock锁就不一定会等待下去,如果尝试获取不到锁,线程可以不用一直等待就结束了;
  6. synchronized的锁可重入、不可中断、非公平,而Lock锁可重入、可仲断、可公平(两者皆可)
  7. Lock锁适合大量同步的代码的同步问题,synchronized锁适合代码少量的同步问题。

线程死锁

什么是线程死锁:
假设有A B 两个线程—他们运行都需要资源 1 2
A、B两个线程并发执行,A获取到了资源1 请求获取资源2B获取到了资源2 ,请求获取资源1
这个时候,A拿着1 等待 2 才能执行完成 而B却是拿着2 等待1 才能执行完成。
因为 A B 都拿着资源不放手,因此进入了无限的等待,没完没了。这时候就是死锁了。

占着茅坑拉一半屎,要占两个才能拉完,拉不完就不走了。

死锁产生的必要条件
以下这四个条件是死锁的必要条件,只要系统发生死锁,这些条件必然成立,而只要上述条件之一不满足,就不会发生死锁。

  • 互斥条件
    一个坑只能一个人拉,有人蹲着厕所,别人只能在门外等,不能一齐快乐拉屎。
    文明点:进程要求对所分配的资源(如打印机)进行排他性控制,即在一段时间内某资源仅为一个进程所占有。此时若有其他进程请求该资源,则请求进程只能等待。
  • 不可剥夺条件
    在别人拉完之前,不能强迫别人出去。只能等别人拉完,开门出去(释放资源)
    文明点:进程所获得的资源在未使用完毕之前,不能被其他进程强行夺走,即只能由获得该资源的进程自己来释放(只能是主动释放)。
  • 请求与保持条件
    我已经有一个茅坑了,但是不够,还需要别的坑才能拉完屎,但是我不会放弃原来的茅坑。
    文明点:进程已经保持了至少一个资源,但又提出了新的资源请求,而该资源已被其他进程占有,此时请求进程被阻塞,但对自己已获得的资源保持不放。
  • 循环等待条件
    在这里插入图片描述
    人体 蜈蚣性,谁也不准走。

死锁处理

  1. 预防死锁:通过设置某些限制条件,去破坏产生死锁的四个必要条件中的一个或几个条件,来防止死锁的发生。
  2. 避免死锁:在资源的动态分配过程中,用某种方法去防止系统进入不安全状态,从而避免死锁的发生。
  3. 检测死锁:允许系统在运行过程中发生死锁,但可设置检测机构及时检测死锁的发生,并采取适当措施加以清除。
  4. 解除死锁:当检测出死锁后,便采取适当措施将进程从死锁状态中解脱出来。

“互斥”条件是无法破坏的。因此,在死锁预防里主要是破坏其他几个必要条件,而不去涉及破坏“互斥”条件。

线程通信

线程间通信常用方式如下:

  • 休眠唤醒方式:
    Object的wait、notify、notifyAll
    Condition的await、signal、signalAll
  • CountDownLatch:用于某个线程A等待若干个其他线程执行完之后,它才执行
  • CyclicBarrier:一组线程等待至某个状态之后再全部同时执行
  • Semaphore:用于控制对某组资源的访问权限
    Object和Condition休眠唤醒区别
    object wait()必须在synchronized(同步锁)下使用,
    object wait()必须要通过Notify()方法进行唤醒
    condition await() 必须和Lock(互斥锁/共享锁)配合使用
    condition await() 必须通过 signal() 方法进行唤醒

CountDownLatch:使用

public class CountDownLatchDemo {
	private CountDownLatch countDownLatch=new CountDownLatch(3); //设置要等待的运动员数
	//private CountDownLatch end =new CountDownLatch(0);
	
	
	/*
	 * 运动员方法,由运动员线程调用
	 */
	public void racer() {
		String name=Thread.currentThread().getName(); //获取线程名
		System.out.println(name+"正在准备.......");//运动员正在准备
		try {
			Thread.sleep(1000); //模拟运动员在准备
		} catch (InterruptedException e) { 
			e.printStackTrace();
		}
		System.out.println(name+"准备over!!!!!!!"); //运动员准备完成
		countDownLatch.countDown();//一个运动员准备完成的标志
	}
	
	/*
	 * 教练方法,由教练线程调用
	 */
	public void coach() throws InterruptedException {
		String name =Thread.currentThread().getName();
		System.out.println(name+"等待运所有动员准备完毕。。。。");
		countDownLatch.await();//阻塞线程  当countDownLatch == 0 ,即所有运动员都准备完毕后,才会往下执行
		System.out.println(name+"集合完毕");
	}
	
	public static void main(String[] args) {
		CountDownLatchDemo c=new CountDownLatchDemo();
		Thread t1=new Thread(new Runnable() {
			public void run() {
				c.racer();
			}
		},"racer1");
		Thread t2=new Thread(new Runnable() {
			public void run() {
				c.racer();
			}
		},"racer2");
		Thread t3=new Thread(new Runnable() {
			public void run() {
				c.racer();
			}
		},"racer3");
		Thread t4=new Thread(new Runnable() {
			public void run() {
				try {
					c.coach();
				} catch (InterruptedException e) { 
					e.printStackTrace();
				}
			}
		},"coach");
		t4.start();
		t1.start();
		t2.start();
		t3.start();
		
		
		
	}
	
}

//结果
/*racer1正在准备.......
racer3正在准备.......
racer2正在准备.......
coach等待运所有动员准备完毕。。。。
racer3准备over!!!!!!!
racer2准备over!!!!!!!
racer1准备over!!!!!!!
coach集合完毕 */

多线程

多线程特性

  • 原子性
    原子性,即一个操作或者多个操作 要么全部执行并且执行的过程不会被任何因素打断,要么就都不执行。
  • 可见性
    可见性是指当多个线程访问同一个变量时,一个线程修改了这个变量的值,其他线程能够立即看得到修改的值。显然,对于单线程来说,可见性问题是不存在的。
  • 有序性
    有序性即程序执行的顺序按照代码的先后顺序执行。

为了保证多线程的三个特性,有以下控制类。

  • ThreadLocal:线程本地变量
    实现:
public class ThreadLocalDemo {
	//1-创建银行对象:钱,存款,取款
	static class Bank{
		 private ThreadLocal<Integer> threadLocal=new ThreadLocal<Integer>() {
			 protected Integer initialValue() {
				 return 0;
			 }
		 };
		 //取款
		 public Integer get() {
				 return threadLocal.get();
		}
		 //存款
		 public void set(Integer money) {
			 threadLocal.set(threadLocal.get()+money);
		 }
	}
	//2-转账对象: 从银行中取钱,转账,保存到账户
	static class Transfer implements Runnable {
		private Bank bank;
		public Transfer(Bank bank) {
			this.bank=bank;
		}
		@Override
		public void run() { 
			for(int i=0;i<10;i++) {
				bank.set(10);
				System.out.println(Thread.currentThread().getName()+"账户余额:"+bank.get());
			}
		}
	}

	
	//3-在main 方法中使用两个对象,模拟转账
	public static void main(String[] args) { 
		Bank bank= new Bank();
		Transfer transfer=new Transfer(bank);
		Thread t1=new Thread(transfer,"customer1");  //客户1 会新建一个账户,然后给账户转10 10次
		Thread t2=new Thread(transfer,"customer2");	//客户2 会新建一个账户,然后给账户转10 10次
		// 客户1和客户2都是各自有一个银行账号副本,他们互不干扰。 
		//就像银行里面的  每个用户都有一个银行卡记录着余额,我存钱取钱,不干别人的的事情。  我每次存钱取钱都要通过银行来操作,我不能直接对卡操作。
		//ThreadLocal<Integer> threadLocal 就是我们的银行卡, 我们要通过 Bank对象即银行  来进行存钱取钱的操作。 
		t1.start();
		t2.start();
	}
	
}
  • 原子类:保证变量原子操作
    Java的java.util.concurrent.atomic包里面提供了很多可以进行原子操作的类,分为以下四类:
    原子更新基本类型:AtomicInteger、AtomicBoolean、AtomicLong
    原子更新数组:AtomicIntegerArray、AtomicLongArray
    原子更新引用:AtomicReference、AtomicStampedReference等
    原子更新属性:AtomicIntegerFieldUpdater、AtomicLongFieldUpdater

  • Lock类:保证线程有序性
    在这里插入图片描述

  • Volatile关键字:保证线程变量可见性

线程池

线程池关系图

在这里插入图片描述

Executor接口:
声明了execute(Runnable runnable)方法,执行任务代码
ExecutorService接口:
继承Executor接口,声明方法:submit、invokeAll、invokeAny以及shutDown等
AbstractExecutorService抽象类:
实现ExecutorService接口,基本实现ExecutorService中声明的所有方法
ScheduledExecutorService接口:
继承ExecutorService接口,声明定时执行任务方法
ThreadPoolExecutor类:
继承类AbstractExecutorService,实现execute、submit、shutdown、shutdownNow方法
ScheduledThreadPoolExecutor类:
继承ThreadPoolExecutor类,实现ScheduledExecutorService接口并实现其中的方法
Executors类:
提供快速创建线程池的方法

多线程的缺点:
处理任务的线程创建和销毁都非常耗时并消耗资源。
多线程之间的切换也会非常耗时并消耗资源。
解决方法:采用线程池
使用时线程已存在,消除了线程创建的时耗
通过设置线程数目,防止资源不足
//------------------------------------------------------------//
在Java中创建线程池常用的类是ThreadPoolExecutor,该类的全参构造函数如下:

public ThreadPoolExecutor(int corePoolSize,  //线程池中核心线程数的最大值
                          int maximumPoolSize,//线程池中能拥有最多线程数
                          long keepAliveTime, //表示空闲线程的存活时间。
                          TimeUnit unit, //表示keepAliveTime的单位。
                          BlockingQueue<Runnable> workQueue, //用于缓存任务的阻塞队列
                          ThreadFactory threadFactory, //指定创建线程的工厂
                          RejectedExecutionHandler handler) { // handle表示当workQueue已满,且池中的线程数达到maximumPoolSize时,线程池拒绝添加新任务时采取的策略


四种常用线程池
newCachedThreadPool
该方法创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。
此类型线程池特点是:

  1. 工作线程的创建数量几乎没有限制(其实也有限制的,数目为Interger. MAX_VALUE)
  2. 空闲的工作线程会自动销毁,有新任务会重新创建
  3. 在使用CachedThreadPool时,一定要注意控制任务的数量,否则,由于大量线程同时运行,很有会造成系统瘫痪。

newFixedThreadPool
该方法创建一个指定工作线程数量的线程池。每当提交一个任务就创建一个工作线程,如果工作线程数量达到线程池初始的最大数,则将提交的任务存入到池队列中。
优点:具有线程池提高程序效率和节省创建线程时所耗的开销。
缺点:在线程池空闲时,即线程池中没有可运行任务时,它不会释放工作线程,还会占用一定的系统资源。

newSingleThreadExecutor
该方法创建一个单线程化的Executor,即只创建唯一的工作者线程来执行任务,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO,优先级)执行。如果这个线程异常结束,会有另一个取代它,保证顺序执行。
单工作线程最大的特点是可保证顺序地执行各个任务,并且在任意给定的时间不会有多个线程是活动的。

newScheduleThreadPool
该方法创建一个定长的线程池,而且支持定时的以及周期性的任务执行,支持定时及周期性任务执行。

秒杀抢购实现

下次更新

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值