(1)线程基本概念:一个线程是一个程序内部的顺序控制流。
(2)进程的基本概念:每个进程都有独立的代码和数据空间(进程上下文),进程切换的开销大。
(3)线程:轻量的进程,同一类线程共享代码和数据空间,每个线程有独立的运行栈和程序计数器(PC),开销小。
(4)多进程:在操作系统中,能同时运行多个任务(程序)。
(5)多线程:在同一应用程序中,有多个顺序流同时执行。
(6)线程的概念模型:虚拟的CPU,封装在java.lang.Thread类中。
(7)每个线程都是通过某个特定Thread对象的方法run()来完成其操作的,方法run()称为线程体。
(8)构造线程的两种方法
定义一个线程类,它继承类Thread并重写其中的方法run( );
提供一个实现接口Runnable的类作为线程的目标对象,在初始化一个Thread类或者Thread子类的线程对象时,把目标对象传递给这个线程实例,由该目标对象提供线程体run( )。
public Thread( ThreadGroup group, Runnable target,String name);
(1)Thread类
(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 " );
}
}
(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)两种线程构造方式的比较
(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");
}}
(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();
}
}
有时线程之间彼此不独立、需要同步口
线程间的互斥
同时运行的几个线程需要共享一个(些)数据
共享的数据,在某一时刻只允许一个线程对其进行操作
“生产者/消费者”问题
用两个线程模拟存票、售票过程
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使用监视器机制
将需要互斥的语句段放入synchronized(object){}语句中,且两处的object是相同的
class Producer extends Thread {
Tickets t=null;
public Producer(Tickets t) { this.t=t,}public void run(){
while((t.number)
说明
改进上例功能。将互斥方法放在共享的资源类 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.put();
}
}
class Consumer extends Thread{
Tickets t=null;
public Consumer(Tickets t){this.t=t; }
历
public void run()
{//如果售票数小于限定总量,则个断售票while(t.i
t.sell();
}
}
同步与锁的要点:
线程的等待―—wait()方法
更有效地协调不同线程的工作,需要在线程间建立沟通渠道,通过线程间的“对话”来解决线程间的同步问题
java.lang.Object类的一些方法为线程间的通讯提供了有效手段一wait()如果当前状态不适合本线程执行,正在执行同步代码(synchronized)的某个线程A调用该方法(在对象x上),该线程暂停执行而进入对象x的等待池,并释放已获得的对象x的锁。线程A要一直等到其他线程在对象x上调用notify或notifyAll方法,才能够在重新获得对象x的锁后继续执行从wait语句后继续执行)
线程的唤醒――notify()和notifyAlI()方法
#修改上例,要求:使每存入一张票,就售一张票,售出后,再存入
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();
}
}
后台线程
#创建一个无限循环的后台线程,验证主线程结束后,程序即结束
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)
}
}
线程的生命周期
诞生状态
就绪状态
运行状态
阻塞状态(Blocked)
在线程发出输入/输出请求且必须等待其返回。
遇到用synchronized标记的方法而未获得锁。
为等候一个条件变量,线程调用wait()方法。
休眠状态(Sleeping)
死亡状态
死锁
结束线程的生命
线程的优先级
线程调度
假设某线程正在运行,则只有出现以下情况之一,才会使其暂停运行
一个具有更高优先级的线程变为就绪状态(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();//放弃执行权
}
}
}
线程安全:当多个线程访问同一个对象时,如果不用考虑这些线程在运行时环境下的调不需要进行额外的同步,或者在调用方进行任何其他的协调操作,调用这个对象的行为都可以获得正确的结果,那这个对象是线程安全的。
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(数值类型的高精度实现)
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);
}
}
);
线程兼容和线程对立
同步的互斥实现方式:临界区(Critical Section),互斥量(Mutex),信号量(Semaphone)
Synchronized关键字:经过编译后,会在同步块前后形成monitorenter和monitorexit两个字节码。
(1)synchronized同步块对自己是可重入的,不会将自己锁死;
(2)同步块在已进入的线程执行完之前,会阻塞后面其他线程的进入
重入锁ReentrantLock (java.util.concurrent)
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();
}
}
}
阻塞同步:互斥同步存在的问题是进行线程阻塞和唤醒所带来的性能问题,这种同步称为阻塞同步(Blocking
Synchronization)。这是—种悲观并发策略
非阻塞同步:不同于悲观并发策略,而是使用基于冲突检测的乐观并发策略,就是先进行操作,如果没有其他线程征用共享数据,则操作成功;否则就是产生了冲突,采取不断重试直到成功为止的策略,这种策略不需要把线程挂起,称为非阻塞同步
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();
}
}
可重入代码:也叫纯代码。相对线程安全来说,可以保证线程安全。可以在代码执行过程中断它,转而去执行另
一段代码,而在控制权返回后,原来的程序不会出现任何错误。
线程本地存储:如果一段代码中所需要的数据必须与其他代码共享,那就看看这些共享数据的代码是否能保证在同一个线程中执行,如果能保证,就可以把共享数据的可见范围限定在同一个线程之内,这样无需同步也能保证线程之间不出现数据争用问题。
//本地存储的例子
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()+"]");}}
}
}
自旋锁、自适应锁、锁消除、锁粗化、偏向锁
自旋锁
互斥同步存在的问题:挂起线程和恢复线程都需罢转入内核企中元成,这些操作给系统的并发性能带来很大的压力
自旋锁:如果物理机器有一个以上的处理器能让两个或以上的线程同时并行执行,那就可以让后面请求锁的那个线程“稍等一会”、但不放弃处理器的执行时间,看看持有锁的线程是否很快就会释放锁。为了让线程等待,我们只需要让线程执行一个忙循环(自旋),这项技术就是自旋锁。Java中自旋次数默认10次。
自适应自旋
锁消除
锁粗化
偏向锁
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(Uniform Resource Locator):一致资源定位器的简称,它表示Internet上某资源的地址。
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
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
}
public String getProtocol():获取使用协议
public String getHost():获取主机名
public String getPort():获取端口号
public String getFile():获取文件名
public String getRef():获取引用地址
一个URLConnection对象代表一个URL资源与Java程序的通讯连接,可以通过它对这个URL资源读或写。
与URL的区别
一个单向,一个双向
可以查看服务器的响应消息的首部
可以设置客户端请求消息的首部
使用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");}}
}
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;
}
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;
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)
try{
Socket socket=new Socket("127.0.0.1",2000);}catch(IOException e){
System.out.println("Error:"+e)
}
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()));
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虚拟机是一个想象中的机器,在实际的计算机上通过软件模拟来实现。Java虚拟机有自己想象的硬件,如处理器、堆栈、寄存器、还有相应的指令系统。
Java虚拟机更像是提供了统一的接口,类似于JDBC协议,并且虚拟机提供了不同操作系统之上针对统一接口所需要的驱动(实现)。
Java应用程序 Java应用程序 Java应用程序
Java虚拟机 Java虚拟机 Java虚拟机
Windows UNIX Linux RTOS
X86 SPARC MIPS PPC
**(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包括以下几种:
(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反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意方法和属性;这种动态获取信息以及动态调用对象方法的功能称为Java语言的反射机制。
(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的数据类型作为自己的数据类型。
Java源文件(.java文件)–>经过Javac编译器编译–>二进制字节码文件(.class文件)–>Jvm类加载器加载–>解释器解释–>机器码(机器可理解的代码)–>操作系统平台
4.1 反射作用
通过反射,可以在运行时动态地创建对象并调用其属性,不需要提前在编译期知道运行的对象是谁。
4.2 反射原理
简单来说就是通过反编译,来获取类对象的属性、方法等信息。
Java的反射机制是在编译时并不确定是哪个类被加载了,而是在程序运行的时候才加载、探知、自审。使用的是在编译期并不知道的类。
反编译:.class–>.java
注意:Jvm从本地磁盘把字节码文件加载到Jvm内存中,Jvm会自动创建一个class对象。即一个类只会产生一个class对象。原因:类加载机制–双亲委派机制
JVM中提供了三层的ClassLoader:
BootstrapClassLoader:主要负责加载核心的类库(java.lang.*等),构造ExtClassLoader和APPClassLoader。
ExtClassLoader:主要负责加载jre/lib/ext目录下的一些扩展的jar。
AppClassLoader:主要负责加载应用程序的主函数类。
在考虑存在自定义类加载器情况下,对类的加载首先是从自定义类加载器中检查该类是否已经被加载过,如果没有被加载,则向上委托,拿到父类构造器AppClassLoader加载器进行检查,如果还是没有被加载,则依次向上委托,不断检查父类加载器是否已经加载过该类。如果已经加载,则无需进行二次加载,直接返回。如果经过BootstrapClassLoader加载器检查后,发现该类未被加载,则从父类向下开始加载该类。如果BootstrapClassLoader加载器无法加载该类,则交由子类加载器加载,依次向下加载。
双亲委派机制的作用:
(1)通过Class类中的静态方法forName,来获取类对象
Class clazz1 = Class.forName("全限定类名");
(2)通过类名.class
Class clazz2 = Demo.class;
(3)通过类的实例获取该类的字节码文件对象
Class clazz3 = p.getClass();
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();
}
}
}