1—线程
1.1—runable创建线程
3.2 实现Runnable接口
自定义类实现Runnable,实现里面run()方法,创建Thread类,使用Runnable接口的实现对象作为参数传递给Thread对象,调用Strat方法。
优点:线程类可以实现多个几接口,可以再继承一个类
缺点:没返回值,不能直接启动,需要通过构造一个Thread实例传递进去启动
public class ThreadDemo2 implements Runnable {
@Override
public void run() {
System.out.println("通过Runnable实现多线程,名称:"+Thread.currentThread().getName());
}
}
public static void main(String[] args) {
ThreadDemo2 threadDemo2 = new ThreadDemo2();
Thread thread = new Thread(threadDemo2);
thread.setName("demo2");
// start线程执行
thread.start();
System.out.println("主线程名称:"+Thread.currentThread().getName());
}
// JDK8之后采用lambda表达式
public static void main(String[] args) {
Thread thread = new Thread(() -> {
System.out.println("通过Runnable实现多线程,名称:"+Thread.currentThread().getName());
});
thread.setName("demo2");
// start线程执行
thread.start();
System.out.println("主线程名称:"+Thread.currentThread().getName());
}
1.2—通过线程池创建线程
自定义Runnable接口,实现run方法,创建线程池,调用执行方法并传入对象
优点:安全高性能,复用线程
缺点: jdk5后才支持,需要结合Runnable进行使用
public class ThreadDemo4 implements Runnable {
@Override
public void run() {
System.out.println("通过线程池+runnable实现多线程,名称:" +
Thread.currentThread().getName());
}
}
public static void main(String[] args) {
// 创建线程池
ExecutorService executorService = Executors.newFixedThreadPool(3);
for(int i=0;i<10;i++){
// 线程池执行线程任务
executorService.execute(new ThreadDemo4());
}
System.out.println("主线程名称:"+Thread.currentThread().getName());
// 关闭线程池
executorService.shutdown();
}
一般常用的Runnable 和 第四种线程池+Runnable,简单方便扩展,和高性能 (池化的思想)
1.3.–继承Thread类
步骤:
1.定义一个类继承Thread类
2.重写run方法:里面写线程要运行的任务代码
3.创建Thread子类对象
4.调用start方法:开启线程并调用run方法
1.4.-实现Callable接口
步骤
1.创建Callable的实现类
2.重写call方法,将线程的任务代码封装到call方法中
—线程的几个状态
说说线程的生命周期和状态?
Java 线程在运行的生命周期中的指定时刻只可能处于下面 6 种不同状态的其中一个状态(图源《Java 并发编程艺术》4.1.4 节)。
2. Java中可以有哪些方法来保证线程安全?
加锁:比如synchronize/ReentrantLock
使用volatile声明变量,轻量级同步,不能保证原子性(需要解释)
使用线程安全类,例如原子类 AtomicXXX等
使用线程安全集合容器,例如:CopyOnWriteArrayList/ConcurrentHashMap等
ThreadLocal本地私有变量/信号量Semaphore等
3.–辗转相除求最大公因数(临时加的)
求2个数m,n(m>n)的最大公因数
//解题思路:
若m%n==0,则n是m和n的最大公因数。
若m%n==k,则递归执行n%k==k2,k%k2==k3 … 直到取余的结果为0,则被取余数kn就是 m和n的最大公因数!
答案如下:
public class Test13 {
public static void main(String[] args) {
System.out.println(f5(35, 12));
}
/**
* 求2个数的最大公因数
* @param m
* @param n
* @return
*/
static int f5(int m, int n) {
if (n == 0) {
return m;
}
return f5(n, m % n);
}
4.–练习7:求最2个数的最小公倍数
求2个数m,n(m>n)的最小公倍数
解题思路:
最小公倍数,可以通过m和n的乘积除以二者的最大公约数,即可得到!
公式:最小公倍数 = m* n / 最大公约数
答案如下:
public class Test13 {
public static void main(String[] args) {
// 最大公约数
System.out.println(f5(16, 12));
// 最小公倍数
System.out.println(16*12/f5(16, 12));
}
/**
* 求2个数的最大公因数
* @param m
* @param n
* @return
*/
static int f5(int m, int n) {
if (n == 0) {
return m;
}
return f5(n, m % n);
}
5.–为什么说 Java 语言“编译与解释并存”?
这是因为 Java 语言既具有编译型语言的特征,也具有解释型语言的特征。因为 Java 程序要经过先编译,后解释两个步骤,
-
由 Java 编写的程序需要先经过编译步骤,生成字节码(
.class
文件), -
这种字节码必须由 Java 解释器来解释执行。
6.–基本类型和包装类型的区别?
- 包装类型不赋值就是
null
,而基本类型有默认值且不是null
。 - 包装类型可用于泛型,而基本类型不可以。
- 基本数据类型的局部变量存放在 Java 虚拟机栈中的局部变量表中,基本数据类型的成员变量(未被
static
修饰 )存放在 Java 虚拟机的堆中。包装类型属于对象类型,我们知道几乎所有对象实例都存在于堆中。 - 相比于对象类型, 基本数据类型占用的空间非常小。
7.–包装类型的常量池技术?
Byte
,Short
,Integer
,Long
这 4 种包装类默认创建了数值 [-128,127] 的相应类型的缓存数据,Character
创建了数值在 [0,127] 范围的缓存数据,Boolean
直接返回True
orFalse
。
8.–面向对象和面向过程的区别
两者的主要区别在于解决问题的方式不同:
- 面向过程把解决问题的过程拆成一个个方法,通过一个个方法的执行解决问题。
- 面向对象会先抽象出对象,然后用对象执行方法的方式解决问题。
9.–成员变量与局部变量的区别有哪些?
- 语法形式 :从语法形式上看,成员变量是属于类的,而局部变量是在代码块或方法中定义的变量或是方法的参数;成员变量可以被
public
,private
,static
等修饰符所修饰,而局部变量不能被访问控制修饰符及static
所修饰;但是,成员变量和局部变量都能被final
所修饰。 - 存储方式 :从变量在内存中的存储方式来看,如果成员变量是使用
static
修饰的,那么这个成员变量是属于类的,如果没有使用static
修饰,这个成员变量是属于实例的。而对象存在于堆内存,局部变量则存在于栈内存。 - 生存时间 :从变量在内存中的生存时间上看,成员变量是对象的一部分,它随着对象的创建而存在,而局部变量随着方法的调用而自动生成,随着方法的调用结束而消亡。
- 默认值 :从变量是否有默认值来看,成员变量如果没有被赋初始值,则会自动以类型的默认值而赋值(一种情况例外:被
final
修饰的成员变量也必须显式地赋值),而局部变量则不会自动赋值。
10.–创建一个对象用什么运算符?对象实体与对象引用有何不同?
-
new 运算符,new 创建对象实例(对象实例在堆内存中),对象引用指向对象实例(对象引用存放在栈内存中)。
-
一个对象引用可以指向 0 个或 1 个对象(一根绳子可以不系气球,也可以系一个气球);一个对象可以有 n 个引用指向它(可以用 n 条绳子系住一个气球)。