静态代码块
static {},可初始化操作
匿名对象
只能被调用一次,可作为参数传递,较灵活
内部类
成员内部类 -- 写在成员位置
成员内部类访问外部类成员的特点:直接访问 包括私有的。
格式:外部类.内部类 变量名 = new 外部类().new 内部类();
外部类访问内部类的特点: 需要先创建内部类对象,再进行访问,可以直接访问,包括私有的
局部内部类 -- 写在局部位置
访问特点 :直接访问 包括私有的
public class Outer {
public static void main(String[] args) {
//局部内部类 不需要加修饰符
String name = "张三";
class Inner{
private void function(){
System.out.println(name);
}
}
new Inner().function();
}
}
public class Outer {
public Outer() {
}public static void main(String[] args) {
final String name = "张三";
class Inner {
Inner() {
}public void function() {
System.out.println(name);
}
}(new Inner()).function();
}
}问题:
final String name = "张三"中为什么要加这一个final?
注意:在JDK1.8之前 需要手动添加final 1.8之后 自动添加
1、name为局部变量,存在栈中,方法运行完变量消失。inner为对象引用存在堆中,当不经常使用的时候会被GC回收。
2、当 inner对象调用方法正在使用name局部变量的时候就消失,不合常理。
3、将定义一个被final修饰的常量存在常量池中(堆中),生命周期比较长久,在jvm退出的时候才消失。
匿名内部类
格式:new 类(包含抽象类)/接口(){重写方法;};
创建的是当前new的这个类或者接口的子类对象,只不过这个子类没有名字
public interface Animal {void eat();
//1.8新特性 定义默认方法
default void show(){
System.out.println("你好....");
}
}
public class Student implements Animal {
@Override
public void eat() {
System.out.println("chi ---");
}
}public class Outer {
public static void main(String[] args) {
Animal animal = new Student();
animal.eat();
System.out.println("-----------------");
//匿名内部类
//现在创建的对象是谁?名字我们不知道,但是我们知道 创建的是 Animal的实现类的对象
//多态
Animal a = new Animal(){@Override
public void eat() {
System.out.println("chibuchi---");
}
};
a.eat();
System.out.println("---------------------------");
//创建Student子类对象
Student stu = new Student(){};
stu.eat();
System.out.println("-------------------------");
//将这个整体看做成 匿名对象
//调用方法 只能调用一次
//做为参数传递 传递前不能做其他的操作
new Animal(){@Override
public void eat() {
System.out.println("哈哈");
}
}.eat();
System.out.println("-----------------------");
//Student student = new Student();
show(new Animal(){@Override
public void eat() {
System.out.println("我跟student是兄弟....");
}
});
}public static void show(Animal a){
a.eat();
}
}
多线程
进程 -- 在内存中正在运行的软件
线程 -- 进程中最小的执行单元
没有线程哪来的进程 -- 存在进程 最少得有一个线程 no code
多线程 -- 多个线程并发的执行
并发:在同一个小的时间段内 线程可以同时执行
线程创建方式一
创建一个类继承Thread
重写Thread的run方法
创建子类对象
启动线程
public class PrimeThread extends Thread{
//2.重写run方法
@Override
public void run() {
//线程中需要执行的 功能(代码)
//fori itar iter
for (int i = 0; i < 10; i++) {
System.out.println(i);
}
}
}public class ThreadDemo {
//main方法本身也是一个线程 由JVM开启的线程
public static void main(String[] args) {
//3.创建thread的子类对象
PrimeThread t1 = new PrimeThread();
//4.启动线程
//t1.run(); 调用方法
t1.start();//启动线程for (int i = 0; i < 10; i++) {
System.out.println("main:" + i);
}
}
}线程创建方式二
创建一个类实现Runnable接口
实现run方法
创建Runnable实现类的对象
将 Runnable实现类的对象作为参数 创建Thread对象
启动线程 start方法
public class PrimeRunnable implements Runnable {
//2. 实现run方法
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName() + ":" + i);
}
}
}public class PrimeDemo {public static void main(String[] args) {//3. 创建Runnable实现类的对象PrimeRunnable primeRunnable = new PrimeRunnable();//4. 将 Runnable实现类的对象作为参数 创建Thread对象Thread t1 = new Thread(primeRunnable);Thread t2 = new Thread(primeRunnable);//5. 启动线程 start方法t1.start();t2.start();}}第一种继承方式 继承 只支持单继承 耦合度比较高
第二种方式 实现 多实现 耦合性比较低
采用第二种较好。
线程的安全性问题
当多个线程 共享同一个资源的时候 就有可能会发生线程安全性的问题!!
1、重复票
2、0张票
3、负数票
使用锁解决线程安全性问题
加锁
同步代码块
synchronized( 需要一个任意的对象(锁))
{
代码块中放操作共享数据的代码。
}
public class TicketsThread extends Thread {
//100 张票 三个窗口售票
//每一个窗口都有100张票 还是 三个窗口共同出售 100张票
//被static修饰的属性的特点 只有一份 被所有的对象共享
private static int tickets = 100;//锁对象的条件 被所有的线程共享 是一个惟一的
private static Student stu = new Student();@Override
public void run() {
while(true){synchronized (stu) {
if (tickets > 0) {
System.out.println(getName() + "正在出售第" + tickets + "张票");
tickets--;
} else {
break;
}
}
}
}
}public class Demo {
public static void main(String[] args) {
TicketsThread t1 = new TicketsThread();
t1.setName("小明");
TicketsThread t2 = new TicketsThread();
t2.setName("小红");
TicketsThread t3 = new TicketsThread();
t3.setName("小兰");
t1.start();
t2.start();
t3.start();
}
}线程安全的 效率低
之前 学习 集合 StringBuilder 线程不安全的 -- 效率高
Thread.currentThread().getName();获取当前线程对象的名称。
为什么要复写run方法?
因为我们希望程序中的某段代码可以同时运行,提高程序的运行效率。就是start启动线程之后,JVM会自动的调用run方法。
为什么要调用start而不是run?
start方法 :调用系统资源,启动线程 ,开启一个独立的新的空间。并在新的空间中自动去运行run方法。
run方法:普通的方法 ,不能调用系统资源。
start方法和run方法的区别?
run:只是封装线程任务。
start:先调用系统资源,在内存中开辟一个新的空间启动线程,再执行run方法。
线程的生命周期
新建状态:当我们使用new去创建线程对象的时候。
就绪状态:当我们调用start方法后,线程就有了获得CUP执行权的资格
运行状态:当线程获得CPU的使用权后就进入了运行状态。
注意:就绪状态与运行状态是可以相互转换的,当线程获得CPU使用权就进入运行状态
失去CPU使用权后重新回到就绪状态,等待CPU切到当前线程。
阻塞状态:在运行时期的线程调用了sleep方法或者在等待同步锁的时候就进入了阻塞状态。
当获取到同步锁,或者sleep时间到了的时候则又进入了就绪状态。
死亡状态:当run方法执行完毕或者发生了异常后,线程进入死亡状态。
线程 -- 并发执行
线程不安全 -- 执行效率会高!!
线程是不是越多越好? 线程越多 被CPU切到几率越低
产生一些安全性问题 -- 多个线程 争抢同一个资源
解决方案 -- 加锁 -- 效率会变