Java学习笔记-基础语法Ⅹ-进程线程
学习快一个月了,现在学到了黑马Java教程的300集
打印流的特点:
- 只负责输出数据,不负责读取数据
- 有自己的特有方法
字节打印流:PrintStream,使用指定的文件名创建新的打印流
import java.io.FileNotFoundException;
import java.io.PrintStream;
public class Demo {
public static void main(String[] args) throws FileNotFoundException {
PrintStream ps = new PrintStream("test.txt");
ps.write(97);
ps.println(97);
ps.close();
}
}
字符打印流:PrintWriter
import java.io.FileNotFoundException;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
public class Test {
public static void main(String[] args) throws IOException {
// 错误示例
// PrintWriter pw = new PrintWriter("demo.txt",true);
PrintWriter pw = new PrintWriter(new FileWriter("demo.txt"),true);
pw.println(97);
pw.close();
}
}
对象序列化流
对象序列化:将对象保存到磁盘中,或者在网络中传输对象,即使用一个字节序列表示一个对象,该字节序列包含:对象的类型、对象的数据等,字节序列写到文件之后,相当于文件中持久保存了一个对象的信息
字节序列还可以从文件中读取回来,重构对象,把他进行反序列化
对象序列化流:ObjectOutputStream
- 将Java对象的原始数据类型和图形写入OutputStream,可以使用ObjectInputStream读取(重构)对象,通过使用流的文件来实现对象的持久存储。如果流是网络套接字流,则可以在另一个主机或进程中重构对象
构造方法:ObjectOutputStream(OutputSteram out):创建一个写入指定的OutputStream的ObjectOutputStream
注意:
- 一个对象想要被序列化,该对象所属的类必须实现Serializable接口
- Serializable是一个标记接口,实现该接口,不需要重写任何方法
import java.io.*;
public class Demo {
public static void main(String[] args) throws IOException, ClassNotFoundException {
Student s1 = new Student("林青霞",19);
Student s2 = new Student("张曼玉",18);
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("My_File/src/demo19/demo.txt"));
oos.writeObject(s1);
oos.writeObject(s2);
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("My_File/src/demo19/demo.txt"));
Object o = ois.readObject();
System.out.println(o);
// 可以用循环改进
o = ois.readObject();
System.out.println(o);
}
}
对象序列化流常见问题
-
用对象序列化流序列化一个对象后,如果修改了对象所属的类文件,读取数据会出现问题,抛出IncalidClassException
-
解决办法:给对象所属的类加一个serialVersionUID,
private static final long serialVersionUID = 42L
-
如果一个对象中的某个成员变量的值不想被序列化,可以给该成员变量加transient关键字修饰,该关键字标记的成员变量不参与序列化过程
玩游戏次数
// 玩游戏的类,静态方法可直接调用
import java.util.Random;
import java.util.Scanner;
public class PlayGames {
public static void play(){
System.out.println("猜数,请输入一个小于100的数:");
Random r = new Random();
int i = r.nextInt(100);
Scanner sc = new Scanner(System.in);
int number = sc.nextInt();
while(number!=i){
if(number>i){
System.out.println("大了,请再猜:");
}else if(number<i){
System.out.println("小了。请再猜");
}
number = sc.nextInt();
}
System.out.println("恭喜你猜对了,数字为"+i);
}
}
// main函数
import java.io.*;
import java.util.Properties;
public class Properties_demo {
public static void main(String[] args) throws IOException {
Properties pt = new Properties();
File file = new File("games.txt");
FileReader fr = new FileReader(file);
pt.load(fr);
int count = Integer.parseInt(pt.getProperty("count"));
if(count<3){
PlayGames.play();
count++;
pt.setProperty("count",String.valueOf(count));
FileWriter fw = new FileWriter("games.txt");
pt.store(fw,"hello");
}else{
System.out.println("你的游戏次数已用完,请充值!");
}
}
}
// 初始状态
// games.txt
count=0
进程&线程
进程:正在运行的程序,是系统进行资源分配和调用的独立单位,并且每一个进程都有它自己的内存空间和系统资源
线程:是进程中的单个顺序控制流,是一条执行路径
某类要实现多线程的话,需要继承Thread
,然后在该类中重写run
方法,重写run
方法的目的是用来封装被线程执行的代码,但是直接调用的话,相当于普通方法的调用,如果想启动线程,则需要使用start
方法启动,然后由JVM调用此线程的run
方法
public class MyThread extends Thread{
@Override
public void run() {
for(int i = 0;i<1000;i++){
System.out.println(getName()+","+i);
}
}
}
public class My_thread {
public static void main(String[] args) {
MyThread mt1 = new MyThread();
MyThread mt2 = new MyThread();
mt1.start();
mt2.start();
mt1.setName("1111");
mt2.setName("2222");
}
}
获取线程名称用getName,设置线程名称用setName,也可以一开始就用带参构造方法设置线程名称,同时还可以调用Thread类中的静态方法currentThread来获取当前正在执行的线程
线程调度:
- 分时调度:轮流使用
- 抢占式调度:有限让优先级高的线程使用CPU
Java使用抢占式调度模型
Thread类中设置和获取线程优先级:
- getPriority
- setPriority
线程优先级高仅仅表示线程获取的CPU时间片的几率高
线程控制主要有3个方法:
- sleep:设置睡眠时间
- join:等待线程死亡
- setDaemon:守护线程
join的用法:
public class MyThread extends Thread{
@Override
public void run() {
for(int i = 0;i<1000;i++){
System.out.println(getName()+","+i);
}
}
}
public class My_thread2 {
public static void main(String[] args) throws InterruptedException {
MyThread mt1 = new MyThread();
MyThread mt2 = new MyThread();
MyThread mt3 = new MyThread();
// 注意join在start之后才生效
// mt1.join();
mt1.start();
mt1.join();
mt2.start();
mt3.start();
}
}
在使用setDaemon的时候,自己尝试了与黑马视频很多不同的代码,发现有意思的地方
public class MyThread extends Thread{
@Override
public void run() {
for(int i = 0;i<100;i++){
// System.out.println(getName());
System.out.println(getName()+","+i);
}
}
}
public class My_thread3 {
public static void main(String[] args) {
MyThread mt1 = new MyThread();
MyThread mt2 = new MyThread();
mt1.setName("关羽");
mt2.setName("张飞");
mt1.setDaemon(true);
mt2.setDaemon(true);
Thread.currentThread().setName("刘备");
mt1.start();
mt2.start();
for(int i = 0;i<10;i++){
System.out.println(Thread.currentThread().getName());
// System.out.println(Thread.currentThread().getName()+","+i);
}
}
}
如果继承Thread的类简洁写法如从System.out.println(getName()+","+i);
换到System.out.println(getName());
并且main函数中也是简洁写法,那么可以达到守护的效果,但是如果继承Thread的类是不简洁写法System.out.println(getName()+","+i);
,而main函数中是简洁写法System.out.println(Thread.currentThread().getName());
,那么输出只会是main函数中的内容。
个人猜测是非简洁写法本身就抢不过简洁写法,那么只有让非守护线程的main函数执行,执行完毕后又只有守护线程,所以就结束了
多线程实现方式Ⅱ
除了继承Thread,还可以实现Runnable,相比于继承Thread,接口方法好处在于:
- 避免Java单继承的局限性
- 适合多个相同程序的代码去处理同一个资源的情况,把线程和程序的代码、数据有效分离
public class runnableDemo implements Runnable{
@Override
public void run() {
for(int i = 0;i<100;i++){
System.out.println(Thread.currentThread().getName()+","+i);
}
}
}
public class ThreadDemo {
public static void main(String[] args) {
runnableDemo rd1 = new runnableDemo();
Thread t1 = new Thread(rd1,"飞机");
Thread t2 = new Thread(rd1,"高铁");
t1.start();
t2.start();
}
}
卖票案例
描述:有100张票,3个窗口售卖,要做到同步
方法Ⅰ:
// Ticket类
public class Ticket implements Runnable {
private int ticket = 100;
private boolean flag = true;
@Override
public void run() {
while (true) {
if(flag) {
flag = false;
if (ticket > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "正在卖第" + ticket + "票");
ticket--;
}
flag = true;
}
}
}
}
// 测试类
public class SellDemo {
public static void main(String[] args) {
Ticket t = new Ticket();
Thread t1 = new Thread(t,"窗口1");
Thread t2 = new Thread(t,"窗口2");
Thread t3 = new Thread(t,"窗口3");
t1.start();
t2.start();
t3.start();
}
}
这样做不可以达到效果,因为原子锁比较复杂,不能直接用if语句实现,要用Java提供的API接口
Java提供了同步方法
public class TicketSynchronize implements Runnable{
private int tickets = 100;
Object obj = new Object();
@Override
public void run() {
while (true) {
synchronized (obj){
if(tickets>0){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"正在售出第"+tickets+"票");
tickets--;
}
}
}
}
}
public class Demo {
public static void main(String[] args) {
TicketSynchronize ts = new TicketSynchronize();
Thread t1 = new Thread(ts,"窗口1");
Thread t2 = new Thread(ts,"窗口2");
Thread t3 = new Thread(ts,"窗口3");
t1.start();
t2.start();
t3.start();
}
}
上面内容中,如果一直是一个线程,那么把票数从100加到1000,可能是因为synchronized是一个重型锁,锁的时间比较长
同步的好处&弊端:
- 好处:解决多线程的数据安全问题
- 弊端:当线程很多时,每个线程都会去判断同步上的锁,耗费资源
同步方法
同步方法时,锁的对象要从原来的Object换成this
同步静态方法时,锁对象是类名.class
线程安全的类:
- StringBuffer:线程安全,可变的字符序列,功能同StringBuilder
- Vector:线程安全,功能同ArrayList
- Hashtable:线程安全,功能同HashMap
Lock
比synchronized方法提供更广泛的锁定操作,允许更灵活的操作
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class SellTicket implements Runnable{
private int tickets = 100;
private Lock lock = new ReentrantLock();
@Override
public void run() {
while(true){
lock.lock();
try{
if(tickets>0){
System.out.println(Thread.currentThread().getName()+"正在卖出第"+tickets+"张票");
tickets--;
}
}
finally {
lock.unlock();
}
}
}
}
public class Demo {
public static void main(String[] args) {
SellTicket st = new SellTicket();
Thread t1 = new Thread(st,"窗口1");
Thread t2 = new Thread(st,"窗口2");
Thread t3 = new Thread(st,"窗口3");
t1.start();
t2.start();
t3.start();
}
}
生产者消费者案例
public class Box {
private int milk;
private boolean state = false;
public synchronized void put(int milk){
if(state){
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
this.milk = milk;
System.out.println("送奶工将第"+this.milk+"瓶奶送入奶箱");
state = true;
notifyAll();
}
public synchronized void get(){
if(!state){
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("用户拿到第"+this.milk+"瓶奶");
state = false;
notifyAll();
}
}
public class Producer implements Runnable{
private Box b;
public Producer(Box b) {
this.b = b;
}
@Override
public void run() {
for(int i = 0;i<5;i++){
b.put(i);
}
}
}
public class Consumer implements Runnable{
private Box b;
public Consumer(Box b) {
this.b = b;
}
@Override
public void run() {
while(true){
b.get();
}
}
}
public class BoxDemo {
public static void main(String[] args) {
Box b = new Box();
Producer p = new Producer(b);
Consumer c = new Consumer(b);
Thread t1 = new Thread(p);
Thread t2 = new Thread(c);
t1.start();
t2.start();
}
}