第一章 序列化
1.概述
java提供了一种对象序列化的机制。用一个字节序列表示一个对象,该字节序列包含对象的数据、对象的类型、对象的属性等..... 字节写出到文件之后,相当于文件中持久保存了一个对象的信息。
反之,字节序列还可以从文件中读取回来,重构对象。进行反序列化。对象的数据、类型、属性等信息,都可以用来在内存中创建对象。
2.ObjectOutputStream类 序列化流
可以将java对象的原始数据类型写出到文件,实现对象的持久存储。
序列化操作:相当于是进行加密和解密,让其非正常显示。
1.一个对象想要被序列化,必须满足两个条件
1.1: 该类必须实现java.io.Serializable接口,Serializable接口是一个标记接口如果不实现这个接口,不能被序列化或者反序列化,会抛出NotSerializableException异常。
1.2: 该类的所有属性必须是能序列化的,如果由一个属性不需要可序列化,则该属性必须注明是瞬态的,使用transient关键字修饰;
瞬态:瞬态属性通常存储临时状态或者临时计算结果,不需要持久存储。
public class Demo01 {
public static void main(String[] args) throws Exception {
/**
* ObjectOutputStream 序列化流构造方法
*/
FileOutputStream fileOutputStream=new FileOutputStream("a.txt");
ObjectOutputStream out=new ObjectOutputStream(fileOutputStream);
Student student=new Student("zhang3","哈尔滨",33);
out.writeObject(student);//序列化,写入文件
out.close();
fileOutputStream.close();
}
}
3.ObjectInputStream类 反序列化流
public class Demo02 {
/**
* ObjectInputStream 反序列化流
*/
public static void main(String[]args)throws Exception{
Student s=null;
FileInputStream fileInputStream= new FileInputStream("a.txt");
ObjectInputStream in=new ObjectInputStream(fileInputStream);
// 读一个对象
s= (Student) in.readObject();
in.close();
fileInputStream.close();
System.out.println("s = "+s);
}
}
第二章 打印流
平时控制台打印输出,都是调用print或者println方法,这两个方法都来自于java.io.PrintStream类,该类能够方便的打印各种数据类型的值。是一种便捷输出
public class Demo03 {
public static void main(String[] args) throws Exception {
/**
* PrintStream类 构造方法
*/
PrintStream printStream = new PrintStream("a.txt");
/**
* System.out 就是PrintStream类 只不过他的流向是系统规定的流向控制台,可用set修改
*/
System.out.println("97");
// 设置系统的打印流走向 , 输出到a.txt;
System.setOut(printStream);
System.out.println(96);
System.out.println(96);
}
}
上述代码会将 '97' 输出到控制台,将两个 '96' 写到 “a.txt” 文件中,因为用了System.setOut(printStream);改变流向。
第三章 多线程
之前写代码 如果没有跳转语句都是从上到下的执行,那么现在想要设计一个程序, 边打游戏边听歌,怎么设计?
解决上面问题,就要使用多线程或者多进程解决。
1.并发与并行。
- 并行:指两件或者多个事件同一时刻发生(同时执行)。
- 并发:指两件或者多个事情同一个时间段内发生(交替执行)。
2.进程和线程
- 进程:指内存中运行的应用程序,一个应用程序可以同时运行多个进程,进程也是程序的依次执行的过程。是系统运行程序的基本单位。系统运行一个程序就是进程创建、运行、消亡的过程。
- 线程:进程中的一个执行单元,负责当前进程中程序的执行,一个进程至少有一个线程。一个进程如果有多个线程,这个应用程序就称之为多线程程序。
3.线程类
在java中要想实现多线程,有两种手段,一种是继续Thread类,另外一种是实现Runable接口。
java 使用java.lang.Thread 类代表线程,所有线程对象都必须是Thread或者其子类的实例。
每个线程的作用是完成一定的任务,其实就是执行一段程序或者代码。
java中通过继承Thread类来创建并启动多线程。
步骤:
1.定义Thread类的子类,重写该类中的run方法,run方法的方法体中内容就代表了线程需要完成的任务。
2.创建Thread子类的实例,也就是创建了线程对象。
3.调用线程对象的start方法启动线程。
public class MyThread extends Thread{
// 定义指定线程名称的构造方法
public MyThread(String name){
// 调用父类的String参数的构造方法,可以执行线程名称
super(name);
}
@Override
public void run(){
for (int i = 0; i < 100; i++) {
System.out.println(getName()+"正在运行"+i);
}
}
}
public class Demo04 {
public static void main(String[] args) {
MyThread myThread=new MyThread("我的线程");
myThread.start();
for (int i = 0; i < 99; i++) {
System.out.println("main线程"+i);
}
}
}
打印输出“我的线程”和“main线程”交替打印
4.Thread类Api
Thread类:
构造方法:
* public Thread(): 分配一个新的线程对象
* public Thread(String name): 分配一个指定名字的新的线程对象
* public Thread(Runnable target): 分配一个带有指定目标的新的线程对象
* public Thread(Runnable target,String name) :分配一个带有指定目标的新的线程对象并指定名字
常用方法:
* public String getName(): 获取当前线程的名字
* public void start(): 让此线程开始执行
* public void run(): 此线程执行任务的代码在此定义
* public static void sleep(long millis): 当前在在执行的线程以指定的毫秒数暂停
* public static Thread currentThread(): 返回对当前正在执行的线程对象。
5.实现接口方式创建线程
public class MyRunnable implements Runnable {
@Override
public void run() {
for (int i = 0; i < 99; i++) {
System.out.println(Thread.currentThread().getName() + " " + i);
}
}
}
public class Demo06 {
public static void main(String[] args) {
// 创建自定义类对象 线程任务对象
MyRunnable mr =new MyRunnable();
// 创建线程对象
Thread t =new Thread(mr,"小强");
// 开启线程
t.start();
for (int i = 0; i < 99; i++) {
System.out.println("main"+i);
}
}
}
6.匿名内部类方式
/**
* 使用匿名内部类方式 方便创建对象或实现接口
*/
public class Demo07 {
public static void main(String[] args) {
Runnable r=
new Runnable(){
public void run(){
for (int i = 0; i <99 ; i++) {
System.out.println("kfc"+i);
}
}
};
// 少用一个变量承接
new Thread(new Runnable(){
public void run(){
for (int i = 0; i <99 ; i++) {
System.out.println("wwwwwww"+i);
}
}
}).start();
// main线程
for (int i = 0; i <99 ; i++) {
System.out.println("main线程"+i);
}
}
}
第四章 线程安全
1.线程安全概述
比如 现在有多个线程同时运行,所有线程都需要操作同样的变量。如果程序每次运行结果和单线程运行结果是一样的,而且变量的值也和预期一样,就是线程安全的。
代码模拟四个窗口同时售卖100张票:
public class MyRunnalbe01 implements Runnable {
private int ticket = 100;
@Override
public void run() {
// 窗口永远开启
while (true) {
if (ticket > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "正在卖:" + ticket--);
}
}
}
}
public class Demo08 {
public static void main(String[] args) {
// 创建线程任务对象
MyRunnalbe01 myRunnalbe01 =new MyRunnalbe01();
// 创建4个窗口对象
Thread t1 =new Thread(myRunnalbe01,"窗口01");
Thread t2 =new Thread(myRunnalbe01,"窗口02");
Thread t3 =new Thread(myRunnalbe01,"窗口03");
Thread t4 =new Thread(myRunnalbe01,"窗口04");
// 同时卖票
t1.start();
t2.start();
t3.start();
t4.start();
}
}
会发现结果打印出现多个窗口卖了同一张票的情况:
窗口02正在卖:10
窗口04正在卖:10
窗口04正在卖:7
窗口01正在卖:7
窗口02正在卖:7
窗口03正在卖:7
发现程序出了问题,这种问题称为线程不安全。
线程安全问题 都是由全局变量或者静态变量引起的。若每个线程对变量只是读操作,没有写操作,一般来说都是安全的。如果多个线程同时执行写操作,就要考虑线程安全。
2.线程同步
如果要解决多线程并发访问一个资源的安全性问题,也就是票存在还是不存在的问题 ,java中提供了同步机制(synchronized)来解决。
根据案例简述:
例如窗口1线程进入操作的时候,窗口2和窗口3只能在外等待,窗口1结束,窗口1,2,3才有机会去执行,也就是说某个线程修改共享资源时,其他线程不能修改这个资源。等修改完毕之后,才能去抢夺资源。
上述问题有三种解决方法:
1.同步代码块。
2.同步方法。
3.锁机制。
3.同步代码块
- 同步代码块:sychronized 关键字 可以用于方法中的某个区块中,表示只对这个区块的资源实行互斥访问。
- 格式:
synchronized(同步锁){
需要同步操作的代码
}
同步锁:对象的同步锁是一个概念,可以想象为在对象上标记了一个锁。
1.锁对象 可以是任意类型。
2.多个线程对象,要使用同一把锁。
任何时候,最多只需一个线程拥有同步锁,谁拿到锁谁就能进入到代码块,其他的线程只能在外等待(Blocked)
使用同步代码块解决代码:
public class MyRunnalbe01 implements Runnable {
private int ticket = 100;
Object lock =new Object();
@Override
public void run() {
// 窗口永远开启
while (true) {
synchronized (lock){
if (ticket > 0) {
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "正在卖:" + ticket--);
}
}
}
}
}
4.同步方法:
- 同步方法:使用synchronized修饰的方法叫做同步方法,保证一个线程执行该方法的时候 其他线程只能在方法外等待,
public class MyRunnalbe02 implements Runnable {
private int ticket = 100;
@Override
public void run() {
while (true) {
sellTicket();
}
}
private synchronized void sellTicket() {
if (ticket > 0) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "正在卖:" + ticket--);
}
}
}
5.Lock锁
Lock机制 提供了一个比synchronized代码块和synchronized方法更广泛的锁定操作,同步代码块、同步方法具有的功能Lock都有,除此之外更加强大,更体现了面向对象。
Lock锁也叫做同步锁,加锁和释放锁方法化了,
- public void lock(): 加同步锁
- public void unlock();释放同步锁
public class MyRunnalbe03 implements Runnable {
private int ticket = 100;
Lock lock=new ReentrantLock();
@Override
public void run() {
// 窗口永远开启
while (true) {
lock.lock();
if (ticket > 0) {
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "正在卖:" + ticket--);
}
lock.unlock();
}
}
}
第五章 线程状态
1.概述
在线程的生命周期中一共有6种状态:
* new(新建)刚被创建还没启动
* Runnable(可运行状态)线程在java虚拟机中的运行状态,可能在运行自己的代码,也可能没有,取决于操作系统。
* Blocked (锁阻塞)当一个线程试图获取一个对象锁,但是对象锁被其他线程持有,则该线程进入所阻塞状态。持有锁的线程是Runnable状态。
*Waiting(无限等待)一个线程在等待另一个线程执行一个唤醒动作,该线程进入 Waiting状态。这个状态不能自动唤醒,必须等待另一个线程调用notify或者notifyAll方法才能够唤醒
* TimedWaiting(记时等待) 同waiting状态,这个状态一直保存到唤醒动作或者超时。 *Teminated(被终止)因为run方法正常退出而死亡,或者因为没有捕获到异常终止了run方法而死亡。
2.睡眠sleep方法
public static void sleep(long time) 让当前线程进入到睡眠状态,到毫秒值后自动醒来继续执行
3.等待和唤醒
Object类的方法
public void wait():让当前线程进入到等待状态,这个方法必须锁对象调用。
public class Demo09 {
public static void main(String[] args) throws InterruptedException {
// 步骤1:子线程开启,进入无限等待状态,如果没有被唤醒 将无法继续运行。
Runnable r = new Runnable() {
@Override
public void run() {
try {
System.out.println("beagin wait ....");
synchronized ("") {
"".wait();
}
System.out.println("over");
} catch (Exception e) {
}
}
};
new Thread(r).start();
// 步骤2:加入如下代码,3秒之后会执行notify方法,唤醒wait中的线程
Thread.sleep(3000);
new Thread(new Runnable() {
@Override
public void run() {
synchronized ("") {
System.out.println("唤醒");
"".notify();
}
}
}).start();
}
}
4.多线程小练习
题目要求:利用多线程求解某范围素数,每个线程负责 1000范围:线程1找1-1000;线 程 2 找 1001-2000;线程 3 找 2001-3000。编程程序将每个线程找到的素数及时打印。所有的素数输出完毕后,再输出素数的总数;
分析:由题可知需要一个类实现Runnable接口,在重写run方法中表明查找素数需求且传入参数为线程负责范围,因为三线程负责范围没有交集,所以不需要锁对象。
public class FindPrimesTask implements Runnable{
private int start;
private int end;
// 创建一个集合用于存储各线程找到的素数
List<Integer> primeNumbers = new ArrayList<>();
// 定义一个任务,用于查找指定范围内的素数并将其添加到集合中
public FindPrimesTask(int start, int end) {
this.start = start;
this.end = end;
}
@Override
public void run() {
for (int num = start; num <= end; num++) {
if (isPrime(num)) {
System.out.println(Thread.currentThread().getName()+"找到素数"+num);
primeNumbers.add(num);
}
}
System.out.println(Thread.currentThread().getName()+"素数总数:" + primeNumbers.size());
}
// 判断一个数是否为素数
public static boolean isPrime(int num) {
if (num < 2) {
return false;
}
for (int i = 2; i <= Math.sqrt(num); i++) {
if (num % i == 0) {
return false;
}
}
return true;
}
}
public class Demo10 {
public static void main(String[] args) {
// 创建三个线程,每个线程负责1000范围内的素数查找
FindPrimesTask task1 = new FindPrimesTask(1, 1000);
FindPrimesTask task2 = new FindPrimesTask(1001, 2000);
FindPrimesTask task3 = new FindPrimesTask(2001, 3000);
// 启动线程
Thread thread1 = new Thread(task1,"线程1");
Thread thread2 = new Thread(task2,"线程2");
Thread thread3 = new Thread(task3,"线程3");
// 等待所有线程完成任务
thread1.start();
thread2.start();
thread3.start();
}
}