注:自用,无逻辑,复习到哪写到哪
抽象类和接口
抽象类:一个类中如果有抽象方法,那么该类一定是抽象方法
接口:抽象方法的集合,即该类中只有抽象方法,默认修饰abstract
注意点:
1)与接口相比,抽象方法的abstract关键字不能省略
2)有抽象方法的一定是抽象类,但抽象类不一定有抽象方法
3)与接口相比,抽象类中的成员属性可以是常量,也可以是变量(接口的成员属性默认被final修饰)
内部类及匿名内部类
内部类
class Outer{
private int num = 10;
public void method(){
private int num = 20; //定义一个局部变量
class Inner{
private int num = 30;
public void show(){
System.out.println(num);
System.out.println(this.num);
System.out.println(Outer.this.num);
}
}
}
}
上述代码结果为20、30、10
自jdk1.8后,局部内部类访问的局部变量默认加修饰符final,1.8之前不加final编译会出错。
原因:内部类对象的生命周期和局部变量的生命周期不一致
匿名内部类
是内部类的一种简化方式,本质为继承该类或者实现接口的子类匿名对象
interface Message{
public void send(String str);
}
class Demo{
public static void main(String args[]){
new Message(){
public void send(String str){
System.out.println(str);
}
}.send("匿名内部类");
}
}
String和StringBuffer作为方法参数类型
String类型作为参数
class Demo{
public static void main(String args[]){
String a = "hello";
String b = "world";
change(a,b);
System.out.println("a = " + a); //a = hello
System.out.println("b = " + b); //b = world
}
public static void change(String x,String y){
x = y;
System.out.println("x = " + x); //x = world
y = x + y;
System.out.println("y = " + y); //y = worldworld
}
}
解析:String作为特殊的引用类型,字符串类型,作为参数传递和基本数据类型作为参数传递效果一样
即形参的改变对实际参数没有影响。
StringBuffer作为参数传递
class Demo{
public static void main(String args[]){
StringBuffer a = new StringBuffer("hello");
StringBuffer b = new StringBuffer("world");
change(a,b);
System.out.println("a = " + a); //a = hello
System.out.println("b = " + b); //b = worldworld
}
public static void change(StringBuffer x,StringBuffer y){
x = y;
System.out.println("x = " + x); //x = world
y.append(x);
System.out.println("y = " + y); //y = worldworld
}
}
解析:x = y只改变了形参的指向,而没有改变实参的指向,append方法则改变的是字符串本身。
多线程
1、线程和进程
进程:是程序一次执行的过程,是系统运行程序的基本单位,因此进程是动态的。
线程:是在进程基础上划分更小的程序单元,线程是在进程基础上创建并且使用的,所以线程依赖于进程的支持。
线程的启动速度比进程快很多,使用多线程进行并发处理的时候,执行的效率比进程快很多,所以线程又被称为轻量级进程
2、多线程
多个线程同时运行或交替运行,单核CPU是顺序执行,即交替运行,多核CPU可以在多个CPU中同时运行。
3、实现多线程的相关类及接口
public class Thread extends Object implements Runnable
java.lang.Thread包中,一个类只要继承了此类,就表示这个类为线程的主体类
覆写public void run()方法作为线程主方法,实际来自于Runnable接口
调用public void start()方法是唯一多线程启动的方法
class MyThread extends Thread{
private String title;
public MyThread(String title){
this.title = title;
}
@Override
public void run(){
for(int i = 0 ; i < 10 ; i++){
System.out.println(this.title + "运行:" + i);
}
}
}
pubilc class ThreadDemo{
public static void main(String args[]){
new MyThread("线程A").start();
new MyThread("线程B").start();
new MyThread("线程C").start();
}
}
观察Thread类继承了Runnable接口,此接口作为一个函数式接口,只有一个抽象方法即:public void run(),因此继承Thread类实现的run()方法其实可以直接实现Runnable接口。
而Thread类中的run方法源代码如下:
@Override
public void run(){
if(target != null){
target.run();
}
}
而target在Thread类里是一个Runnablel类型的成员变量,Thread提供如下的构造方法,因此可以判断Thread可以接收一个Runnable的实现类。
public Thread(Runnable target);
因为Runnable是一个函数式接口,因此启动多线程也可以用Lambda表达式
new Thread(()->{
for(int i = 0 ; i < 10 ; i++){
System.out.println("i = " + i);
}
}).start();
就结构来讲,两种多线程实现的方式更推荐Runnable,因为可以避免单继承的局限,也可以更好的进行功能扩充。
4、线程的相关方法
1)线程的命名与取得
构造方法:public Thread(Runnable target,String name);
设置名字:public final void setName(String name);
取得名字:pbulic final String getName();
获取当前线程名字:public static Thead currendtThread().getName();
new Thread(()->{
for(int i = 0 ; i < 10 ; i++){
System.out.println("i = " + i);
}
},"线程A").start();
当在主方法输入如下代码。也可以获取一个线程名
public class Demo {
public static void main(String[] args) {
System.out.println(Thread.currentThread().getName());
//输出为main
}
}
结果为main,说明主方法也是一个线程
解析:每当使用java命令执行程序的时候就表示启动了一个JVM的进程,一台电脑上可以启动若干个JVM进程,每一个JVM进程都会有各自的线程。
主线程可以创建若干个子线程,创建子线程的目的是可以将一些复杂逻辑或者比较耗时的逻辑交由子线程处理,即主线程负责整体流程,而子线程负责处理耗时操作。
2)线程的休眠与中断
如果希望某个线程可以暂缓执行一个,可以使用休眠处理,方法如下:
public static void sleep(long millis) throws InterruptedException;
public static void sleep(long millis,int nanos) throws InterruptedException;
在进行休眠时可能会产生中断异常,该异常必须进行处理
线程休眠时可以被打断,该打断由另一个线程完成
方法如下:
判断线程是否被中断:public boolean isInterrupted()
中断线程执行:public void interrupt();
Thread thread = new Thread(()->{
System.out.println("需要睡觉补充精力");
try {
Thread.sleep(10000);//预计准备休眠10秒
System.out.println("睡足了,可以继续工作");
} catch (InterruptedException e) {
System.out.println("被中断睡眠");
}
});
thread.start();
//主线程开始进行中断
Thread.sleep(1000);
if(!thread.isInterrupted()) { //该线程中断了吗
System.out.println("打扰睡眠");
System.out.println(Thread.currentThread().getName()); //main
thread.interrupt(); //中断线程的休眠是由另一个线程所做的事
}
//所有正在执行的线程都是可以被中断的,中断线程必须进行异常处理
3)线程的礼让
线程的礼让指先将资源让给别的线程执行,方法如下
public static void yield();
每次调用该方法只会礼让一次当前的资源
Thread thread = new Thread(()->{
for(int i = 0 ; i < 100 ; i++) {
if(i % 3 == 0) {
Thread.yield();
System.out.println("玩耍的线程执行礼让");
}
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "执行 i = " + i);
}
},"玩耍的线程");
thread.start();
for(int x = 0 ; x < 100 ; x++) {
Thread.sleep(100);
System.out.println("霸道的main线程 number = " + x);
}
4)线程的优先级
从理论上来讲,线程的优先级越高,越有可能抢占到资源,方法如下
设置优先级:public final void setPriority(int newPriority);
获取优先级:public final int getPriority();
在进行获取优先级定义的时候通过Int型数字来完成,而对于数字的选择在Thread类中定义有三个常量
最高优先级:public static final int MAX_PRIORITY; // 10
中等优先级:public static final int NORM_PRIORITY; //5
最低优先级:public static final int MIN_PRIORITY; //1
main线程的优先级属于中等优先级,而默认创建的线程也是中等优先级
5)守护线程
我们平常创建的普通线程成为用户线程,而服务于用户线程的线程,被称为守护线程。
守护线程在用户线程执行的过程中一直存在,并且运行在后台状态。
方法如下:
设置为守护线程:public final void setDaemon(boolean on);
判断是否为守护线程:public final boolean isDaemon();
在整个JVM里面最大的守护线程就是GC线程。程序执行中GC线程会一直存在,如果程序执行完毕,GC线程也将消失。
Synchronized关键字
用于同步代码操作。一般采用当前对象this进行同步,可以定义同步方法或同步代码块,在同步代码块或方法中,只允许一个线程执行。
一般同步会造成性能下降。
//假定有100张票,3个票贩子进行抢票,为保证票数的数据同步,需要为购买操作的代码块加锁
class BuyTicket implements Runnable{
private int count = 100;
@Override
public void run() {
while(true) {
synchronized(this){ //每一次只允許一個線程進行訪問 同步代码块
if(this.count > 0) {
try {
Thread.sleep(100);//模拟网络延迟
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "买票,票数为 = " + this.count--);
}
else {
System.out.println("票卖光了");
break;
}
}
}
}
}
public class Demo {
public static void main(String[] args) {
BuyTicket by = new BuyTicket();
new Thread(by,"票贩子A").start();
new Thread(by,"票贩子B").start();
new Thread(by,"票贩子C").start();
}
}
线程的交替执行问题—线程的唤醒与睡眠
现有生产者与消费者,需求如下
1)生产者负责信息内容的生产;
2)每当生产者生产完成一项完整的信息之后消费者要从这里取走信息
3)如果生产者没有生产完,则消费者要等待它生产完成,如果消费者还没有对信息进行消费,则生产者应该等待消费者消费后继续生产
解决思路:
1)可以使用Object类中的睡眠和唤醒线程方法
当产品生产完毕,生产线程睡眠,唤醒消费线程。
当消费完产品,消费线程睡眠,唤醒生产线程。
Object类中方法如下:
线程等待:public final void wait() throws InterruptedException
唤醒第一个等待线程:public final void notify();
唤醒全部等待线程:public final void notifyAll();
使用唤醒或等待方法时,如果没有用synchronized绑定住wait/notify的对象,将会出现java.lang.IllegalMonitorStateException的异常。
//生产消费的同一种产品
class Message{
private String title;
private String content;
private boolean flag = true;//表示生产或消费的形式
//flag = true 允许生产,但不允许消费
//flag = false 允许消费,但不允许生产
//生产方法
public synchronized void set(String title,String content) {
if(this.flag == false) { //允许消费,不允许生产
try {
super.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
this.title = title;
this.content = content;
this.flag = false;//生产完毕,等待消费
super.notify();//唤醒等待的消费线程
}
//消费方法
public synchronized String get() {
if(this.flag == true) { //true表示该生产了
try {
super.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
try {
return this.title + "-" + this.content;
}
finally {
this.flag = true;//继续生产
super.notify();//唤醒生产线程
}
}
}
class Producer implements Runnable{
private Message msg;
public Producer(Message msg) {
this.msg = msg;
}
@Override
public void run() {
for(int i = 0 ; i < 100 ; i++) {
if(i % 2 == 0) {
try {
Thread.sleep(100);//模拟网络延迟
} catch (InterruptedException e) {
e.printStackTrace();
}
this.msg.set("111","11111111");
}
else {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
this.msg.set("222","22222222");
}
}
}
}
class Consumer implements Runnable{
private Message msg;
public Consumer(Message msg) {
this.msg = msg;
}
@Override
public void run() {
for(int i = 0 ; i < 100 ; i++) {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(this.msg.get());
}
}
}
public class PCDemo {
public static void main(String[] args) {
Message msg = new Message();
new Thread(new Producer(msg)).start(); //启动生产者线程
new Thread(new Consumer(msg)).start(); //启动消费者线程
}
}