Java_basic_knowledge_supplement
Java_basic_knowledge_supplement
1. 多线程学习
1. 两种实现方法
1.继承Thread类
将类声明为 Thread
的子类。该子类应重写 Thread
类的 run
方法。接下来可以分配并启动该子类的实例。
不推荐使用:避免OOP单继承局限性
package com.zhang.demo1;
public class Thread1 extends Thread{
@Override
public void run() {
for (int i = 0; i < 20; i++){
System.out.println("run"+i);
}
}
public static void main(String[] args) {
Thread1 thread1 = new Thread1();
thread1.start();
for (int i = 0; i < 20; i++) {
System.out.println("main" + i);
}
}
}
2.实现Runnable接口(例子:购票)
实现 Runnable
接口的类。该类然后实现 run
方法。然后可以分配该类的实例,在创建 Thread
时作为一个参数来传递并启动。
推荐使用:避免单继承局限性,灵活方便,方便同一个对象被多个线程使用
package com.zhang.demo1;
public class Runnable1 implements Runnable {
@Override
public void run() {
for (int i = 0; i < 20; i++){
System.out.println("run"+i);
}
}
public static void main(String[] args) {
Runnable1 runnable1 = new Runnable1();
Thread thread = new Thread(runnable1);
thread.start();
//上面两行代码等价于
//new Thread(runnable1).start();
for (int i = 0; i < 20; i++) {
System.out.println("main" + i);
}
}
}
下面是一个购票系统的例子
package com.zhang.demo1;
//buy tickets
//problem:多个线程操作同一个资源,出现问题,即不同的人买到了同号的票
//solution:进程同步问题
public class Runnable2 implements Runnable{
private int ticketNums = 10;
@Override
public void run() {
while (true){
if(ticketNums<=0) break;
System.out.println(Thread.currentThread().getName() + " get " + ticketNums--);
}
}
public static void main(String[] args) {
Runnable2 runnable2 = new Runnable2();
new Thread(runnable2,"Jack").start();
new Thread(runnable2,"Tom").start();
new Thread(runnable2,"Chester").start();
}
}
发现问题:会出现某个人买到的票很多的情况
原因:cpu运行速度太快
解决方法:进行线程休眠,模拟网络延时(后续改进)
2. 实现callable接口
Callable
接口类似于 Runnable
,两者都是为那些其实例可能被另一个线程执行的类设计的。
但是 Runnable
不会返回结果,并且无法抛出经过检查的异常。
//创建执行服务(创建一个池,参数是要提交执行的个数)
ExecutorService executorService = Executors.newFixedThreadPool(1);
//提交执行
Future<Boolean> result1 = executorService.submit(callable1);
//获取结果
boolean resultSubmit1 = result1.get();//此处get方法需要抛出异常
//关闭服务
executorService.shutdownNow();
一个实例
package com.zhang.demo2;
import java.util.concurrent.*;
public class Callable1 implements Callable {
private String name;
public Callable1(String name) {
this.name = name;
}
public String getName() {
return name;
}
@Override
public Boolean call() throws Exception {
System.out.println("Hello, I am " + Callable1.this.getName());
return true;
}
public static void main(String[] args) throws ExecutionException, InterruptedException {
Callable1 callable1 = new Callable1("no.1");
Callable1 callable2 = new Callable1("no.2");
Callable1 callable3 = new Callable1("no.3");
//创建执行服务(创建一个池)
ExecutorService executorService = Executors.newFixedThreadPool(3);
//提交执行
Future<Boolean> result1 = executorService.submit(callable1);
Future<Boolean> result2 = executorService.submit(callable2);
Future<Boolean> result3 = executorService.submit(callable3);
//获取结果
boolean resultSubmit1 = result1.get();//此处get方法需要抛出异常
boolean resultSubmit2 = result1.get();
boolean resultSubmit3 = result1.get();
//关闭服务
executorService.shutdownNow();
}
}
3. 静态代理(例子:婚庆公司)
真实对象(目标对象)都要实现同一个接口
代理对象代理真实对象,代理对象类中要声明一个真实对象的实例化对象
**好处:**代理对象可以做真实对象做不了或不方便做的事情,真实对象则专注于自己的事情
实例展示:以婚庆公司为例子
package com.zhang.demo3;
public class StaticProxy {
public static void main(String[] args) {
/*未使用代理对象的代码
new You().HappyMarry();
*/
//使用代理对象的代码
new WeddingCompany(new You()).HappyMarry();
}
}
interface Marry{
void HappyMarry();
}
//真实角色
class You implements Marry{
@Override
public void HappyMarry() {
System.out.println("I am getting married , I am very happy!");
}
}
//代理角色,婚庆公司
class WeddingCompany implements Marry{
private Marry target;
public WeddingCompany(Marry target) {
this.target = target;
}
@Override
public void HappyMarry() {
beforeMarry();
this.target.HappyMarry();
afterMarry();
}
private void beforeMarry() {
System.out.println("beforeMarry");
}
private void afterMarry() {
System.out.println("afterMarry");
}
}
4. Lambda表达式
目的:简化。
避免匿名内部类过多,实质属于函数式编程的概念。
如果一个接口,只有一个抽象方法,那么他就是函数式接口
对于这个函数式接口,我们可以通过lambda表达式创建该接口对象
如果我们有一个函数式接口:
- 我们选择写一个实现类,然后new一个对象
- 也可以写一个静态内部类
- 也可以在函数(main)中写一个局部内部类
- 也可以写一个匿名内部类
- 或直接使用lambda表达式(最方便最简洁)
package com.zhang.demo4_lambda;
public class TestLambda {
//3.静态内部类
static class Like2 implements ILike{
@Override
public void lambda() {
System.out.println("i like lambda2");
}
}
public static void main(String[] args) {
ILike iLike = new Like();
iLike.lambda();
iLike = new Like2();
iLike.lambda();
//4.局部内部类
class Like3 implements ILike{
@Override
public void lambda() {
System.out.println("i like lambda3");
}
}
iLike = new Like3();
iLike.lambda();
//5.匿名内部类,没有类的名称,必须借助接口或父类
iLike = new ILike() {
@Override
public void lambda() {
System.out.println("i like lambda4");
}
};
iLike.lambda();
//6.用lambda简化
iLike = ()->{
System.out.println("i like lambda5");
};
iLike.lambda();
}
}
//1.定义一个函数式接口(只有一个抽象函数的接口)
interface ILike{
void lambda();
}
//2.实现类
class Like implements ILike{
@Override
public void lambda() {
System.out.println("i like lambda");
}
}
下面是一个纯lambda的实现方法
package com.zhang.demo4_lambda;
public class TestLambda2 {
public static void main(String[] args) {
A a = (int number)->{
System.out.println("this is AMethod, and the number is " + number);
};
a.AMethod(5);
}
}
interface A{
void AMethod(int number);
}
下面是对于上面这个实现方法的继续简化
//简化方法
//1. 参数类型
a = (number)->{
System.out.println("this is AMethod, and the number is " + number);
};
a.AMethod(2);
//2. 括号
a = number->{
System.out.println("this is AMethod, and the number is " + number);
};
a.AMethod(3);
//3. 花括号
a = number->System.out.println("this is AMethod, and the number is " + number);
a.AMethod(4);
可见:正确输出。
总结:
- lambda表达式在只有一行代码的情况下才能够简化为一行,如果有多行则只能用第二步简化
- **重要前提:**必须是函数式接口
- 如果有多个参数,则不能简化圆括号,但可以简化掉参数类型
- 为何要学习lambda接口呢,因为Runnable接口就是一个函数式接口
5. 线程状态
方法 | 说明 |
---|---|
setPriority(int newPriority) | 更改线程的优先级 |
static void sleep(long millis) | 在指定的毫秒数内让当前正在执行的线程休眠 |
void join() | 等待该线程终止 |
static void yield() | 暂停当前正在执行的线程对象,并执行其他线程 |
void interrupt() | 中断线程,不建议使用该方法 |
boolean isAlive() | 测试线程是否处于活动状态 |
1. 线程终止
- 建议线程正常停止,利用次数,不建议死循环
- 建议使用标志位
- 不要使用stop或destory等过时方法
package com.zhang.demo5_state;
public class TestStop implements Runnable{
//1.设置一个标志位
private boolean flag = true;
@Override
public void run() {
int i = 0;
while (flag){
System.out.println("run + " + i++);
}
}
//2.写一个公开方法修改标志位,用来停止线程
public void stop(){
this.flag = false;
}
public static void main(String[] args) {
TestStop testStop = new TestStop();
new Thread(testStop).start();
for (int i = 0; i < 8; i++) {
System.out.println("main + " + i);
if(i == 7){
//调用stop方法
testStop.stop();
System.out.println("it is time to stop");
}
}
}
}
2. 线程休眠(例子:购票plus)
对之前的购票例子进行优化:
package com.zhang.demo5_state;
public class TestSleep implements Runnable{
private int ticketNums = 10;
@Override
public void run() {
while (true){
if(ticketNums<=0){
break;
}
//模拟网络延时
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " get " + ticketNums--);
}
}
public static void main(String[] args) {
TestSleep testSleep = new TestSleep();
new Thread(testSleep,"Jack").start();
new Thread(testSleep,"Tom").start();
new Thread(testSleep,"Chester").start();
}
}
运行结果如图所示,可见很好的解决了之前的问题,可见,模拟网络延时的作用就是:放大问题的发生性
但出现了新问题:出现了-1号票
原因:线程不安全,多个线程同时操作一个对象
每个线程都有一把锁,sleep不会释放锁。
3. 线程礼让
即:将线程从运行状态转为就绪状态,cpu重新调度线程,但仍可能继续调度当前线程。
package com.zhang.demo5_state;
//测试线程礼让
public class TestYield {
public static void main(String[] args) {
MyYield myYield = new MyYield();
new Thread(myYield,"a").start();
new Thread(myYield,"b").start();
}
}
class MyYield implements Runnable{
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + " begin");
Thread.yield();
System.out.println(Thread.currentThread().getName() + " end");
}
}
如图,礼让成功,a执行一半后,执行b
如图,礼让失败,a执行了礼让方法,但cpu继续调度a
4. 线程强制执行
使用thread1.join()方法,让thread1进程强制上cpu执行,
package com.zhang.demo5_state;
public class TestJoin implements Runnable{
@Override
public void run() {
try {
Thread.sleep(1000); //使用sleep,才能正常显示
} catch (InterruptedException e) {
e.printStackTrace();
}
for (int i = 0; i < 10; i++) {
System.out.println("I am VIP." + i);
}
}
public static void main(String[] args) throws InterruptedException {
//启动我们的线程
TestJoin testJoin = new TestJoin();
Thread thread = new Thread(testJoin);
thread.start();
for (int i = 0; i < 20; i++) {
if(i == 15){
thread.join();
}
System.out.println("I am main." + i);
}
}
}
输出结果如下图:
5. 线程状态总结
通过阅读JDK帮助文档可知,线程可以处于下列状态之一:
NEW
至今尚未启动的线程处于这种状态。RUNNABLE
正在 Java 虚拟机中执行的线程处于这种状态。BLOCKED
受阻塞并等待某个监视器锁的线程处于这种状态。WAITING
无限期地等待另一个线程来执行某一特定操作的线程处于这种状态。TIMED_WAITING
等待另一个线程来执行取决于指定等待时间的操作的线程处于这种状态。TERMINATED
已退出的线程处于这种状态。
package com.zhang.demo5_state;
public class TestState {
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(()->{
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("thread");
});
//观察声明后
System.out.println(thread.getState());//应当输出NEW
//观察启动后
thread.start();
System.out.println(thread.getState());//应当输出RUNNABLE
//观察阻塞后
while (thread.getState() != Thread.State.TERMINATED){
Thread.sleep(10);//通过这行代码控制输出的数量,否则由于cpu运行过快,会输出一堆TIMED_WAITING
System.out.println(thread.getState());//此时线程处于sleep阻塞状态
}
//观察结束后
System.out.println(thread.getState());//执行结束,应当输出TERMINATED
}
}
3. 线程同步
1. 线程优先级
务必:先设置优先级,再启动
优先级低只是意味着获得调度的概率低,并不是优先级低就不会被调用,需要看cpu的调度
package com.zhang.demo6_synchronization;
public class TestPriority {
public static void main(String[] args) {
//输出主线程的优先级
System.out.println(Thread.currentThread().getName() + "-->" + Thread.currentThread().getPriority());
MyPriority myPriority = new MyPriority();
Thread thread1 = new Thread(myPriority);
Thread thread2 = new Thread(myPriority);
Thread thread3 = new Thread(myPriority);
Thread thread4 = new Thread(myPriority);
//先设置优先级,再启动
thread2.setPriority(Thread.MIN_PRIORITY);
thread3.setPriority(8);
thread4.setPriority(Thread.MAX_PRIORITY);// ==10
thread1.start();
thread2.start();
thread3.start();
thread4.start();
}
}
class MyPriority implements Runnable{
@Override
public void run() {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "-->" + Thread.currentThread().getPriority());
}
}
暂未解决的问题
由于某种原因并不能正确按照理想情况输出(偶尔也会),设置了sleep也不起作用,大部分都在随机输出,课程弹幕也有同样问题。
可以观察到main方法默认优先级大小是5,处于中间位置