第一章 初步线程
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方法即可。
步骤如下:
- 定义Runnable接口的实现类,并重写该接口的run()方法,该run()方法的方法体同样是该线程的线程执行体。
- 创建Runnable实现类的实例,并以此实例作为Thread的target来创建Thread对象,该Thread对象才是真正 的线程对象。
- 调用线程对象的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类所具有的优势:
- 避免单继承的局限性
一个类只能继承一个类,类继承了Thread类后不能继承其他类。Runnable则避免了该现象。
- 增强程序的扩展性,降低耦合(解耦)
实现Runnable接口的方式,把设置线程任务和开启新线程进行了分离,实现类中重写了Run方法,用来设置线程任务,创建Thread对象,调用start方法,来开启一个新的线程。
- 线程池只能放入实现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方法需要注意的细节
- wait方法与notify方法必须要由同一个锁对象调用。因为:对应的锁对象可以通过notify唤醒使用同一个锁对 象调用的wait方法后的线程。
- wait方法与notify方法是属于Object类的方法的。因为:锁对象可以是任意对象,而任意对象的所属类都是继 承了Object类的。
- 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.ExecutorService , java.util.concurrent.Executors静态工厂类。
使用线程池中线程对象的步骤:
- 创建线程池对象。
- 创建Runnable接口子类对象。(task)
- 提交Runnable接口子类对象。(take task)
- 关闭线程池(一般不做)。
代码如下:
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表达式的标准格式:
由三种部分组成:
- 一些参数
- 一个箭头
- 一段代码
格式:(参数列表)->{一些重写方法的代码}
解释说明格式:
():接口中抽象方法的参数列表,没有参数,就空着;有参数就写出参数,多个参数用逗号隔开
-> :传递的意思把参数传给方法体{}
{}:重写接口的抽象方法