7月20日知识点
今天的主要内容——线程
-
线程
-
线程的基本概念
- 线程与进程的区别
-
线程的两种创建方式(掌握)
注意线程两种创建方式的区别
内部类实现线程创建
线程的第三种创建方式——实现Callable接口
-
Thread类中的方法使用
- getName()
- setName()
- currentthread()
- sleep
- join
- wait
- yield
- setDaemon
-
线程的同步(掌握)
同步代码块
同步方法
多线程(线程安全问题)(掌握)
线程的死锁(了解)
多线程和队列实现买卖票(掌握)
-
-
Lambda表达式
- lambda表达式的几种方式
- lambda表达式用途
线程
1. 线程的基本概念
-
1.什么是线程
- 线程是程序执行的一条路径, 一个进程中可以包含多条线程
- 多线程并发执行可以提高程序的效率, 可以同时完成多项工作
进程:其实是一个静态的概念
* 我们平时所说的进程开始执行,指的是:进程中主线程开始执行了,即main方法开始执行了计算机中实际运行的都是线程。
操作系统是支持多线程,多进程的,系统是同时执行多个线程的
CPU的执行:交替执行众多线程,因为速度快,表面上是同时执行多个线程,其实不是的。
注:
① Java的线程是通过java.lang.Thread类来实现的
② VM启动时会有一个主方法(main方法)所定义的线程
③ 可通过创建Thread的实例来创建新的线程
④ 每个线程都是通过某个特定的Thread对象所对应的run方法来完成其操作的,方法run()称为线程体
⑤ 通过调用Thread类中的start()方法来启动一个线程。
2. 多线程(多线程并行和并发的区别)(了解)
- 并行就是两个任务同时运行,就是甲任务进行的同时,乙任务也在进行。(需要多核CPU)
- 并发是指两个任务都请求运行,而处理器只能按受一个任务,就把这两个任务安排轮流进行,由于时间间隔较短,使人感觉两个任务都在运行。
- 比如我跟两个网友聊天,左手操作一个电脑跟甲聊,同时右手用另一台电脑跟乙聊天,这就叫并行。
- 如果用一台电脑我先给甲发个消息,然后立刻再给乙发消息,然后再跟甲聊,再跟乙聊。这就叫并发。
3. 多线程(Java程序运行原理和JVM的启动是多线程的吗)(了解)
-
A:Java程序运行原理
- Java命令会启动java虚拟机,启动JVM,等于启动了一个应用程序,也就是启动了一个进程。该进程会自动启动一个 “主线程” ,然后主线程去调用某个类的 main 方法。
-
B:JVM的启动是多线程的吗
JVM启动至少启动了垃圾回收线程和主线程,所以是多线程的。
-
运行程序证明JVM的多线程
public class TestJVMMultiThread { public static void main(String[] args) { //创建垃圾对象观察垃圾回收线程 for(int i = 0;i < 100;i++) { new Test(); } for(int i = 0;i < 100;i++) { System.out.println("主线程在执行"); } } } class Test{ @Override public void finalize(){ System.out.println("垃圾回收线程在执行"); } }
4. 线程的两种创建方式
3. 线程两种创建方式程序
-
创建线程程序1:
-
用lambda表达式简写线程启动过程
public class TestLambda { public static void main(String[] args) { System.out.println("哈哈哈哈我在测试啊"); Runnable runner = ()->{ StringBuffer s = new StringBuffer(); for(int i= 0;i < 10;i++) System.out.println(s.append("haha")); }; new Thread(runner).start(); //注意:同一个线程不可以启动多次 System.out.println("任务完成"); } } /* * 在JDK1.8中输出结果为: * ------------------------------------------ * 哈哈哈哈我在测试啊 任务完成 haha hahahaha hahahahahaha hahahahahahahaha hahahahahahahahahaha hahahahahahahahahahahaha hahahahahahahahahahahahahaha hahahahahahahahahahahahahahahaha hahahahahahahahahahahahahahahahahaha hahahahahahahahahahahahahahahahahahahaha ------------------------------------------ * */
-
-
创建线程程序2:
- 此时Runner线程和main线程交替运行
* 能使用接口实现线程就不要用继承,因为用了接口以后还可以继承和实现其它接口,而如果只继承会比较死板
4. 创建线程的两种方式的区别(掌握)
- 查看源码的区别:
- a.继承Thread : 由于子类重写了Thread类的run(), 当调用start()时, 直接找子类的run()方法
- b.实现Runnable : 构造函数中传入了Runnable的引用, 成员变量记住了它, start()调用run()方法时内部判断成员变量Runnable的引用是否为空, 不为空编译时看的是Runnable的run(),运行时执行的是子类的run()方法
- Thread类中的run()源码为:
public void run() {
if (target != null) {
target.run();
}
}
5. 匿名内部类实现线程
-
继承Thread类
new Thread() { //1.继承Thread类 public void run(){ //2.重写run方法 for(int i = 0;i < 1000;i++) { System.out.println("aaaaaa"); } } }.start(); //3.开启线程
-
实现Runnable接口
new Thread(new Runnable() { //1.将Runnable子类对象传递给Thread构造方法 public void run(){ //2.重写run方法 for(int i = 0;i < 1000;i++) { System.out.println("bb"); } } }).start(); //3.开启线程
6. 创建线程的两种方式的区别(掌握)
- 继承Thread
- 好处是:可以直接使用Thread类中的方法,代码简单
* 弊端是:如果已经有了父类,就不能用这种方法
- 实现Runnable接口
- 好处是:即使自己定义的线程类有了父类也没关系,因为有了父类也可以实现接口,而且接口是可以多实现的
* 弊端是:不能直接使用Thread中的方法需要先获取到线程对象后,才能得到Thread的方法,代码复杂
7. 线程的第三种创建方式
-
使用Callable接口_java.util.concurrent
- Callable接口与Runnable接口的区别:
- Runnable没有返回值一说,而且run()方法并不抛出异常
- Callable中的call方法具有返回值
- Callable之所以有返回值,也是因为实现了泛型,而Runnable接口不存在泛型
- Callable接口与Runnable接口的区别:
7.1 FutureTask类的学习——java.util.concurrent.FutureTask<V>
- FutureTask的构造器
此时可理解为:FutureTask的作用就是接收一个实现了Callable接口的实现类对象
-
此时注意到FutureTask实现了Runnable接口
- 也就是说可以把FutureTask的实现类对象传入到Runnable类型
- 而Thread类中的构造器的参数为Runnable类型
-
因此梳理后可知
①先实现Callable接口
class myThread_3 implements Callable<String>{ @Override public String call() throws Exception { return "线程创建成功"; } }
②将Callable实现类对象传给FutureTask构造器
FutureTask<String> ft = new FutureTask<>(new myThread_3()); * 因为FutureTask实现了Runnable接口,因此以下代码也正确 Runnable runner = new FutureTask<String>(new myThread_3()); * 接口引用指向实现类对象 * 但是此时runner只能访问Runnable中方法,不可以访问FutureTask中的方法
③创建线程并启动
new Thread(ft).start();
④使用FutureTask类中方法
public V get() throws InterruptedException,ExecutionException 通过Thread启动线程之后 可以通过FutureTask存在get方法可以取得call()方法中的返回值 /*用到Callable有什么用?只是为了返回一个值?执行线程的代码run用什么代替?*/ /*回答:此时线程启动start()方法调用的是call()方法,将需要执行的代码放到call()方法中,同时call()方法的特点在于有返回值*/
-
程序
public class TestCallable{ public static void main(String[] args) { FutureTask<String> ft = new FutureTask<String>(new myThread_3()); new Thread(ft).start(); //此时的启动需要调用的是call()方法 String result = null; try { result = ft.get(); } catch (InterruptedException | ExecutionException e) { e.printStackTrace(); } System.out.println(result); } } class myThread_3 implements Callable<String>{ @Override public String call() throws Exception { System.out.println("此时start()方法调用的是call()方法"); return "线程创建成功"; } } /* 在JDK1.8中输出结果为: * ---------------------------- * 此时start()方法调用的是call()方法 线程创建成功 ---------------------------- * */
8. 多线程(获取名字和设置名字)(掌握)
-
获取名字
-
通过getName()方法获取线程对象的名字
public final String getName();
-
-
设置名字
-
通过构造函数可以传入String类型的名字
public Thread(String name) public Thread(Runnable target,String name)
-
通过setName(String)方法可以设置线程对象的名字
public final void setName(String name);
-
-
程序实现
new Thread("线程一") { //设置线程名字 public void run() { for(int i = 0;i < 100;i++) { System.out.println(this.getName() + "....aaaa"); //获取线程名字 } } }.start(); new Thread("线程二") { //设置线程名字 public void run() { for(int i = 0;i < 100;i++) { System.out.println(this.getName() + "....bb"); } } }.start(); new Thread() { public void run() { this.setName("线程3"); //设置线程名字 for(int i = 0;i < 100;i++) { System.out.println(this.getName() + "....哈哈"); } } }.start();
9. 多线程(获取当前线程的对象)(掌握)
public static Thread currentThread()
Thread.currentthread();——注意返回值是Thread
//匿名内部类实现当前线程对象的获取
new Thread() {
public void run() {
this.setName("线程A");
for(int i = 0;i < 1000;i++) {
System.out.println( Thread.currentThread().getName() + "....aaaa");
}
}
}.start();
new Thread(new Runnable() {
public void run() {
for(int i = 0;i < 1000;i++) {
System.out.println( Thread.currentThread().getName() + "....bb");
}
}
},"线程B").start();
}
10. 多线程(休眠线程)(掌握)
-
public static void sleep(long millis) throws InterruptedException
- 抛出InterruptionException
public class TestSleepOfThread {
public static void main(String[] args) {
Runnable r = ()->{
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Thread A");
};
new Thread(r).start();
System.out.println("main Thread");
}
}
/*
* 在JDK1.8中输出结果为:
* -------------
* main Thread
Thread A
-------------
* */
11. 多线程(守护线程)(掌握)
setDaemon(), 设置一个线程为守护线程, 该线程不会单独执行, 当其他非守护线程都执行结束后, 自动退出
-
public final void setDaemon(boolean on)
-
on - if true, marks this thread as a daemon thread
public class TestSetDaemon { public static void main(String[] args) { Thread t1 = new Thread("线程A") { public void run() { for(int i = 0;i < 2;i++) { System.out.println(Thread.currentThread().getName() + "....aaaa"); } } }; Thread t2 = new Thread(new Runnable() { public void run() { for(int i = 0;i < 50;i++) { System.out.println(Thread.currentThread().getName() + "....bb"); } } },"线程B"); t2.setDaemon(true); //将t2设置为守护线程 t1.start(); t2.start(); } } /* * 在JDK1.8中输出结果为: * ------------------------ * 线程A....aaaa 线程B....bb 线程A....aaaa 线程B....bb //存在时间缓冲问题 线程B....bb 线程B....bb 线程B....bb 线程B....bb 线程B....bb 线程B....bb * */
-
12. 多线程(加入线程)(掌握)
join(), 当前线程暂停, 等待指定的线程执行结束后, 当前线程再继续
join(int), 可以等待指定的毫秒之后继续
-
public final void join() throws InterruptedException
public class TestJoin { public static void main(String[] args) { Thread t1 = new Thread("线程A") { public void run() { for(int i = 0;i < 100;i++) { System.out.println(getName() + "....aaaaaaa"); } } }; Runnable r = ()->{ for(int i = 0;i < 50;i++) { if(i == 2) { try { t1.join(); //t1.join(1000); t1插队执行1秒后,t1和t2线程再抢占CPU交替执行 } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println(Thread.currentThread().getName() + "....bb"); } }; t1.start(); new Thread(r).start(); } }
13. 多线程(礼让线程)(了解)
- yield让出cpu
14. 多线程(设置线程的优先级)(了解)
- setPriority()设置线程的优先级
15. 多线程(同步代码块)(掌握)
- 1.什么情况下需要同步
- 当多线程并发, 有多段代码同时执行时, 我们希望某一段代码执行的过程中CPU不要切换到其他线程工作. 这时就需要同步.
- 如果两段代码是同步的, 那么同一时间只能执行一段, 在一段代码没执行结束之前, 不会执行另外一段代码.
- 2.同步代码块
使用synchronized关键字加上一个锁对象来定义一段代码, 这就叫同步代码块
锁对象不可以用匿名对象,因为匿名对象不是同一个对象
-
多个同步代码块如果使用相同的锁对象, 那么他们就是同步的
public class TestTickets_2 { public static void main(String[] args) { MyThread_6 myt = new MyThread_6(); new Thread(myt,"线程A").start(); new Thread(myt,"线程B").start(); } } class MyThread_6 implements Runnable{ private int ticket = 5; @Override public void run() { while(true) { if(this.ticket > 0) { System.out.println(Thread.currentThread().getName() + "开始卖票 = " + ticket--); } else { System.out.println("票已经卖完"); break; } } } }
16. 多线程(同步方法)(掌握)
public class TestSynchronized_2 {
public static void main(String[] args) {
Printer_2 p = new Printer_2();
Runnable r = ()->{
for(int i = 0;i < 100;i++) {
p.print1();
}
};
Thread t2 = new Thread() {
public void run() {
for(int i = 0;i < 100;i++) {
p.print2();
}
}
};
new Thread(r).start();
t2.start();
}
}
class Printer_2{
public synchronized void print1() {
System.out.print("早");
System.out.print("上");
System.out.print("好");
System.out.print("啊");
System.out.print("\r\n");
}
public void print2() {
synchronized(this) {
System.out.print("河");
System.out.print("正");
System.out.print("宇");
System.out.print("好");
System.out.print("帅");
System.out.print("\r\n");
}
}
}
17. 多线程(线程安全问题)(掌握)
多线程并发操作同一数据时, 就有可能出现线程安全问题
-
使用同步技术可以解决这种问题, 把操作数据的代码进行同步, 不要多个线程一起操作
/* * 铁路售票,一共100张,通过四个窗口卖完 * */ public class TestSynchronized_3 { public static void main(String[] args) { new Ticket().start(); new Ticket().start(); new Ticket().start(); } } class Ticket extends Thread{ private static int ticket = 100; public void run() { while(true) { //synchronized(this) synchronized(Ticket.class) { if(ticket == 0) { break; } System.out.println(getName() + "这是第" + ticket-- + "号票"); } } } }
18. 多线程(火车站卖票的例子用实现Runnable接口)(掌握)
public class TestSynchronized_4 {
public static void main(String[] args) {
Ticket_2 t = new Ticket_2();
new Thread(t,"线程A").start();
new Thread(t,"线程B").start();
new Thread(t,"线程C").start();
new Thread(t,"线程D").start();
}
}
class Ticket_2 implements Runnable{
private int ticket = 100; //此时ticket不需要设置为共享,因为均为同一对象
@Override
public void run() {
while(true) {
synchronized(this) {
if(ticket == 0) {
break;
}
System.out.println(Thread.currentThread().getName() + " 这是第" + ticket-- + "号票");
}
}
}
}
19. 多线程(死锁)(了解)
public class TestDeadLock {
private static Object o1 = new Object();
private static Object o2 = new Object();
public static void main(String[] args) {
new Thread("线程A") {
public void run() {
while(true) {
synchronized(o1) {
System.out.println(getName() + "正在使用o1,等待o2");
synchronized(o2) {
System.out.println(getName() +"等到o2,执行成功");
}
}
}
}
}.start();
new Thread("线程B") {
public void run() {
while(true) {
synchronized(o2) {
System.out.println(getName() + "正在使用o2,等待o1");
synchronized(o1) {
System.out.println(getName() + "等到o1,执行成功");
}
}
}
}
}.start();
}
}
/*
* 在JDK1.8中输出结果为:
* ----------------------
线程A正在使用o1,等待o2
线程A等到o2,执行成功
线程A正在使用o1,等待o2
线程B正在使用o2,等待o1
-------------------------
* */
- 因此:synchronized不要嵌套使用,容易出错
20. 多线程和队列实现买卖票
Queue接口
public interface Queue {
public void append(Object obj)throws Exception;
public Object delete()throws Exception;
public Object getFront()throws Exception;
public boolean isEmpty();
}
Class MyQueue
public class MyQueue implements Queue{
//1 设置队列的默认长度
static final int DEFAULT_SIZE=10; //默认长度为10
//2 设置队头
int front;
//3 设置队尾
int rear;
//4 定义统计元素的变量
int count;
//5 队的最大长度
int maxSize;
Object[] queue; //设置队列
//空构造
public MyQueue() {
this.init(DEFAULT_SIZE); //用户给定长度 默认长度为10
}
//有参数的构造
public MyQueue(int size) {
this.init(size); //开辟用户给定的长度
}
/**
* 初始化方法
* @param size
*/
public void init(int size) {
//初始化属性
this.maxSize=size; //外部传进来的size
//空队列
front=rear=0;
count=0;
queue=new Object[size];
}
@Override
public void append(Object obj) throws Exception {
// TODO Auto-generated method stub
//首先队列是否已满
if(count>0&&front==rear) { //判断队列是否已满
throw new Exception("队列已满");
}
this.queue[rear]=obj;
rear=(rear+1)%maxSize;
count++;
}
@Override
public Object delete() throws Exception {
// TODO Auto-generated method stub
if(this.isEmpty()) {
throw new Exception("队列为空队");
}
Object obj=this.queue[front];
front=(front+1)%maxSize;
count--;
return obj;
}
@Override
public Object getFront() throws Exception {
// TODO Auto-generated method stub
if(!this.isEmpty()) {
return this.queue[front];
}
return null;
}
@Override
public boolean isEmpty() {
// TODO Auto-generated method stub
return this.count==0;
}
}
Class WindowQueue
public class WindowQueue { //卖票的窗口
//定义卖票队列
int maxSize=10;
MyQueue queue=new MyQueue(maxSize);
int num=0; //最多卖100张票
boolean flag=true ; //判断是否继续卖票
//排队买票
public synchronized void producer()throws Exception{
if(this.queue.count<maxSize) {
this.queue.append(num++); //等待买票的数量++
System.out.println("第"+num+"个客户排队买票");
this.notifyAll(); //唤醒等待的线程
}else {
System.out.println("队列已满 请等待");
this.wait();
}
}
//卖票
public synchronized void consumer()throws Exception{
if(this.queue.count>0) {
Object obj=this.queue.delete(); //出队
int temp=Integer.parseInt(obj.toString());
System.out.println("第"+(temp+1)+"个客户买到票离开队列");
//如果当前的队列为空 并且卖出票数大于100
if(this.queue.isEmpty()&&this.num>=100) {
this.flag=false;
}
this.notifyAll(); //唤醒等待的线程
}else {
System.out.println("队列已空 请等待");
this.wait();
}
}
}
Class Producer
public class Producer implements Runnable{
WindowQueue queue;
public Producer(WindowQueue queue) {
this.queue=queue;
}
@Override
public void run() {
// TODO Auto-generated method stub
while(queue.num<100) { //必须小于100张票 才可以买票
try {
Thread.sleep(1000);
queue.producer();
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
Class Consumer
public class Consumer implements Runnable{
WindowQueue queue;
public Consumer(WindowQueue queue) {
this.queue=queue;
}
@Override
public void run() {
// TODO Auto-generated method stub
while(queue.flag) { //如果队列为空 并且票数大于100 就不会卖票了
try {
Thread.sleep(1000);
queue.consumer();
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
Class QueueTest
public class QueueTest {
public static void main(String[] args) throws Exception {
WindowQueue queue=new WindowQueue();
Producer p=new Producer(queue);
Consumer con=new Consumer(queue);
//以上的代码一定要注意 传入的是同一个对象
Thread t1=new Thread(p);
Thread t2=new Thread(con);
t1.start();
t2.start();
}
}
Lambda表达式
-
Lambda表达式的形式
参数,箭头(→)以及一个表示
Lambda适用于接口中一个抽象方法时候使用
-
举例1——函数式接口使用
Runnable runner = ()->{ StringBuffer s = new StringBuffer(); for(int i= 0;i < 10;i++) System.out.println(s.append("haha")); };
-
举例2——只有一个抽象方法的接口的实现类的使用
Thread t1 = new Thread(()->{ StringBuffer s = new StringBuffer(); for(int i= 0;i < 10;i++) System.out.println(s.append("haha")); });
-
区别于匿名内部类——需要写出方法声明
new Thread(new Runnable() { public void run() { StringBuffer s = new StringBuffer(); for(int i= 0;i < 10;i++) System.out.println(s.append("haha")); } }).start(); Thread t1 = new Thread() { public void run() { StringBuffer s = new StringBuffer(); for(int i= 0;i < 10;i++) System.out.println(s.append("haha")); } };