最近准备秋招,就开始之前java内容的复习,把复习过程中一些基本知识记录了下来,预祝自己秋招顺利吧。
基础篇
1.进程和线程
进程就是应用程序在内存中分配的空间,也就是正在运行的程序,各个进程之间互不干扰。同时进程保存着程序每一个时刻运行的状态。
一个进程就包含了多个线程,每个线程负责一个单独的子任务。
进程和线程的提出极大的提高了操作系统的性能。进程让操作系统的并发性成为了可能,而线程让进程的内部并发成为了可能。
1.1进程和线程的区别
进程是一个独立的运行环境,而线程是在进程中执行的一个任务。他们两个本质的区别是是否单独占有内存地址空间及其它系统资源(比如I/O):
- 进程单独占有一定的内存地址空间,所以进程间存在内存隔离,数据是分开的,数据共享复杂但是同步简单,各个进程之间互不干扰;而线程共享所属进程占有的内存地址空间和资源,数据共享简单,但是同步复杂。
- 进程单独占有一定的内存地址空间,一个进程出现问题不会影响其他进程,不影响主程序的稳定性,可靠性高;一个线程崩溃可能影响整个程序的稳定性,可靠性较低。
- 进程单独占有一定的内存地址空间,进程的创建和销毁不仅需要保存寄存器和栈信息,还需要资源的分配回收以及页调度,开销较大;线程只需要保存寄存器和栈信息,开销较小。
另外一个重要区别是,进程是操作系统进行资源分配的基本单位,而线程是操作系统进行调度的基本单位,即CPU分配时间的单位 。
1.2上下文切换
上下文切换有时也叫做线程切换或者任务切换,是指cpu从一个进程(或线程)切换到另一个进程或(者线程)。上下文是指某一个时间点cpu寄存器和程序计数器的内容。
举例说明 线程A ---> B
1.先挂起线程A,将其在cpu中的状态保存在内存中。
2.在内存中检索下一个线程B的上下文并将其在 CPU 的寄存器中恢复,执行B线程。
3.当B执行完,根据程序计数器中指向的位置恢复线程A。
2.Java多线程入门类和接口
JDK提供了Thread
类和Runnable
接口来让我们实现自己的“线程”类。
- 继承
Thread
类,并重写run
方法; - 实现
Runnable
接口的run
方法;
2..1 继承Thread
类
public class MyThread_01 extends Thread{
@Override
public void run() {
System.out.println("MyThread_01 run");
}
public static void main(String[] args) {
MyThread_01 myThread_01 = new MyThread_01();
myThread_01.start();
}
}
注意要调用start()
方法后,该线程才算启动!
我们在程序里面调用了start()方法后,虚拟机会先为我们创建一个线程,然后等到这个线程第一次得到时间片时再调用run()方法。
注意不可多次调用start()方法。在第一次调用start()方法后,再次调用start()方法会抛出illegalThreadStateException异常。
2.2实现Runnable接口
略
小细节
当main线程启动一个线程的时候,main线程并不会阻塞,会和新开的线程交替执行
public class Demo1 extends Thread{
@Override
public void run() {
for (int i=0; i<5;i++){
try {
Thread.sleep(500);
System.out.println("线程启动了...【"+ Thread.currentThread().getName()+"】");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) throws InterruptedException {
Demo1 demo1 = new Demo1();
demo1.start();
for (int i = 0; i < 5; i++) {
System.out.println("main 线程启动");
Thread.sleep(1000);
}
}
}
运行结果:
main 线程启动
线程启动了...【Thread-0】
main 线程启动
线程启动了...【Thread-0】
线程启动了...【Thread-0】
main 线程启动
线程启动了...【Thread-0】
线程启动了...【Thread-0】
main 线程启动
main 线程启动Process finished with exit code 0
实现Runnable接口的方式更加适合多个线程共享一个资源的情况
jdk1.8新特性Lamda表达式
属于函数式编程
lambda 表达式的语法格式如下:
(parameters) -> expression
或
(parameters) ->{ statements; }
public class MyLamda {
public static void main(String[] args) {
Like like = ()->{
System.out.println("i like java lamda");
};
like.say();
}
}
interface Like{
void say();
}
线程的状态
线程的优先级
java中优先级可以指定,范围是1——10(从低到高),默认优先级为5
但是,并不一定是优先级越高越先执行,要取决于操作系统,java只是给操作系统一个优先级的参考值,线程最终在操作系统的优先级还是由操作系统决定的。
我们使用方法Thread
类的setPriority()
实例方法来设定线程的优先级。
守护线程
在Java中有两类线程:User Thread(用户线程)、Daemon Thread(守护线程)
如果 User Thread已经全部退出运行了,只剩下Daemon Thread存在了,虚拟机也就退出了。 因为没有了被守护者,Daemon也就没有工作可做了,也就没有继续运行程序的必要了。
当主线程结束之后,守护线程也就结束
public class ShouHu {
public static void main(String[] args) {
Runnable runnable=()->{
for( ; ; ){
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("我是守护线程");
}
};
Thread thread = new Thread(runnable);
thread.setDaemon(true);
thread.start();
for (int i = 0; i < 5; i++) {
System.out.println("main线程");
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
线程同步
在多线程编程,一些敏感数据不允许被多个线程同时访向,此时就使用同步访问技术,保证数据在任何同一时刻,最多有一个线程访向,以保证数据的完整性
synchronized
synchronized是Java中的关键字,是一种同步锁。它修饰的对象有以下几种:
- 修饰一个代码块,被修饰的代码块称为同步语句块,其作用的范围是大括号{}括起来的代码,作用的对象是调用这个代码块的对象;
- 修饰一个方法,被修饰的方法称为同步方法,其作用的范围是整个方法,作用的对象是调用这个方法的对象;
- 修改一个静态的方法,其作用的范围是整个静态方法,作用的对象是这个类的所有对象;
- 修改一个类,其作用的范围是synchronized后面括号括起来的部分,作用主的对象是这个类的所有对象。
死锁
产生死锁的四个必要条件:
1.互斥条件:一个资源每次只能被一个进程使用。
2.请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放
3.不剥夺条件:进程已获得的资源,在末使用完之前,不能强行剥夺。
4.循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。
释放锁
Lock锁
1、从Java5开始,Java提供了一种功能更强大的线程同步机制——通过显式定义同步锁对象来实现同步,在这种机制下,同步锁由Lock对象充当。
2、Lock 提供了比synchronized方法和synchronized代码块更广泛的锁定操作,Lock允许实现更灵活的结构,可以具有差别很大的属性,并且支持多个相关的Condition对象。
3、Lock是控制多个线程对共享资源进行访问的工具。通常,锁提供了对共享资源的独占访问,每次只能有一个线程对Lock对象加锁,线程开始访问共享资源之前应先获得Lock对象。
4、某些锁可能允许对共享资源并发访问,如ReadWriteLock(读写锁),Lock、ReadWriteLock是Java5提供的两个根接口,并为Lock 提供了ReentrantLock(可重入锁)实现类,为ReadWriteLock提供了ReentrantReadWriteLock 实现类。
5、Java8新增了新型的StampedLock类,在大多数场景中它可以替代传统的ReentrantReadWriteLock。ReentrantReadWriteLock 为读写操作提供了三种锁模式:Writing、ReadingOptimistic、Reading。
public class MyLock {
public static void main(String[] args) {
TestLock testLock = new TestLock();
new Thread(testLock).start();
new Thread(testLock).start();
new Thread(testLock).start();
}
}
class TestLock implements Runnable{
private int tickets = 100;
//定义锁
private final ReentrantLock reentrantLock = new ReentrantLock();
@Override
public void run() {
while (true){
//加锁
reentrantLock.lock();
if(tickets<=0){
break;
}
System.out.println(Thread.currentThread().getName()+", "+(tickets--));
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
//解锁
reentrantLock.unlock();
}
}
}
}