Java多线程

第一章 初步线程

1.多线程实现底层原理★★★★

1.1多线程简单的随即打印原理★★★

在这里插入图片描述
程序在执行的时候,先会找JVM,JVM会执行main方法形成一个主线程,然后找操作系统OS开辟一条main方法通向cpu的路径,cpu开辟一个栈存主线程,在主线程中,又出现一个单线程run,此时出现多个线程,在同级的条件下,抢占式调度,所以会出现随机打印。

1.2多线程原理内存图解★★★★★

在这里插入图片描述
在main方法中创建一个线程,在堆内存存放new的对象。在执行run方法时,还是只有一个线程,然后会在栈中开辟一个属于自己的栈空间。当执行start时,出现第二个线程,会在栈中新开辟一个内存空间,执行run方法,如果在new一个线程执行start时,出现第三个线程,在栈中开辟新的内存空间,执行run方法。每一个执行线程都有一片自己所属的栈内存空间.

2.Thread类

  • 在上一天内容中我们已经可以完成最基本的线程开启,那么在我们完成操作过程中用到了 java.lang.Thread 类, API中该类中定义了有关线程的一些方法,具体如下:

构造方法:

  • public Thread() :分配一个新的线程对象。
  • public Thread(String name) :分配一个指定名字的新的线程对象。
  • public Thread(Runnable target) :分配一个带有指定目标新的线程对象。
  • public Thread(Runnable target,String name) :分配一个带有指定目标新的线程对象并指定名字

常用方法:

  • public String getName() :获取当前线程名称。
  • public void start() :导致此线程开始执行; Java虚拟机调用此线程的run方法。
  • public void run() :此线程要执行的任务在此处定义代码。
  • public static void sleep(long millis) :使当前正在执行的线程以指定的毫秒数暂停(暂时停止执行)。

翻阅API后得知创建线程的方式总共有两种,一种是继承Thread类方式,一种是实现Runnable接口方式,方式一我 们上一天已经完成,接下来讲解方式二实现的方式。

2.2创建线程方式二(Runnable)★

采用 java.lang.Runnable 也是非常常见的一种,我们只需要重写run方法即可。
步骤如下:

  1. 定义Runnable接口的实现类,并重写该接口的run()方法,该run()方法的方法体同样是该线程的线程执行体。
  2. 创建Runnable实现类的实例,并以此实例作为Thread的target来创建Thread对象,该Thread对象才是真正 的线程对象。
  3. 调用线程对象的start()方法来启动线程。

代码如下:

public class MyRunnable implements Runnable{ 
	@Override      
	public void run() {      
		for (int i = 0; i < 20; i++) {          
			System.out.println(Thread.currentThread().getName()+" "+i);             
		}         
    }   
 }
public class Demo {     
	public static void main(String[] args) {         
		//创建自定义类对象  线程任务对象         
		MyRunnable mr = new MyRunnable();         
		//创建线程对象         
		Thread t = new Thread(mr, "小强");         
		t.start();         
		for (int i = 0; i < 20; i++) {             
			System.out.println("旺财 " + i);        
			 }    
		 } 
	}
	

2.3Thread和Runnable的区别

实现Runnable接口比继承Thread类所具有的优势:

  1. 避免单继承的局限性

    一个类只能继承一个类,类继承了Thread类后不能继承其他类。Runnable则避免了该现象。

  2. 增强程序的扩展性,降低耦合(解耦)

    实现Runnable接口的方式,把设置线程任务和开启新线程进行了分离,实现类中重写了Run方法,用来设置线程任务,创建Thread对象,调用start方法,来开启一个新的线程。

  3. 线程池只能放入实现Runable或Callable类线程,不能直接放入继承Thread的类
提一下匿名内部类实现线程的创建:
 new Thread( new Runnable(){        
 public void run(){            
 	for (int i = 0; i < 20; i++) {              	
	 System.out.println("张宇:"+i);                 
	}                
 }             
}).start();

第二章 线程安全★★★★★

1.多线程出现的问题:

假如火车站买火车票,在多个窗口同时卖所有的票。当同一个车厢内的某个座位,在窗口一卖掉后,在窗口二也会卖这个座位的票,但是这个票已经卖掉了,返回值为0,下个窗口也会卖这个座位的票,返回值为-1。会出现多个窗口卖重复的票,就会出现线程安全问题。

出现问题的状态:
1.打印三个相同的100票号?
出现该问题是,在线程一调用start方法时,还没执行run方法,票号100还没减一,此时线程二,调用start方法,票数还是100。
2.打印票1 , -1, 0的票号?
因为在三个线程同时得到1号票,执行–后,为0,此时线程二调用–后变-1.

2.解决线程安全的方法有三种:

2.1方法一:同步代码块

创建一个锁对象,然后用synchronized关键字把该对象锁在里面,代码块里面是共享的方法代码。
当有一个线程来访问的时候,先获得锁对象,如果有就开始执行代码块中的代码。若线程二来访问时,没有获得所对象,则等待线程一释放锁对象,然后立刻开始调用。

2.2方法二:同步方法

该方法与同步代码块的原理是一样一样的,都是锁对象,只不过用了另一种形式体现出来。

2.3方法三:Lock锁
  • java.util.concurrent.locks.Lock 机制提供了比synchronized代码块和synchronized方法更广泛的锁定操作, 同步代码块/同步方法具有的功能Lock都有,除此之外更强大,更体现面向对象。
  • public void lock() :加同步锁。
  • public void unlock() :释放同步锁。
