第一章:线程
1、线程和进程的基本概念
(1)线程基本概念:一个线程是一个程序内部的顺序控制流。
(2)进程的基本概念:每个进程都有独立的代码和数据空间(进程上下文),进程切换的开销大。
(3)线程:轻量的进程,同一类线程共享代码和数据空间,每个线程有独立的运行栈和程序计数器(PC),开销小。
(4)多进程:在操作系统中,能同时运行多个任务(程序)。
(5)多线程:在同一应用程序中,有多个顺序流同时执行。
(6)线程的概念模型:虚拟的CPU,封装在java.lang.Thread类中。
- CPU所执行的代码,传递给Thread类。
- CPU所处理的数据,传递给Thread类。
(7)每个线程都是通过某个特定Thread对象的方法run()来完成其操作的,方法run()称为线程体。
(8)构造线程的两种方法
-
定义一个线程类,它继承类Thread并重写其中的方法run( );
-
提供一个实现接口Runnable的类作为线程的目标对象,在初始化一个Thread类或者Thread子类的线程对象时,把目标对象传递给这个线程实例,由该目标对象提供线程体run( )。
public Thread( ThreadGroup group, Runnable target,String name);
2、通过Thread类创建线程
(1)Thread类
- Thread类直接继承了Object类,并实现了Runnable接口。位于java.lang包中封装了线程对象需要的属性和方法
(2)Thread类详解
| 名称 | 说明 |
|---|---|
| public Thread() | 构造一个新的线程对象,默认名为Thread-n,n是从0开始递增的整数 |
| public Thread(Runnable target) | 构造一个新的线程对象,以一个实现Runnable接口为参数 |
| public Thread(String name) | 构造一个新的线程对象,并同时指定线程名 |
| public static Thread currentThread() | 返回当前正在运行的线程对象 |
| public static void yield() | 使当前线程对象暂停,允许别的线程开始运行 |
| public static void sleep(long millis) | 使当前线程暂停运行指定毫秒数,但此线程并不失去已获得的锁。 |
| public void start() | 启动线程,JVM将调用此线程的run方法,结果是将同时运行两个线程,当前线程和执行run方法的线程 |
| public void run() | Thread的子类应该重写此方法,内容应为该线程应执行的任务。 |
| public final void stop() | 停止线程运行、释放该线程占用的对象锁 |
| public void interrupt() | 中断此线程 |
| public final void join() | 如果此前启动了线程A,调用join方法将等待线程A,死亡才能继续执行当前线程 |
| public final void join(long millis) | 如果此前启动了线程A,调用join方法将等待指定毫秒数或线程A死亡才能继续执行当前线程 |
| public final void setPriority(int newPriority) | 设置线程优先级 |
| public final void setDaemon(Booleanon) | 设置是否为后台线程,如果当前运行线程均为后台线程则JVM停止运行。这个方法必须在start()方法前使用 |
| public final void checkAccess() | 判断当前线程是否有权力修改调用此方法的线程 |
| public void setName(String name) | 更改本线程的名称为指定参数 |
| public final boolean isAlive() | 测试线程是否处于活动状态,如果线程被启动并且没,有死亡则返回true |
(3)继承Thread类
-
从Thread类派生一个子类,并创建子类的对象子类应该重写Thread类的run方法,写入需要在新线程中执行的语句段。
-
调用start方法来启动新线程,自动进入run方法。
#在新线程中完成计算某个整数的阶乘
class FactorialThread extends Thread{
private int num;
public FactorialThread( int num ){this.num=num; }
public void run()
{
int i=num;
int result=1;
System.out.println("new thread started" );
while(i>0)
{result=result*i;i=i-1; }
System.out.println("The factorial of "+num+" is "+result);
System.out.println("new thread ends");
}
}
public class FactorialThreadTester
{
public static void main( String []args )
{
System.out.println("main thread starts");
FactorialThread thread=new FactorialThread(10);
thread.start();1/将自动进入run()方法
System.out.println("main thread ends " );
}
}
3、通过 Runnable接口构造线程
(1)只有一个run()方法
(2)Thread类实现了Runnable接口口便于多个线程共享资源
(3)Java不支持多继承,如果已经继承了某个基类,便需要实现Runnable接口来生成多线程
(4)以实现Runnable的对象为参数建立新的线程
(5)start方法启动线程就会运行run()方法
class FactorialThread implements Runnable{
private int num;
public FactorialThread( int num ) {
this.num=num;
}
public void run(){
int i=num;
int result=1;
while(i>0){
result=result*i;i=i-1;
}
System.out.println("The factorial of "+num+" is "+result);
System.out.println("new thread ends");
}
}
public class FactorialThreadTester {
public static void main( String []args ){
System.out.println("main thread starts");
FactorialThread t=new FactorialThread(10);//实现了Runnable的类
new Thread(t).start();//运行FactorialThread的run
System.out.println("new thread started,main thread ends " );
}
}
#使用Runnable接口实现上面多线程的例子
class TestThread implements Runnable {
private int sleepTime;
public TestThread(){
sleepTime = ( int ) ( Math.random() * 6000 );
public void run(){
try {
System.out.println(
Thread.currentThread().getName() + " going to sleep for "+sleepTime );
Thread.sleep( sleepTime );
}
catch ( lnterruptedException exception ){};
System.out.println( Thread.currentThread().getName()+ "finished" );}
}
public class ThreadSleepTester {
public static void main( String [] args ){
TestThread thread1 = new TestThread();
TestThread thread2 = new TestThread();
TestThread thread3 = new TestThread();
System.out.println( "Starting threads" );
new Thread(thread1,"Thread1").start();
new Thread(thread2,"Thread2").start();
new Thread(thread3,"Thread3").start();
System.out.println( "Threads started, mainends\n" );
(6)两种线程构造方式的比较
- 使用Runnable接口:可以将CPU,代码和数据分开,形成清晰的模型;还可以从其他类继承
- 直接继承Thread类:编写简单,直接继承,重写run方法,不能再从其他类继承
4、线程的休眠
(1)如果启动新线程后希望主线程多持续一会再结束,可在start语句后加上让当前线程(这里当然是main)休眠1毫秒的语句:
public class FactorialThreadTester{
public static void main( String [] args){
System.out.println("main threadstarts");
FactorialThread thread=new FactorialThread(10);
thread.start();
try { Thread.sleep(1); }catch(Exception e){};
System.out.println("main threadends " );
}
}
(2)创建3个新线程,每个线程睡眠一段时间(0~6秒),然后结束
class TestThread extends Thread {
private int sleepTime;
public TestThread( String name ){
super( name );
sleepTime = ( int ) ( Math.random()* 6000 );}
public void run() {
try {
System.out.printIn(
getName() +" going to sleep for " + sleepTime );
Thread.sleep( sleepTime );//线程休眠
}
catch ( lnterruptedException exception) {};
System.out.println( getName() + " finished")}
}
public class ThreadSleepTester {
public static void main( String ] args){//创建并命名每个线程
TestThread thread1 = new TestThread( "thread1" );
TestThread thread2 = new TestThread( "thread2" );
TestThread thread3 = new TestThread( "thread3");
System.out.println( "Starting threads" );
thread1.start();//启动线程1
thread2.start();//启动线程2
thread3.start(); //启动线程3
System.out.println( "Threads started, main ends\n");
}}
5、线程内部的数据共享
(1)用同一个实现了Runnable接口的对象作为参数创建多个线程,多个线程共享同一对象中的相同的数据。
#只用一个Runnable类型的对象为参数创建3个新线程。
class TestThread implements Runnable {
private int sleepTime;
public TestThread(){
sleepTime = ( int ) ( Math.random()*6000}
public void run(){
try {
System.out.println(Thread.currentThread().getName() +"going to sleep for " +sleepTime );
Thread.sleep(sleepTime );
}
catch ( InterruptedException exception ){}
System.out.println( Thread.currentThread().getName() + "finished");
}
}
public class ShareTargetTester {
public static void main( String [] args ) {
TestThread threadobj = new TestThread();
System.out.println( "Starting threads" );
new Thread(threadobj,"Thread1").start();
new Thread(threadobj,"Thread2").start();
new Thread(threadobj,"Thread3").start();
System.out.println( "Threads started, main ends\n");
}
}
(2)用三个线程模拟三个售票口,总共出售200张票-用3个线程模仿3个售票口的售票行为-这3个线程应该共享200张票的数据
class SellTickets implements Runnable{
private int tickets=200;
public void run(){
while(tickets>O){
System.out.println( Thread.currentThread().getName()+" is selling ticket "+tickets--);
}}}
public class SellTicketsTester {
public static void main(String[] args){
SellTickets t=new SellTickets();
new Thread(t).start();
new Thread(t).start();
new Thread(t).start();
}
}
6、多线程的同步控制
有时线程之间彼此不独立、需要同步口
线程间的互斥
-
同时运行的几个线程需要共享一个(些)数据
-
共享的数据,在某一时刻只允许一个线程对其进行操作
“生产者/消费者”问题
- 假设有一个线程负责往数据区写数据,另一个线程从同一数据区中读数据,两个线程可以并行执行
- 如果数据区已满,生产者要等消费者取走一些数据后才能再写
- 当数据区空时,消费者要等生产者写入一些数据后再取
用两个线程模拟存票、售票过程
- 假定开始售票处并没有票,一个线程往里存票另外一个线程则往出卖票
- 我们新建一个票类对象,让存票和售票线程都访问它。本例采用两个线程共享同一个数据对象来实现对同一份数据的操作
public class ProducerAndConsumer {
public static void main(String[] args){
Tickets t=new Tickets(10);//建立票对象,票总数10
new Consumer(t).start();//开始卖票线程
new Producer(t).start();//开始存票线程
}
}
class Tickets {
int number=O;//票号
int size;//总票数
boolean available=false;//表示目前是否有票可售
public Tikets(int size){
this.size=size;
}
}
class Producer extends ThreadTickets{
t=null;
public Producer(Tickets t){this.t=t; }
public void run){
while( t.number < t.size){
System.out.printIn("Producer puts ticket"+(++t.number));
t.available=true;
}
}
}
class Consumer extends Thread//售票线程{
Tickets t=null;int i=O;
public Consumer(Tickets t){this.t=t; }
public void run(){
while(i<t.size)
if(t.available==true && i<=t.number)
System.out.println(consmrrbuys tReTT
if(i==t.number)//现有的票号卖完了
t.available=false;
}
}
}
线程同步
-
互斥:许多线程在同一个共享数据上操作而互不干扰,同一时刻只能有一个线程访问该共享数据。因此有些方法或程序段在同一时刻只能被一个线程执行,称之为监视区。
-
协作:多个线程可以有条件地同时操作共享数据。执行监视区代码的线程在条件满足的情况下可以允许其它线程进入监视区。
-
synchronized—一线程同步关键字,实现互斥。
-
用于指定需要同步的代码段或方法,也就是监视区。
-
可实现与一个锁的交互。例如: synchronized(对象){代码段}
-
synchronized的功能是:首先判断对象的锁是否在,如果在就获得锁,然后就可以执行紧随其后的代码段;如果对象的锁不在(已被其他线程拿走),就进入等待状态,直到获得锁。
-
当被synchronized限定的代码段执行完,就释放锁。
-
-
互斥
- 存票线程和售票线程应保持互斥关系。即售票线程执行时不进入存票线程、存票线程执行时不进入售票线程。
-
Java使用监视器机制
- 每个对象只有一个“锁”,利用多线程对“锁”的争夺实现线程间的互斥。
- 当线程A获得了一个对象的锁后,线程B必须等待线程A完成规定的操作、并释放出锁后,才能获得该对象的锁,并执行线程B中的操作。
-
将需要互斥的语句段放入synchronized(object){}语句中,且两处的object是相同的
class Producer extends Thread {
Tickets t=null;
public Producer(Tickets t) { this.t=t,}public void run(){
while((t.number)<t.size) {
synchronized(t){//申请对象t的锁
System.out.println("Producer puts ticket"+(++t.number));
t.available=true;
}//释放对象t的锁
}
System.out.println("Producer ends!");
}
}
class Consumer extends Thread {Tickets t=null;
int i=0;
public Consumer(Tickets t){ this.t=t; }public void run(){
while(i<t.size){
synchronized(t){//申请对象t的锁if(t.available==true && i<=t.number)
System.out.println("Consumer buys ticket "+(++i));if(i==t.number){
try{ Thread.sleep(1); }catch(Exception e)0}t.available=false;
}
//释放对象t的锁
System.out.println("Consumer ends");
}
}
-
说明
- 存票程序段和售票程序段为获得同一对象的锁而实现互斥操作
- 当线程执行到synchronized的时候,检查传入的实参对象,并申请得到该对象的锁。如果得不到,那么线程就被放到一个与该对象锁相对应的等待线程池中。直到该对象的锁被归还,池中的等待线程才能重新去获得锁,然后继续执行下去
- 除了可以对指定的代码段进行同步控制之外,还可以定义整个方法在同步控制下执行,只要在方法定义前加上synchronized关键字即可
-
改进上例功能。将互斥方法放在共享的资源类 Tickets中
class Tickets { int size;//票总数 int number=0;//存票序号int i=0;//售票序号 boolean available=false;//是否有待售的票 public Tickets(int size){ this.size=size; } public synchronized void put(){//同步方法,实现存票的功能 System.out.println("Producer puts ticket "+(++number)); available=true; public synchronized void sell(){//同步方法,实现售票的功能if(available==true && i<=number) System.out.println("Consumer buys ticket "+(++i));if(i==number)available=false; } } class Producer extends Thread{ Tickets t=null; public Producer(Tickets t){this.t=t; } public void run() {//如果存票数小于限定总量,则不断存入票while(t.number<t.size) t.put(); } } class Consumer extends Thread{ Tickets t=null; public Consumer(Tickets t){this.t=t; } 历 public void run() {//如果售票数小于限定总量,则个断售票while(t.i<t.size) t.sell(); } } -
同步与锁的要点:
- 只能同步方法,而不能同步变量;
- 每个对象只有一个锁;当提到同步时,应该清楚在什么上同步?也就是说,在哪个对象上同步? 类可以同时拥有同步和非同步方法,非同步方法可以被多个线程自由访问而不受锁的限制。
- 如果两个线程使用相同的实例来调用的synchronized方法,那么—次只能有一个线程执行方法,另一个需要等待锁。
- 线程睡眠时,它所持的任何锁都不会释放。
- 线程可以获得多个锁。比如,在一个对象的同步方法里面调用另外一个对象的同步方法,则获取了两个对象的同步锁。
- 同步损害并发性,应该尽可能缩小同步范围。同步不但可以同步整个方法,还可以同步方法中一部分代码块。
- 在使用同步代码块时候,应该指定在哪个对象上同步,也就是说要获取哪个对象的锁。
7、线程的等待与唤醒
线程的等待―—wait()方法
-
更有效地协调不同线程的工作,需要在线程间建立沟通渠道,通过线程间的“对话”来解决线程间的同步问题
-
java.lang.Object类的一些方法为线程间的通讯提供了有效手段一wait()如果当前状态不适合本线程执行,正在执行同步代码(synchronized)的某个线程A调用该方法(在对象x上),该线程暂停执行而进入对象x的等待池,并释放已获得的对象x的锁。线程A要一直等到其他线程在对象x上调用notify或notifyAll方法,才能够在重新获得对象x的锁后继续执行从wait语句后继续执行)
线程的唤醒――notify()和notifyAlI()方法
- notify()随机唤醒一个等待的线程,本线程继续执行。
- 线程被唤醒以后,还要等发出唤醒消息者释放监视器,这期间关键数据仍可能被改变。
- 被唤醒的线程开始执行时,一定要判断当前状态是否适合自己运行。
- notifyAll()唤醒所有等待的线程,本线程继续执行。
#修改上例,要求:使每存入一张票,就售一张票,售出后,再存入
class Tickets {
public synchronized void put(){
if(available)//如果还有存票待售,则存票线程等待
try{ wait();} catch(Exception e){}
System.out.println("Producer puts ticket "+(++number));available=true;
notify();//存票后唤醒售票线程开始售票}
public synchronized void sell(){
if(!available)//如果没有存票,则售票线程等待
try{ wait();} catch(Exception e)
System.out.println("Consumer buys ticket "+(number));
available=false;
notify();//售票后唤醒存票线程开始存票
if (number==size) number=size+1;//在售完最后一张票后,
//设置一个结束标志,number>size表示售票结束
}
}
class Producer extends Thread {
Tickets t=null;
public Producer(Tickets t) { this.t=t; }
public void run(){
while(t.number<t.size) t.put();}
}
class Consumer extends Thread {
Tickets t=null;
public Consumer(Tickets t){ this.t=t; }public void run() {
while(t.number<=t.size)t.sell();
}
}
- 程序说明
- 当Consumer线程售出票后,available值变为false,当Producer线程放入票后,available值变为true
- 只有available为true时,Consumer线程才能售票,否则就必须等待Producer线程放入新的票后的通知
- 只有available为false时,Producer线程才能放票,否则必须等待Consumer线程售出票后的通知
8、后台进程
后台线程
- 也叫守护线程,通常是为了辅助其它线程而运行的线程,它不妨碍程序终止。
- 一个进程中只要还有一个前台线程在运行,这个进程就不会结束;如果一个进程中的所有前台线程都已经结束那么无论是否还有未结束的后台线程,这个进程都会结束。
- “垃圾回收”便是一个后台线程。
- 如果对某个线程对象在启动(调用start方法)之前调用了setDaemon(true)方法,这个线程就变成了后台线程
#创建一个无限循环的后台线程,验证主线程结束后,程序即结束
public class Ex8_10{
public static void main(String[] args)
{ThreadTest t=new ThreadTest();
t.setDaemon(true);
t.start();
}
}
class ThreadTest extends Thread {
public void run() {
while(true)
}
}
- 运行程序,则发现整个程序在主线程结束时就随之中止运行了,如果注释掉t.setDaemon(true)语句,则程序永远不会结束
9、线程的生命周期与死锁
线程的生命周期
- 线程从产生到消亡的过程
- 一个线程在任何时刻都处于某种线程状态(thread state)

-
诞生状态
- 线程刚刚被创建。
-
就绪状态
- 线程的start方法已被执行口。
- 线程已准备好运行。
-
运行状态
- 处理机分配给了线程,线程正在运行。
-
阻塞状态(Blocked)
-
在线程发出输入/输出请求且必须等待其返回。
-
遇到用synchronized标记的方法而未获得锁。
-
为等候一个条件变量,线程调用wait()方法。
-
-
休眠状态(Sleeping)
- 执行sleep方法而进入休眠。
-
死亡状态
- 线程已完成或退出。
死锁
- 线程在运行过程中,其中某个步骤往往需要满足一些条件才能继续进行下去,如果这个条件不能满足,线程将在这个步骤上出现阻塞
- 线程A可能会陷于对线程B的等待,而线程B同样陷于对线程c的等待,依次类推,整个等待链最后又可能回到线程A。如此一来便陷入一个彼此等待的轮回中,任何线程都动弹不得,此即所谓死锁(deadlock)
- 对于死锁问题,关键不在于出现问题后调试,而是在于预防
结束线程的生命
- 通常,可通过控制run方法中循环条件的方式来结束一个线程口用stop方法可以结束线程的生命
- 但如果一个线程正在操作共享数据段,操作过程没有完成就用stop结束的话,将会导致数据的不完整,因此并不提倡使用此方法
10、线程的调度
线程的优先级
-
线程调度
- 在单CPU的系统中,多个线程需要共享CPU,在任何时间点上实际只能有一个线程在运行
- 控制多个线程在同一个CPU上以某种顺序运行称为线程调度
- Java虚拟机支持—种非常简单的、确定的调度算法,叫做固定优先级算法。这个算法基于线程的优先级对其进行调度
- 每个Java线程都有一个优先级,其范围都在1和10之间。默认情况下,每个线程的优先级都设置为5
- 在线程A运行过程中创建的新的线程对象B初始状态具有和线程A相同的优先级
- 如果A是个后台线程,则B也是个后台线程
- 可在线程创建之后的任何时候,通过setPriority(int priority)方法改变其原来的优先级
- 具有较高优先级的线程比优先级较低的线程优先执行口对具有相同优先级的线程,Java的处理是随机的
- 底层操作系统支持的优先级可能要少于10个,这样会造成一些混乱。因此,只能将优先级作为—种很粗略的工具使用。最后的控制可以通过明智地使用yield()函数来完成,(yield()把锁让出来让同级线程运行
- 我们只能基于效率的考虑来使用线程优先级,而不能依靠线程优先级来保证算法的正确性
-
假设某线程正在运行,则只有出现以下情况之一,才会使其暂停运行
-
一个具有更高优先级的线程变为就绪状态(Ready) 。
-
由于输入/输出(或其他一些原因)、调用sleep、wait、yield方法使其发生阻塞。
-
对于支持时间分片的系统,时间片的时间期满。
-
//创建两个具有不同优先级的线程,都从1递增到400000,每增加50000显示一次
public class Ex8_13{
public static void main(String[] args){
TestThread[] runners = new TestThread[2];
for (int i = 0; i<2; i++)runners[i] = new TestThread(i);
runners[0].setPriority(2);//设置第一个线程优先级为2
runners[1].setPriority(3);//设置第二个线程优先级为3
for (int i = 0; i<2; i++)
runners[i].start();
}
class TestThread extends Thread{
private int tick = 1;
private int num;
public TestThread(int i){ this.num=i;}
public void run() {
while (tick< 400000){
tick++;
if ((tick % 50000) == 0){//每隔50000进行显示
System.out.printIn("Thread #" + num + ", tick = " +tick);yield();//放弃执行权
}
}
}
11、线程安全与线程兼容与对立
线程安全:当多个线程访问同一个对象时,如果不用考虑这些线程在运行时环境下的调不需要进行额外的同步,或者在调用方进行任何其他的协调操作,调用这个对象的行为都可以获得正确的结果,那这个对象是线程安全的。
final修饰: public final a = 100;
java.lang.String :String s ="string";
枚举类型:public enum Color {RED, GREEN, BLANK, YELLOW}
java.lang.Number的子类如Long, Double
Biglnteger, BigDecimal(数值类型的高精度实现)
- 通常意义上的线程安全,需要保证这个对象单独操作是线程安全的,调用的时候不需要做额外的保障措施,但是对于一些特定顺序的连续调用,就需要在调用时使用同步手段保证调用的正确性
- 如:Vector,HashTable等
import java.util.*
public class VectorSafe {
private static Vector<Integer> vector = new Vector<Integer>();
public static void main(String[] args){
while(true){
for(inti = 0;i<10;i++){
vector.add(i);
}
Thread removeThread = new Thread(new Runnable(){//删除向量元素的线程
public void run() {
for(inti = O;i<vector.size();i++){
vector.remove(i);
}
});
Thread printThread = new Thread(new Runnable(){//读取向量元素的线程
public void run(){
for(int i = O;i<vector.size();i++){
System.out.println((vector.get(i)));}
}
);
removeThread.start();printThread.start();
while(Thread.activeCount()>20);}}
}
运行过程中出现数组下标越界错误
相对线程安全—―改进VecttorSafe
Thread removeThread = new Thread(new Runnable(){
public void run(){
synchronized(vector){//加上同步机制
for(int i = O;i<vector.size();i++){
vector.remove(i);
}
}
);
线程兼容和线程对立
- 线程兼容:对象本身不是线程安全的,但是可以通过在调用端正确地使用同步手段来保证对象在并发环境中可以安全使用
- 线程对立:无论调用端是否采取了同步措施,都无法在多线程环境中并发使用的代码
- Java中线程对立的例子:Thread类的suspend()和resume()方法可能导致死锁。所以JDK已对其声明废弃(@Deprecated)
12、线程的安全实现-互斥同步
同步的互斥实现方式:临界区(Critical Section),互斥量(Mutex),信号量(Semaphone)
Synchronized关键字:经过编译后,会在同步块前后形成monitorenter和monitorexit两个字节码。
-
(1)synchronized同步块对自己是可重入的,不会将自己锁死;
-
(2)同步块在已进入的线程执行完之前,会阻塞后面其他线程的进入
重入锁ReentrantLock (java.util.concurrent)
- 相比采用synchronized,重入锁可实现:等待可中断、公平锁、锁可以绑定多个条件
Synchronized表现为原生语法层面的互斥锁,而RenentrantLock表现为API层面的互斥锁。
import java.util.concurrent.locks.ReentrantLock;
public class Bufferlnterruptibly {
private ReentrantLock lock = new ReentrantLock();
public void write(){
lock.lock();
try {
long startTime = System.currentTimeMillis();
System.out.println("开始往这个buff写入数据...");
for( ;;)//模拟处理很长时间
{
if (System.currentTimeMillis() - startTime > Integer.MAX_VALUE)break;
}
System.out.println("终于写完了");
} finally {
lock.unlock();
}
}
public void read() throws InterruptedException{
lock.lockInterruptibly();//注意这里,可以响应中断try {
System.out.println("从这个buff读数据");}finally {
lock.unlock();
}
}
}
13、线程的安全实现-非阻塞同步
阻塞同步:互斥同步存在的问题是进行线程阻塞和唤醒所带来的性能问题,这种同步称为阻塞同步(Blocking
Synchronization)。这是—种悲观并发策略
非阻塞同步:不同于悲观并发策略,而是使用基于冲突检测的乐观并发策略,就是先进行操作,如果没有其他线程征用共享数据,则操作成功;否则就是产生了冲突,采取不断重试直到成功为止的策略,这种策略不需要把线程挂起,称为非阻塞同步
- 使用硬件处理器指令进行不断重试策略(JDK1.5以后):
- 测试并设置(Test-and-Set)
- 获取并增加(Fetch-and-Increment)口交换(Swap)
- 比较并交换(Compare-and-Swap,简称CAS)
- 加载链接,条件存储(Load-Linked, Store-conditional,简称LL,SC)
- 例: java实现类AtomicInteger,AtomicDouble等等。
class Counter {
private volatile int count = 0;
public synchronized void increment() {
count++;//若要线程安全执行执行count++,需要加锁
}
public int getCount() {
return count;
}
}
//改进
class Counter {
private Atomiclnteger count = new Atomiclnteger();public void increment() {
count.incrementAndGet();
}//使用AtomicInteger之后,不需要加锁,也可以实现线程安全。public int getCount(){
return count.get();
}
}
14、线程的安全实现-无同步方案
可重入代码:也叫纯代码。相对线程安全来说,可以保证线程安全。可以在代码执行过程中断它,转而去执行另
一段代码,而在控制权返回后,原来的程序不会出现任何错误。
线程本地存储:如果一段代码中所需要的数据必须与其他代码共享,那就看看这些共享数据的代码是否能保证在同一个线程中执行,如果能保证,就可以把共享数据的可见范围限定在同一个线程之内,这样无需同步也能保证线程之间不出现数据争用问题。
//本地存储的例子
public class SequenceNumber {
//通过匿名内部类覆盖ThreadLocal的initialvalue()方法
private static ThreadLocal<integer>seqNum=new ThreadLocal<Integer>(){
public Integer initialValue(){
return 0;
}
};
public int getNextNum(){ //②获取下一个序列值
seqNum.set(seqNum.get()+1);
return seqNum.get();
}
public static void main(String[] args){
SequenceNumber sn = new SequenceNumber()
//③3个线程共享sn,各自产生序列号
TestClient t1 = new TestClient(sn);
TestClient t2 = new TestClient(sn);
TestClient t3 = new TestClient(sn);
t1.start();
t2.start();
t3.start();
private static class TestClient extends Thread{
private SequenceNumber sn;
public TestClient(SequenceNumber sn){
this.sn = sn;
}
public void run(){
for (inti= 0;i< 3; i++){//④每个线程打出3个序列值
System.out.printIn("thread["+Thread.currentThread().getName()+""]sn["+sn.getNextNum()+"]");}}
}
}
15、锁优化
自旋锁、自适应锁、锁消除、锁粗化、偏向锁
-
自旋锁
-
互斥同步存在的问题:挂起线程和恢复线程都需罢转入内核企中元成,这些操作给系统的并发性能带来很大的压力
-
自旋锁:如果物理机器有一个以上的处理器能让两个或以上的线程同时并行执行,那就可以让后面请求锁的那个线程“稍等一会”、但不放弃处理器的执行时间,看看持有锁的线程是否很快就会释放锁。为了让线程等待,我们只需要让线程执行一个忙循环(自旋),这项技术就是自旋锁。Java中自旋次数默认10次。
-
-
自适应自旋
- 自适应意味着锁自旋的时间不再固定,而是由前一次在同一个锁上的自旋时间及锁拥有者的状态来决定。如果在同一个锁对象上,自旋等待刚刚成功获得锁,并且持有锁的线程正在运行中,那么虚拟机就会认为这次自旋也很有可能再次成功,进而它允许自旋等待相对更长的一段时间。
-
锁消除
- 定义:JVM即时编译器在运行时,对一些代码上要求同步,但是被检测到不可能存在共享数据竞争的锁进行消除
- 判定依据:如果判断在一段代码中,堆上的所有数据都不会逃逸出去从而被其他线程访问到,那就可以把它们当作栈上数据对待,认为他们是线程私有的,同步加锁自然无需进行。
-
锁粗化
- 通常我们的代码总是将同步块的作用范围限制得尽量小,只在共享数据的实际作用域中才进行同步,这样是为了使得同步操作的数量尽可能变小
- 另一种情况是,如果一系列的连续操作都对同一个对象反复加锁,甚至加锁操作是出现在循环体中,那即使没有线程争用,频繁的进行互斥同步也会导致不必要的性能损耗,此时只需要将同步块范围扩大即可。即:锁粗化
-
偏向锁
- 目的:消除数据无竞争情况下的同步原语,进一步提高程序运行的性能。偏向锁就是在无竞争的情况下把整个同步都消除掉,连CAS操作都不做
- 偏向:意思是这个锁会偏向于第一个获得它的线程,如果在接下来的执行中,该锁没有被其他线程获取,则持有偏向所得线程永远不需要再进行同步
第二章:网络编程
1、URL对象
- 通过URL读取WWW信息
import java.net.*;
import java.io.*;
public class URLReader {
public static void main(String[] args) throws Exception {URL cs = new URL("http://www.sina.com/");
BufferedReader in = new BufferedReader(newInputStreamReader(cs.openStream()));
String inputLine;
while ((inputLine = in.readLine())!= null)
System.out.println(inputLine);
in.close();
}
}
- URL类
URL(Uniform Resource Locator):一致资源定位器的简称,它表示Internet上某资源的地址。
- URL的组成
protocol:resourceName
协议名指明获取资源所使用的传输协议,如htttp、ftp、gopher、file等,资源名则应该是资源的完整地址,包括主机名、端口号、文件名或文件内部的一个引用。
http://www.sun.com/
http://home.netscape.com/home/welcome.html
http://www.gamelan.com:80/Gamelan/network.html#BOTTOM(BOTTOM:位置)
file:///e:\download\Fop.htm
- URL对象构造
public URL(String spec)
URL urlBase = new URL("http://www.gamelan.com/");
public URL(URL context, String spec)
URL gamelan =new URL("http://www.gamelan.com/pages/");
URL gamelanGames =new URL(gamelan,"Gamelan.game.html");
URL gamelanNetwork =new URL(gamelan, "Gamelan.net.html");
public URL(String protocol, String host, String file);
new URL("http", "www.gamelan.com", "/pages/Gamelan.net.html");
public URL(String protocol, String host, int port, String file);
URL gamelan =newURL("http" ,"www.gamelan.com" ,80,"pages/Gamelan.network.html");
- 例外处理
try {
URL myURL = new URL(...)
}catch (MalformedURLException e){
// exception handler code here
}
- 获取URL对象属性
public String getProtocol():获取使用协议
public String getHost():获取主机名
public String getPort():获取端口号
public String getFile():获取文件名
public String getRef():获取引用地址
2、URLConnection对象
- URLConnection
一个URLConnection对象代表一个URL资源与Java程序的通讯连接,可以通过它对这个URL资源读或写。
-
与URL的区别
-
一个单向,一个双向
-
可以查看服务器的响应消息的首部
-
可以设置客户端请求消息的首部
-
-
使用URLConnection通信的一般步骤
- 构造一个URL对象
- 调用URL对象的openConnection()方法获取对应该URL的URLConnection对象
- 配置此URLConnection对象口读取首部字段
- 获得输入流读取数据
- 获得输出流写入数据关闭连接
import java.net.*;
import java.io.*;
public class URLConnector {
public static void main(String[] args){
try {
URL cs = new URL("http://www.sina.com/");
URLConnection tc = cs.openConnection);
BufferedReader in = new BufferedReader(newInputStreamReader(tc.getInputStream()));
String inputLine;
while ((inputLine = in.readLine()) != null)
System.out.printIn(inputLine);
in.close();
}catch (MalformedURLException e)
{System.out.println("MalformedURLException");}catch (IOException e) {System.out.println("IOException");}}
}
3、Get请求与Post请求
- 发送Get请求
public static String sendGet(String url, String param) {
String result = "";
BufferedReader in = null;
try {
String urlNameString = url +"?" +param;
URL realUrl = new URL(urlNameString);//打开和URL之间的连接
URLConnection connection =realUrl.openConnection();
//设置通用的请求属性
connection.setRequestProperty("accept", "*/*");
connection.setRequestProperty("connection", "Keep-Alive");
//建立实际的连接connection.connect();
//定义 BufferedReader输入流来读取URL的响应
in = new BufferedReader(new InputStreamReader(connection.getInputStream)));
String line;
while ((line = in.readLine()) != null){result+= line;}
} catch (Exception e){}
//使用finally块来关闭输入流
finally {
try {
if(in != null){
in.close();
}
} catch (Exception e2){}
return result;
}
- 发送Post请求
public static String sendPost(String url, String param){
PrintWriter out = null;
BufferedReader in = null;
String result = "";
try {
URL realUrl = new URL(url);//打开和URL之间的连接
URLConnection conn = realUrl.openConnection();//设置通用的请求属性
conn.setRequestProperty("accept", "*/*");
conn.setRequestProperty(" connection", "Keep-Alive");
//允许输出流
conn.setDoOutput(true);
//获取URLConnection对象对应的输出流out = new PrintWriter(conn.getOutputStream());//发送请求参数
out.print(param);// flush输出流的缓冲out.flush();
//读取返回信息部分与get请求类似
in = new BufferedReader(new InputStreamReader(conn.getInputStream0));
String line;
while (line = in.readLine()!= null)
{result += line;
}
} catch (Exception e){}
finally{
try{
if(out!=null){
out.close();
}
if(in!=null){
in.close0;
}
catch(IOException ex){}
return result;
- HttpURLConnection类
- 在URLConnection的基础上提供一系列针对http请求的内容。
- HTTP状态码(例如HTTP_OK:200 )
- setRequestMethod(设置请求方法GETPOST等)
- getResponseCode(获取HTTP的响应)
4、socket通信原理
- TCP传输协议 (Transport Control Protocol )
- 面向连接的能够提供可靠的流式数据传输的协议。类似于打电话的过程。
- URL, URLConnection, Socket, ServerSocket等类都使用TCP协议进行网络通讯。
- socket通讯
- 网络上的两个程序通过一个双向的通讯连接实现数据的交换,这个双向链路的一端称为一个socket。
- socket通常用来实现客户方和服务方的连接。

5、socket通信实现
- 创建socket
Socket(InetAddress address, int port);
Socket(String host, int port);
Socket(InetAddress host, intport,InetAddress localAddr, int localPort)
Socket(Stringhost,intport,InetAddress localAddr, int localPort)
- 客户端Socket的建立
try{
Socket socket=new Socket("127.0.0.1",2000);}catch(IOException e){
System.out.println("Error:"+e)
}
- 服务器端Socket的建立
ServerSocket server=null;
try {
server=new ServerSocket(2000);}catch(IOException e){
System.out.println("can not listen to :"+e);}
Socket socket=null;try {
socket=server.accept();
}catch(IOException e){
System.out.println("Error:"+e);}
- 打开输入/输出流
PrintStream os=new PrintStream(new BufferedOutputStream(socket.getOutputStream()));
DatalnputStream is=new DatalnputStream(socket.getlnputStream());
PrintWriter out=new PrintWriter(socket.getOutputStream(),true);
BufferedReader in=new BufferedReader(new InputStreamReader(socket.getlnputStream()));
- 关闭socket
os.close();
is.close();
socket.close();
- 网络聊天程序
//客户端程序
import java.io.*;import java.net.*;
public class TalkClient {
public static void main(String args[]){try{
Socket socket=new Socket(“127.0.0.1",4700);
BufferedReader sin=new BufferedReader(newInputStreamReader(System.in));
PrintWriter os=new PrintWriter(socket.getOutputStream());
BufferedReader is=new BufferedReader( newlnputStreamReader(socket.getlnputStream()));
String readline;
readline=sin.readLine();
while(!readline.equals("bye")){os.printIn(readline);
os.flush();
System.out.println("Client:"+readline); System.out.println("Server:"+is.readLine());
readline=sin.readLine();
}
os.close();
is.close();
//服务端程序
import java.io. *;
import java.net.*;
import java.applet.Applet;
public class TalkServer{
public static void main(String args[]){
try{
ServerSocket server=null;
try{
server=new ServerSocket(4700);}catch(Exception e) {
System.out.printIn("can not listen to:"
+e);
}
Socket socket=null;
try{
socket=server.accept();}catch(Exception e){
System.out.println("Error."+e);}
String line;
BufferedReader is=new BufferedReader(new InputStreamReader(socket.getlnputStream()));
PrintWriter os=new PrintWriter(socket.getOutputStream());
BufferedReader sin=new BufferedReader(newlnputStreamReader(System.in));
System.out.println("Client:"+is.readLine());
line=sin.readLine();
while(!line.equals("bye")){
os.println(line);
os.flush();
System.out.println("Server:"+line);
System.out.println("Client:"+is.readLine());
line=sin.readLine();
}
os.close();
is.close();
socket.close();
server.close();
}catch(Exception e){
System.out.println("Error:"+e);
}
}
}
6、 Socket多客户端通信实现
//客户端程序 MultiTalkClient.java
import java.io.*;
import java.net.*;
public class MultiTalkClient {
int num;
public static void main(String args[]){
try{
Socket socket=new Socket("127.0.0.1",4700);
BufferedReader sin=new BufferedReader(new InputStreamReader(System.in));
PrintWriter os=new PrintWriter(socket.getOutputStream());
BufferedReader is=new BufferedReader(new lnputStreamReader(socket.getlnputStream()));
String readline;
readline=sin.readLine();
while(!readline.equals("bye"))
{
os.println(readline);
os.flush();
System.out.println("Client:"+readline);
System.out.println("Server:" +is.readLine());
readline=sin.readLine();
}
os.close();
is.close();
socket.close();}catch(Exception e){
System.out.println("Error"+e);}
}
}
//服务器端程序: MultiTalkServer.java
import java.io.*;
import java.net.*;
public class MultiTalkServer{
static int clientnum=O;
public static void main(String args[]) throws IOException {
ServerSocket serverSocket=null;
boolean listening=true;
try{
serverSocket=new ServerSocket(4700);
}catch(IOException e){
System.out.println("Could not listen on port:4700.");
System.exit(-1);
}
while(listening){
new ServerThread(serverSocket.accept(),clientnum).start();
clientnum++;
}
serverSocket.close();
}
}
//程序ServerThread.java
import java.io.*;
import java.net.*;
public class ServerThread extends Thread{
Socket socket=null;
int clientnum;
public ServerThread(Socket socket,int num)
{
this.socket=socket;
clientnum=num+1;
}
public void run() {
try{
String line;
BufferedReader is=new BufferedReader(new InputStreamReader(socket.getlnputStream()));
PrintWriter os=new PrintWriter(socket.getOutputStream());
BufferedReader sin=new BufferedReader(new InputStreamReader(System.in));
System.out.println(“Client"+clientnum+":”+is.readLine());
line=sin.readLine();
while(!line.equals("bye"))
os.println(line);
os.flush();
System.out.println("Server:"+line);
System.out.println("Client:"+ clientnum+is.readLine());
line=sin.readLine();
}
os.close();
is.close();
socket.close();
}catch(Exception e){
System.out.println("“Error:"+e);
}
}
}
第三章:Java虚拟机
1、Java虚拟机的概念
Java虚拟机是一个想象中的机器,在实际的计算机上通过软件模拟来实现。Java虚拟机有自己想象的硬件,如处理器、堆栈、寄存器、还有相应的指令系统。
2、Java虚拟机实现跨平台
Java虚拟机更像是提供了统一的接口,类似于JDBC协议,并且虚拟机提供了不同操作系统之上针对统一接口所需要的驱动(实现)。
Java应用程序 Java应用程序 Java应用程序
Java虚拟机 Java虚拟机 Java虚拟机
Windows UNIX Linux RTOS
X86 SPARC MIPS PPC
- 一个运行中的Java虚拟机有着一个清晰的任务:执行Java程序。程序开始执行时它才运行,程序结束时它就停止。每个Java程序会单独运行一个Java虚拟机。通过命令行启动Java虚拟机: java XXX(类名)
- Java虚拟机总是开始于一个main()方法,这个方法必须是公有public、返回void、直接接收一个字符串数组。在程序执行时,必须给Java虚拟机指明这个包含有main()方法的类名。public static void main(String args[])
- main()方法是程序的起点,它被执行的线程初始化为程序的初始线程。程序中其它的线程都由他来启动。Java中的线程分为两种:守护线程(daemon)和普通线程(non-daemon)。守护线程是Java虚拟机自己使用的线程,比如负责垃圾收集的线程。也可以把自己的程序设置为守护线程。包含main()方法的初始线程不是守护线程。
- 只要Java虚拟机中还有普通的线程在执行,Java虚拟机就不会停止。如果有足够的权限,就可以调用exit()方法终止程序。
- 在Java虚拟机的规范中定义了一系列的子系统、内存区域、数据类型和使用指南。这些组件构成了Java虚拟机的内部结构,他们不仅仅为Java虚拟机的实现提供了清晰的内部结构,更是严格规定了Java虚拟机实现的外部
行为。 - 每一个Java虚拟机都有一个类加载器子系统(class loader subsystem),负责加载程序中的类型(类class和接口interface),并赋予唯一的名字。每一个Java虚拟机都有一个执行引擎(execution engine)负责执行被加载类中包含的指令。
- 所有Java虚拟机中使用的数据都有确定的数据类型数据类型和操作都在Java虚拟机规范中严格定义。Java中的数据类型分为原始数据类型primitive types)和引用数据类型(reference type)。
- 在Java虚拟机中还存在一个Java语言中不能使用的原始数据类型----返回值类型(return value)。这种类型被用来实现Java程序中的“finally classes"。引用类型可能被创建为:类类型(class type),接口类型(interface type)
数组类型(array type)。他们都引用被动态创建的对象。当引用类型引用null时,说明没有引用任何对象。
2、Java虚拟机垃圾回收机制
**(1)回收对象?**堆 (包括Java堆 和 方法区)是 垃圾回收的主要对象,特别是Java堆。
(2)那些内存需要回收?(对象是否可以被回收的两种经典算法: 引用计数法 和 可达性分析算法)
- 引用计数法
引用计数算法是垃圾收集器中的早期策略。在对象中添加一个引用计数器,每当有一个地方 引用它时,计数器值就加一;当引用失效时,计数器值就减一;任何时刻计数器为零的对象就是不可 能再被使用的。
引用计数算法(Reference Counting)虽然占用了一些额外的内存空间来进行计数,但 它的原理简单,判定效率也很高,在大多数情况下它都是一个不错的算法。
public class ReferenceCountingGc {
public object instance = null ;
public static void testGc ( ) {
ReferenceCountingGc objA = new ReferenceCountingGc ( );
ReferenceCountingGc objB = new ReferenceCountingGC ( ) ;
objA.instance = objB;
objB.instance = objA;
objA = null;
objB =null;
//假设在这行发生Gc,objA和objB是否能被回收?
system.gc ( );
}
}
//结果:objA和objB并没有被回收,因为虽然最后两个对象都赋值为空,但是二者之间任然存在相互引用,因此引用计数器不为零。这也从侧面说明了Java虚拟机并不是通过引用计数算法来判断对象是否存活的。
- 可达性分析算法
当前主流的商用程序语言(Java、C#,上溯至前面提到的古老的Lisp)的内存管理子系统,都是 通过可达性分析(Reachability Analysis)算法来判定对象是否存活的。这个算法的基本思路就是通过 一系列称为“GC Roots”的根对象作为起始节点集,从这些节点开始,根据引用关系向下搜索,搜索过 程所走过的路径称为“引用链”(Reference Chain),如果某个对象到GC Roots间没有任何引用链相连, 或者用图论的话来说就是从GC Roots到这个对象不可达时,则证明此对象是不可能再被使用的。
其中,GC Roots包括以下几种:
- 虚拟机栈(栈帧中的局部变量表)中引用的对象;
- 方法区中类静态属性引用的对象;
- 方法区中常量引用的对象;
- 本地方法栈中Native方法引用的对象;
(3)什么时候回收? (堆的新生代、老年代、永久代的垃圾回收时机,MinorGC 和 FullGC)
1、标记-清除算法
算法分为“标记”和“清除”两个阶段:首先标记出所有需要回 收的对象,在标记完成后,统一回收掉所有被标记的对象,也可以反过来,标记存活的对象,统一回 收所有未被标记的对象。标记过程就是对象是否属于垃圾的判定过程。
缺点:
执行效率不稳定。如果Java堆中包含大量对象,而且其中大部分是需要被回收的,这时必须进行大量标记和清除的动作,导致标记和清除两个过 程的执行效率都随对象数量增长而降低。
内存空间的碎片化问题。标记、清除之后会产生大量不连续的内存碎片,空间碎片太多可能会导致当以后在程序运行过程中需要分配较大对象时无法找 到足够的连续内存而不得不提前触发另一次垃圾收集动作。
2、标记-复制算法
将可用内存按容量划分为大小相等的两块,每次只使用其中的一块。当这一块的内存用完了,就将还存活着 的对象复制到另外一块上面,然后再把已使用过的内存空间一次清理掉。
3、标记-整理算法
标记过程仍然与“标记-清除”算法一样,但后续步骤不是直接对可回收对象进行清理,而是让所有存活的对象都向内存空间一端移动,然后直接清理掉边界以外的内存。标记-清除算法与标记-整理算法的本质差异在于前者是一种非移动式的回收算法,而后者是移动式的。
4、分代收集算法
-
新生代(Minor GC)
-
老年代(Major GC)(Full GC)
-
永久代
年轻代
java新生成的对象几乎都会存放在新生代的Eden区中(如果对象的占用内存较大则直接分配至老年代中),当Eden区内存不够的时候就会触发MinorGC(Survivor满不会引发MinorGC,而是将对象移动到老年代中。
在Minor GC开始的时候,对象只会存在于Eden区和Survivor from区,Survivor to区是空的。
Minor GC操作后,Eden区如果仍然存活(判断的标准是被引用了,通过GC root进行可达性判断)的对象,将会被移到Survivor To区。而From区中,对象在Survivor区中每熬过一次Minor GC,年龄就会+1岁,当年龄达到一定值(年龄阈值,可以通过-XX:MaxTenuringThreshold来设置,默认是15)的对象会被移动到年老代中,否则对象会被复制到“To”区。经过这次GC后,Eden区和From区已经被清空。
“From”区和“To”区互换角色,原Survivor To成为下一次GC时的Survivor From区, 总之,GC后,都会保证Survivor To区是空的。
奇怪为什么有 From和To,2块区域?
这就要说到新生代Minor GC的算法了:标记-复制算法,把内存区域分为两块,每次使用一块,GC的时候把一块中的内容移动到另一块中,原始内存中的对象就可以被回收了,优点:避免内存碎片。
老年代
Full GC 是发生在老年代的垃圾收集动作,所采用的是标记-清除算法。
现实的生活中,老年代的人通常会比新生代的人 “早死”。堆内存中的老年代(Old)不同于这个,老年代里面的对象几乎个个都是在 Survivor 区域中熬过来的,它们是不会那么容易就 “死掉” 了的。因此,Full GC 发生的次数不会有 Minor GC 那么频繁,并且做一次 Full GC 要比进行一次 Minor GC 的时间更长。 另外,标记-清除算法收集垃圾的时候会产生许多的内存碎片 ( 即不连续的内存空间 ),此后需要为较大的对象分配内存空间时,若无法找到足够的连续的内存空间,就会提前触发一次 GC 的收集动作。
(4)如何回收?(三种经典垃圾回收算法(标记清除算法、复制算法、标记整理算法)及分代收集算法 和 七种垃圾收集器)
7种作用于不同分代的收集器,其中用于回收新生代的收集器包括Serial、PraNew、Parallel Scavenge,回收老年代的收集器包括Serial Old、Parallel Old、CMS,还有用于回收整个Java堆的G1收集器。(了解即可)
Serial收集器(复制算法): 新生代单线程收集器,标记和清理都是单线程,优点是简单高效;
Serial Old收集器 (标记-整理算法): 老年代单线程收集器,Serial收集器的老年代版本;
ParNew收集器 (复制算法): 新生代收并行集器,实际上是Serial收集器的多线程版本,在多核CPU环境下有着比Serial更好的表现;
Parallel Scavenge收集器 (复制算法): 新生代并行收集器,追求高吞吐量,高效利用 CPU。吞吐量 = 用户线程时间/(用户线程时间+GC线程时间),高吞吐量可以高效率的利用CPU时间,尽快完成程序的运算任务,适合后台应用等对交互相应要求不高的场景;
Parallel Old收集器 (标记-整理算法): 老年代并行收集器,吞吐量优先,Parallel Scavenge收集器的老年代版本;
CMS(Concurrent Mark Sweep)收集器(标记-清除算法): 老年代并行收集器,以获取最短回收停顿时间为目标的收集器,具有高并发、低停顿的特点,追求最短GC回收停顿时间。
G1(Garbage First)收集器 (标记-整理算法): Java堆并行收集器,G1收集器是JDK1.7提供的一个新收集器,G1收集器基于“标记-整理”算法实现,也就是说不会产生内存碎片。此外,G1收集器不同于之前的收集器的一个重要特点是:G1回收的范围是整个Java堆(包括新生代,老年代),而前六种收集器回收的范围仅限于新生代或老年代。
第四章:Java反射机制
1、Java反射机制的概念
Java反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意方法和属性;这种动态获取信息以及动态调用对象方法的功能称为Java语言的反射机制。
2、反射机制常用方法
(1)类类
Class.forName(完整类名)//通过类名,从内存中获取数据类型
类名.class//通过类名,从内存中直接获取数据类型
对象.getClass()//通过对象直接从内存中获取数据类型;这一点更能凸显java反射机制。
(2)反射实例化
//通过获取到的数据类型,调用数据类型构造函数进行实例化//或使用空参构造函数直接实例化成一个对象
getConstructor->newlnstance
getDeclaredConstructor->newlnstance
newlnstance
(3)反射动态方法调用
getMethod
getDeclaredMethod
getDeclaredField
(4)反射读写属性
getDeclaredFields
getDeclaredField
(5)动态获取数据类型
person stu=new student();//数据类型只能扩展,不可压缩。
//stu在运行时HOOk student数据类型,前提student继承person类。
//继承person数据类型的stu,可HOOK student的数据类型作为自己的数据类型。
3、Java程序运行过程
Java源文件(.java文件)–>经过Javac编译器编译–>二进制字节码文件(.class文件)–>Jvm类加载器加载–>解释器解释–>机器码(机器可理解的代码)–>操作系统平台
4、Java反射作用以及原理
4.1 反射作用
通过反射,可以在运行时动态地创建对象并调用其属性,不需要提前在编译期知道运行的对象是谁。
4.2 反射原理
简单来说就是通过反编译,来获取类对象的属性、方法等信息。
Java的反射机制是在编译时并不确定是哪个类被加载了,而是在程序运行的时候才加载、探知、自审。使用的是在编译期并不知道的类。
反编译:.class–>.java
注意:Jvm从本地磁盘把字节码文件加载到Jvm内存中,Jvm会自动创建一个class对象。即一个类只会产生一个class对象。原因:类加载机制–双亲委派机制
5、类加载机制–双亲委派机制
JVM中提供了三层的ClassLoader:
BootstrapClassLoader:主要负责加载核心的类库(java.lang.*等),构造ExtClassLoader和APPClassLoader。
ExtClassLoader:主要负责加载jre/lib/ext目录下的一些扩展的jar。
AppClassLoader:主要负责加载应用程序的主函数类。
在考虑存在自定义类加载器情况下,对类的加载首先是从自定义类加载器中检查该类是否已经被加载过,如果没有被加载,则向上委托,拿到父类构造器AppClassLoader加载器进行检查,如果还是没有被加载,则依次向上委托,不断检查父类加载器是否已经加载过该类。如果已经加载,则无需进行二次加载,直接返回。如果经过BootstrapClassLoader加载器检查后,发现该类未被加载,则从父类向下开始加载该类。如果BootstrapClassLoader加载器无法加载该类,则交由子类加载器加载,依次向下加载。
双亲委派机制的作用:
- 避免相同类二次加载
- 防止核心类库API被修改
6、Java反射使用
6.1 获取类对象的三种方式
(1)通过Class类中的静态方法forName,来获取类对象
Class clazz1 = Class.forName("全限定类名");
(2)通过类名.class
Class clazz2 = Demo.class;
(3)通过类的实例获取该类的字节码文件对象
Class clazz3 = p.getClass();
6.2 反射获取类属性、方法、构造方法
public class TargetDemo {
public TargetDemo (){}
public TargetDemo (String str){
this.str = str;
System.out.println("执行构造器方法");
}
public String str = "hello";
private String username;
private int age;
public void print(){
System.out.println("TargetDemo");
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
public class TestDemo {
public static void main(String[] args) {
try {
//获取类对象
Class target = Class.forName("com.torlesse.consumer.test.TargetDemo");
//获取类对象属性 公共部分不能访问私有
for (Field field : target.getFields()) {
System.out.println(field.getName());
}
//获取构造器
Constructor targetDeclaredConstructor = target.getDeclaredConstructor(String.class);
Object o = targetDeclaredConstructor.newInstance("demo");
System.out.println(o);
//实例化对象
Object o1 = target.newInstance();
//获取方法
Method method = target.getMethod("print");
method.invoke(o1,null);
} catch (Exception e) {
e.printStackTrace();
}
}
}

public TargetDemo (String str){
this.str = str;
System.out.println(“执行构造器方法”);
}
public String str = "hello";
private String username;
private int age;
public void print(){
System.out.println("TargetDemo");
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
```java
public class TestDemo {
public static void main(String[] args) {
try {
//获取类对象
Class target = Class.forName("com.torlesse.consumer.test.TargetDemo");
//获取类对象属性 公共部分不能访问私有
for (Field field : target.getFields()) {
System.out.println(field.getName());
}
//获取构造器
Constructor targetDeclaredConstructor = target.getDeclaredConstructor(String.class);
Object o = targetDeclaredConstructor.newInstance("demo");
System.out.println(o);
//实例化对象
Object o1 = target.newInstance();
//获取方法
Method method = target.getMethod("print");
method.invoke(o1,null);
} catch (Exception e) {
e.printStackTrace();
}
}
}

本文详细介绍了Java中的线程概念、创建与管理,包括线程安全、同步机制、死锁处理,以及网络编程的基础,如URL、URLConnection、Socket通信和垃圾回收机制。内容涵盖线程的生命周期、线程安全实现、同步控制方法,以及Java虚拟机的工作原理,如内存区域、垃圾收集算法等。
1046

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



