Java第十八天(二)
字符编码
读写过程中的字符编码格式要保持一致!
存储:
在计算机中存储自读都是存储字符所对应的数值,并且以二进制的形式表示。
显示:
在显示前会去相关的编码表中去查找目标数值所对应的字符。
常见编码:
| 名称 | 特点 |
|---|---|
| ASCII码表 | 用7bit来表示存储数据 |
| ISO-8859-1 | 用8bit表示 |
| GB2312 | 简体中文编码(国标码) |
| GBK | GB2312增强版 |
| GB18030 | GBK增强版 |
| BIG5 | 支持繁体 |
| Unicode | 支持多种国家的语言,为国际标准。使用2个字节存储。 (不管存储什么字符都会占用2个字节,这样就会产生资源浪费) |
| UTF-8 | 支持多种国家的语言,针对不同字符的范围给出不同的字节表示 0,a,A使用一个字节存储 中间范围(如韩文、日文)使用两个字节存储 中文使用3个字节存储 |
更改指定文件的字符编码格式
在指定文件上右键

使用指定字符编码写入文件
public class CharsetDemo{
public static void main(String[] args) {
// 创建字节输出流的对象
OutputStreamWriter out = null;
try {
// 创建目标文件,使用指定字符编码格式
out = new OutputStreamWriter(new FileOutputStream("Test.txt"),"GBK");
// 定义要写入的字符串
String str = "这是一段中文";
out.write(str);
} catch (FileNotFoundException e) {
e.printStackTrace();
}catch (IOException e) {
e.printStackTrace();
}finally {
if(out != null) {
try {
out.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
对字符串进行转码
public class CharsetDemo2 {
public static void main(String[] args) throws UnsupportedEncodingException {
String str = "这是一段中文";
// 这里使用默认字符集编码格式(GBK)
byte[] beGbk = str.getBytes();
// 这里使用UTF-8格式的字符集
// 这里将会产生一个不支持字符集的异常,我们将其向上抛出
byte[] beUtf = str.getBytes("UTF-8");
// 查看目标字符在不同字符集中对应的编码值
printBytes(beGbk);
// 打印编码值在此字符集对应的字符
// 这里使用的是默认字符集编码格式,所以会正常显示
System.out.println(new String(beGbk));
// 查看目标字符在不同字符集中对应的编码值
printBytes(beUtf);
// 打印编码值在此字符集对应的字符
// 这里使用的是默认字符集编码格式,但与原字符集不同,所以不能正常显示
System.out.println(new String(beUtf));
// 打印编码值在此字符集对应的字符
// 这里使用的是UTF-8字符集编码格式,与原字符集不同,所以能正常显示
System.out.println(new String(beUtf, "UTF-8"));
}
public static void printBytes(byte[] be) {
for(byte b:be) {
System.out.print(b+" ");
}
}
}
线程
进程:进程是程序的一次动态执行过程,他对应了从代码加载、执行至执行完毕的一个完整过程,这个过程也是进程本从产生、发展至消亡的过程。
线程:线程不是进程,但其行为很像进程,线程是比进程更小的执行单位。一个进程在其执行过程中,可以产生多个线程。
多线程:多线程是指一个应用程序中同时存在多个执行体。(例如:迅雷在下载时能同时存在多个下载任务)
多线程的执行原理:虽然执行多线程给人一种几个事件同时发生的感觉,但这只是一种错觉,因为我们的计算机在任何给定的时刻只能执行那些线程中的一个。为了建立这些线程正在同步执行的感觉,Java虚拟机快速地吧控制从一个线程切换到另一个线程,这些线程将被轮流执行,使得每个线程都有机会使用CPU资源。
主线程:每个Java应用程序都有一个缺省的主线程。Java应用程序总是从主类的main方法开始执行。当JVM加载代码,发现main方法之后,就会启动一个线程,这个线程称为“主线程”(main线程),该线程负责执行main方法。

常用构造器
Thread()
分配一个新的 Thread对象。
---------------------------------------------------------------------------------
Thread(Runnable target)
分配一个新的 Thread对象。
---------------------------------------------------------------------------------
Thread(Runnable target, String name)
分配一个新的 Thread对象。
---------------------------------------------------------------------------------
Thread(String name)
分配一个新的 Thread对象。
---------------------------------------------------------------------------------
常用方法
String getName()
返回此线程的名称。
---------------------------------------------------------------------------------
void start()
使此线程开始执行; Java虚拟机调用此线程的run方法。
---------------------------------------------------------------------------------
void setName(String name)
将此线程的名称更改为等于参数 name 。
---------------------------------------------------------------------------------
static Thread currentThread()
返回对当前正在执行的线程对象的引用。
---------------------------------------------------------------------------------
static void sleep(long millis)
使当前正在执行的线程以指定的毫秒数暂停(暂时停止执行),具体取决于系统定时器和调度程序的
精度和准确性。
(当线程执行了该方法就会立刻让出CPU的使用权,使当前线程处于中断状态。经过指定的时间后,该
线程就重新进到线程队列中等待CPU资源,以便从中断处继续运行。)
创建线程(Thread)
第一种方式
将一个类声明为Thread的子类。 这个子类应该重写Thread 类的run方法。 然后可以分配并启动子类的实例。
线程类
public class CountThread extends Thread{
@Override
public void run() {
for(int i = 0;i<50;i++) {
System.out.println(this.getName()+" test"+i);
}
}
}
执行线程类
public class ThreadTest {
public static void main(String[] args) {
// 创建线程对象
CountThread ct = new CountThread();
CountThread ct2 = new CountThread();
// 启动线程
ct.start();
ct2.start();
// 虽然使用该方法也能执行成功,但不是以多线程的方式
// ct.run();
}
}
如果控制台显示的结果在Thread-0和Thread-1之间穿插执行,表示线程启动成功。
如果使用run方法也可以执行成功,但不是以多线程的方式来执行。
第二种方式
创建一个线程的另外一个方法是声明实现Runnable接口的类,并且该类实现run方法。 然后可以分配类的实例,在创建Thread时作为一个参数传递并启动。
线程类
public class CountThread2 implements Runnable{
@Override
public void run() {
for(int i = 0;i<50;i++) {
// 使用Thread静态方法,返回此线程对象的引用,再获得线程名
System.out.println(Thread.currentThread().getName()+" test"+i);
}
}
}
执行线程类
public class ThreadTest2 {
public static void main(String[] args) {
/**
* 若以实现Runable接口的方式创建线程,则要使用以下构造器
* Thread(Runnable target)
* 分配一个新的 Thread对象。
*
* Thread(Runnable target, String name)
* 分配一个新的 Thread对象。
*/
// 第一种创建线程对象方法
CountThread2 ct = new CountThread2();
Thread th = new Thread(ct);
// 第二种创建线程对象方法,并使用第二种构造器自定义线程名
Thread th2 = new Thread(new CountThread2(),"线程2");
// 自定义线程名
th.setName("线程1");
// 启动线程
th.start();
th2.start();
}
}
自定义线程名
线程类
public class CountThread extends Thread{
private String threadName;
// 含参构造器
public CountThread(String threadName) {
super(threadName);
}
// 无参构造器
public CountThread() {
}
@Override
public void run() {
for(int i = 0;i<50;i++) {
System.out.println(this.getName()+" test"+i);
}
}
}
测试类
public class ThreadTest {
public static void main(String[] args) {
/**
* 使用含参构造器创建线程对象并自定义线程名
*/
CountThread ct3 = new CountThread("线程3");
CountThread ct4 = new CountThread("线程4");
ct3.start();
ct4.start();
/**
* 使用无参构造器创建线程对象并自定义线程名
*/
// 创建线程对象
CountThread ct = new CountThread();
CountThread ct2 = new CountThread();
// 自定义线程名
ct.setName("线程1");
ct2.setName("线程2");
// 启动线程
ct.start();
ct2.start();
}
}
线程的状态与生命周期

①新建
当一个Thread类或其子类的对象被声明并创建时,新生的线程对象处于新建状态。此时它已经有了相应的内存空间和其他资源。
②准备就绪
线程创建后仅仅是占有了内存资源,在JVM管理的线程中还没有这个线程,此线程必须调用start()方法(从父类继承的方法)通知JVM,这样JVM就会知道又有一个新线程排队等候切换了。
③运行
线程创建之后就具备了运行的条件,一旦轮到它来享用CPU资源时,即JVM将CPU使用权切换给该线程时,此线程就可以脱离创建它的主线程独立开始自己的声明周期了。
注意:当JVM将CPU使用权切换给线程时,如果线程是通过继承Thread类创建的,该类中被重写的run()方法就立刻执行,在线程没有结束run()方法之前,不要让线程再调用start()方法,否则将发生IllegalThreadStateException(不合法的线程状态异常)异常。
④中断(阻塞)
有四种原因的中断:
(1)JVM将CPU资源从当前线程切换给其他线程,使本线程让出CPU的使用权处于中断状态。
(2)线程使用CPU资源期间,执行了sleep(int millsecond)方法,使当前线程进入休眠状态。(sleep()方法详见上方常用方法)
(3)线程使用CPU资源期间,执行了wait()方法,使得当前线程进入等待状态。等待状态的线程不会主动进入到线程队列中排队等待CPU资源,必须由其他线程调用notify()方法通知它,使得让它重新进入到线程队列中排队等待CPU资源,以便从中断处继续运行。
(4)线程使用CPU资源期间,在执行某个操作时进入到了阻塞状态,例如执行读/写操作时引起阻塞。进入阻塞状态时线程不能进入排队队列,只有当引起阻塞的原因消除时,线程才能重新进入到线程队列中排队等待CPU资源,以便从原来中断处开始继续运行。
⑤死亡
所谓死亡状态就是线程释放了实体,即释放分配给线程对象的内存。处于死亡状态的线程不具有继续运行的能力。线程死亡的原因有二,其一是正常运行的线程完成了它的全部工作,即执行完run()方法中的全部代码,结束了run()方法;其二是线程被强制性地终止,即调用stop()方法强制run()方法结束。
并发(线程同步)
互联网的项目中存在着大量的并发案例,如卖火车票、电商网站等。
Java程序中可以存在多个线程,但是在处理多线程问题时,必须注意这样一个问题:当两个或多个线程同时访问一个变量(共享数据),并且一些线程需要修改这个变量。程序必须应对这样的问题作出处理,否则将发生混乱。
线程同步:当若干个线程功能都需要使用一个synchronized(同步)修饰的方法,即程序中的若干个线程都需要使用一个方法,而这个方法用synchronized给予了修饰。多个线程调用synchronized方法必须遵守同步机制。
线程同步机制:当一个线程A使用synchronized方法时,其他线程想使用这个synchronized方法时就必须等待,直到线程A使用完该synchronized方法。
synchronized方法适用于以下场景:
①代码被多个线程访问
②代码中有共享变量
③共享变量被多条语句操作
范例:火车站有100张票,并通过4个窗口同时售票
分析:4 个窗口代表4个线程,100张票是4 个线程的共享资源。
注意:如果只是单纯创建4个线程运行,则会发生并发问题(不同的线程执行的结果一致)。
采用继承Thread类实现
售票线程类
public class SealTicketsThread extends Thread{
// 由于火车票对于售票窗口来说属于静态成员变量(类的属性),而不是成员变量(对象的属性)
private static int ticketsCount = 200;
// 由于同步锁对于线程类来说是独有的,即只能存在一个同步锁对象,所以也是静态成员变量
// 创建同步锁锁的对象
private static Object obj = new Object();
// 使用含参构造器自定义线程名
public SealTicketsThread(String ticketsCount) {
super(ticketsCount);
}
/**
* 重写run方法进行售票
*/
@Override
public void run() {
while(true) {
// 这里使用同步锁将并发代码锁起来
synchronized (obj) {
if(ticketsCount > 0) {
System.out.println(this.getName()+"正在出售第"+ ticketsCount-- +"张票");
}else {
System.out.println("票已售完!");
break;
}
}
}
}
}
测试类
public class SealTicketsTest {
public static void main(String[] args) {
/**
* 使用含参构造器创建线程对象并自定义线程名
*/
SealTicketsThread sth1 = new SealTicketsThread("窗口1");
SealTicketsThread sth2 = new SealTicketsThread("窗口2");
SealTicketsThread sth3 = new SealTicketsThread("窗口3");
SealTicketsThread sth4 = new SealTicketsThread("窗口4");
sth1.start();
sth2.start();
sth3.start();
sth4.start();
}
}
synchronized(同步)锁对象详解
对于线程的实现方式是使用继承于Thread类来说,synchronized同步代码块的锁对象可以是任意类对象,并且这个对象必须是线程类共享的(静态的)。
例:
public class SealTicketsThread extends Thread{
// 由于火车票对于售票窗口来说属于静态成员变量(类的属性),而不是成员变量(对象的属性)
private static int ticketsCount = 200;
// 由于同步锁对于线程类来说是独有的,即只能存在一个同步锁对象,所以也是静态成员变量
// 创建同步锁锁的对象
private static Test test = new Test();
// 使用含参构造器自定义线程名
public SealTicketsThread(String ticketsCount) {
super(ticketsCount);
}
/**
* 重写run方法进行售票
*/
@Override
public void run() {
while(true) {
// 这里使用同步锁将并发代码锁起来
synchronized (test) {
if(ticketsCount > 0) {
System.out.println(this.getName()+"正在出售第"+ ticketsCount-- +"张票");
}else {
System.out.println("票已售完!");
break;
}
}
}
}
}
// 同步锁测试类
class Test{
}
当不同的代码块使用同一把同步锁时,不会发生并发异常。
/**
* 当同步锁为任意类对象时,让不同的代码块使用同一把同步锁
*/
public class SealTicketsThreadSameLock extends Thread{
// 由于火车票对于售票窗口来说属于静态成员变量(类的属性),而不是成员变量(对象的属性)
private static int ticketsCount = 200;
// 由于同步锁对于线程类来说是独有的,即只能存在一个同步锁对象,所以也是静态成员变量
// 创建同步锁锁的对象
private static Test2 test2 = new Test2();
// 使用含参构造器自定义线程名
public SealTicketsThreadSameLock(String ticketsCount) {
super(ticketsCount);
}
/**
* 重写run方法进行售票
*/
@Override
public void run() {
while(true) {
int i = 0;
// 考虑奇偶情况来分别执行不同的代码块
if(i%2 == 0) {
// 这里使用同步锁将并发代码锁起来
synchronized (test2) {
if(ticketsCount > 0) {
System.out.println(this.getName()+"正在出售第"+ ticketsCount-- +"张票");
}else {
System.out.println("票已售完!");
break;
}
}
}else {
// 这里使用相同的同步锁将并发代码锁起来
synchronized (test2) {
if(ticketsCount > 0) {
System.out.println(this.getName()+"正在出售第"+ ticketsCount-- +"张票");
}else {
System.out.println("票已售完!");
break;
}
}
}
i++;
}
}
}
//同步锁测试类
class Test2{
}
针对上面的代码我们可以将第二个代码块使用方法封装起来。
package com.threadExample;
/**
* 当同步锁为任意类对象时,让不同的代码块使用同一把同步锁
*/
public class SealTicketsThreadSameLock extends Thread{
// 由于火车票对于售票窗口来说属于静态成员变量(类的属性),而不是成员变量(对象的属性)
private static int ticketsCount = 200;
// 由于同步锁对于线程类来说是独有的,即只能存在一个同步锁对象,所以也是静态成员变量
// 创建同步锁锁的对象
private static Test2 test2 = new Test2();
// 使用含参构造器自定义线程名
public SealTicketsThreadSameLock(String ticketsCount) {
super(ticketsCount);
}
/**
* 重写run方法进行售票
*/
@Override
public void run() {
while(true) {
int i = 0;
// 考虑奇偶情况来分别执行不同的代码块
if(i%2 == 0) {
// 这里使用同步锁将并发代码锁起来
synchronized (test2) {
if(ticketsCount > 0) {
System.out.println(this.getName()+"正在出售第"+ ticketsCount-- +"张票");
}else {
System.out.println("票已售完!");
break;
}
}
}else {
// 调用使用方法封装的代码块
saleTickets();
}
i++;
}
}
// 声明一个方法,将第二个代码块封装起来
public static void saleTickets() {
// 这里使用相同的同步锁将并发代码锁起来
synchronized (test2) {
if(ticketsCount > 0) {
// 由于是我们自己声明的方法,所以不能使用this关键字
System.out.println(Thread.currentThread().getName()+"正在出售第"+ ticketsCount-- +"张票");
}else {
System.out.println("票已售完!");
}
}
}
}
//同步锁测试类
class Test2{
}
当synchronized修饰静态方法时,虽然我们不能指定锁对象,但是其同步锁对象为类的类对象。
public class SealTicketsThreadStaticMethod extends Thread{
// 由于火车票对于售票窗口来说属于静态成员变量(类的属性),而不是成员变量(对象的属性)
private static int ticketsCount = 200;
// 使用含参构造器自定义线程名
public SealTicketsThreadStaticMethod(String ticketsCount) {
super(ticketsCount);
}
/**
* 重写run方法进行售票
*/
@Override
public void run() {
while(true) {
int i = 0;
if(i%2 == 0) {
// 这里使用同步锁将并发代码锁起来
// 如果想与saleTickets静态方法使用同一个同步锁,这里的锁对象为当前类的类对象
synchronized (SealTicketsTest.class) {
if(ticketsCount > 0) {
System.out.println(this.getName()+"正在出售第"+ ticketsCount-- +"张票");
}else {
System.out.println("票已售完!");
break;
}
}
}else {
// 调用带有同步锁的静态方法
saleTickets();
}
i++;
}
}
// 创建一个带有同步锁的静态方法,其锁对象为当前类的类对象
public synchronized static void saleTickets() {
if(ticketsCount > 0) {
System.out.println(Thread.currentThread().getName()+"正在出售第"+ ticketsCount-- +"张票");
}else {
System.out.println("票已售完!");
}
}
}
本文深入探讨Java中的字符编码,包括如何更改文件编码、写入指定编码文件和字符串转码。同时,详细介绍了线程的创建、状态与生命周期,以及如何实现并发控制,特别是synchronized锁的使用,强调了同步锁在多线程并发场景中的重要性。
808

被折叠的 条评论
为什么被折叠?