public class Ticket implements Runnable{ 
	private int ticket = 100;           
	Lock lock = new ReentrantLock();      
	/*       * 执行卖票操作       */      
	@Override      
	public void run() {      
	//每个窗口卖票的操作           
	//窗口 永远开启           
	while(true){          
		lock.lock();              
		if(ticket>0){
			//有票 可以卖              
			//出票操作                   
			//使用sleep模拟一下出票时间                   
			try {                  
				Thread.sleep(50);                      
				} catch (InterruptedException e) {                  
				// TODO Auto‐generated catch block                      
				e.printStackTrace();                      
				}
			//获取当前线程对象的名字                   
			String name = Thread.currentThread().getName(); 
			System.out.println(name+"正在卖:"+ticket‐‐);                  
			}              
		lock.unlock();             
	}         
  }     
}

第三章 线程状态

概述:
> 当线程被创建并启动以后,它既不是一启动就进入了执行状态,也不是一直处于执行状态。在线程的生命周期中, 有几种状态呢?在API中 java.lang.Thread.State 这个枚举中给出了六种线程状态:
这里先列出各个线程状态发生的条件,下面将会对每种状态进行详细解析.
在这里插入图片描述
在这里插入图片描述

1.等待唤醒机制(wait(),notify()

背景:为什么会出现等待唤醒机制呢?

多个线程大多数是为了提高效率配合工作而不是随机去获取,举一个例子,当你去餐馆吃饭时,做饭相当于a线程,服务员送餐相当于b线程,只有当线程做完然后开始调用b线程送餐。然后在a线程–b线程–a线程—。为了这种配合就出现了等待唤醒机制。

调用wait和notify方法需要注意的细节

  1. wait方法与notify方法必须要由同一个锁对象调用。因为:对应的锁对象可以通过notify唤醒使用同一个锁对 象调用的wait方法后的线程。
  2. wait方法与notify方法是属于Object类的方法的。因为:锁对象可以是任意对象,而任意对象的所属类都是继 承了Object类的。
  3. wait方法与notify方法必须要在同步代码块或者是同步函数中使用。因为:必须要通过锁对象调用这2个方 法。

2.wait()和sleep()区别

对于sleep()方法,我们首先要知道该方法是属于Thread类中的。而wait()方法,则是属于Object类中的。

sleep()方法导致了程序暂停执行指定的时间,让出cpu该其他线程,但是他的监控状态依然保持者,当指定的时间到了又会自动恢复运行状态。

在调用sleep()方法的过程中,线程不会释放对象锁。

而当调用wait()方法的时候,线程会放弃对象锁,进入等待此对象的等待锁定池,只有针对此对象调用notify()方法后本线程才进入对象锁定池准备获取对象锁进入运行状态。

第四章 线程池***

背景:为什么会出现池,线程池的概念?

并发的线程比较多,调用的时间比较短,频繁的开启关闭线程会消耗过多资源,所以出现线程池的概念。

概念: 其实就是一个容纳多个线程的容器,其中的线程可以反复使用,省去了频繁创建线程对象的操作, 无需反复创建线程而消耗过多资源。
1.线程池的原理:
在这里插入图片描述
2.线程池的具体使用
Java里面线程池的顶级接口是 java.util.concurrent.Executor ,但是严格意义上讲 Executor 并不是一个线程 池,而只是一个执行线程的工具。真正的线程池接口是 java.util.concurrent.ExecutorServicejava.util.concurrent.Executors静态工厂类。
使用线程池中线程对象的步骤:

  1. 创建线程池对象。
  2. 创建Runnable接口子类对象。(task)
  3. 提交Runnable接口子类对象。(take task)
  4. 关闭线程池(一般不做)。
    代码如下:
public class ThreadPoolDemo {     
	public static void main(String[] args) {         
	// 创建线程池对象         
	ExecutorService service = Executors.newFixedThreadPool(2);//包含2个线程对象         
	// 创建Runnable实例对象         
	MyRunnable r = new MyRunnable();           
	//自己创建线程对象的方式         
	// Thread t = new Thread(r);         
	// t.start(); ‐‐‐> 调用MyRunnable中的run()           
	// 从线程池中获取线程对象,然后调用MyRunnable中的run()        
	service.submit(r);         
	// 再获取个线程对象,调用MyRunnable中的run()         
	service.submit(r);         
	service.submit(r);         
	// 注意:submit方法调用结束后,程序并不终止,是因为线程池控制了线程的关闭。         
	// 将使用完的线程又归还到了线程池中         
	// 关闭线程池         
	//service.shutdown();     
	} 
}

第五章 lambda表达式

1.函数式编程

java是面向对象OOP的编程,著重于对象。
函数式编程思想:面向函数编程,不注重过程只注重结果。
例如:y=2x+1;我们只需要输入x,不用关注什么样的过程,只要y的结果就好。

2.lambda表达式简单使用(JDK1.8出的性特性)

new Thread(new Runnable() {          
	@Override              
	public void run() {              
		System.out.println("多线程任务执行!");                  
		}              
}).start();

简化后

// 启动线程
new Thread(()> System.out.println("多线程任务执行!")).start();

lambda表达式的标准格式:
由三种部分组成:

  1. 一些参数
  2. 一个箭头
  3. 一段代码

格式:(参数列表)->{一些重写方法的代码}

解释说明格式:

():接口中抽象方法的参数列表,没有参数,就空着;有参数就写出参数,多个参数用逗号隔开
-> :传递的意思把参数传给方法体{}
{}:重写接口的抽象方法

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值