客户端发起socket连接请求
Socket socket = new Socket(服务器IP地址,TCP端口)
客户端通过建立socket来连接服务器。
socket连接的建立,代表客户端与服务端之间都存有对方的信息,包括IP地址和TCP端口。
TCP端口
TCP端口不是实际的物理端口,是逻辑端口,用来表示不同应用程序的服务。
一台服务器上有65536个端口(0~65535)。
其中,0~1023这些TCP端口是保留给已知的特定的服务使用,比如,HTTP服务是80端口,Telnet服务是23端口,SMTP服务是25端口。
客户端和服务端的通信
客户端与服务端的应用程序通过socket建立连接。
//Client.java
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.Socket;
public class Client {
public void go(){
try {
Socket socket = new Socket("127.0.0.1",5000);
InputStreamReader stream = new InputStreamReader(socket.getInputStream());
BufferedReader reader = new BufferedReader(stream);
String s = reader.readLine();
System.out.println("s is "+s);
reader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
public static void main(String[] args){
Client c = new Client();
c.go();
}
}
//Server.java
import java.io.IOException;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
public class Server {
public void go(){
try {
ServerSocket serverSocket = new ServerSocket(5000);
while(true){
Socket socket = serverSocket.accept();
PrintWriter writer = new PrintWriter(socket.getOutputStream());
// String s = "hello world";
String s = "hahahahaha";
writer.println(s);
writer.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
public static void main(String[] args){
Server server = new Server();
server.go();
}
}
以上,服务器只连接了一个客户端,如果有多个客户端呢?
多线程
线程是独立的线程,代表独立的执行空间。
每个Java应用程序都会有一个主线程,即main()
。
java.lang.Thread
是表示一个线程的类,要启动一个新线程,就要创建一个Thread实例。
Thread实例会有一个任务,线程一启动就会去执行这个任务。
线程的任务是实现Runnable接口的实例。
Runnable接口只有一个方法: run()
,线程一启动,就会执行run方法。
//MyRunnable.java
public class MyRunnable implements Runnable {
@Override
public void run() {
System.out.println("running in a new thread");
}
}
//Test.java
public class Test {
public static void main(String[] args){
MyRunnable runnableJob = new MyRunnable();
Thread myThread = new Thread(runnableJob);
myThread.start();
System.out.println("running in main thread");
}
}
有时候 主线程的“running in main thread”会先打印,有时候 新线程的“running in a new thread”会先打印。
注意哈,线程调度器决定哪个线程跑哪个线程停,但不会保证任何执行时间和顺序。
void start()
启动线程void join()
连接线程static void sleep()
让线程闲置
继续看个例子来了解下 线程调度器这位大哥。
//Test.java
public class Test implements Runnable {
public static void main(String[] args){
Runnable test = new Test();
Thread a = new Thread(test);
a.setName("threadA");
Thread b = new Thread(test);
b.setName("ThreadB");
a.start();
b.start();
}
@Override
public void run() {
for(int i=0;i<5;i++){
System.out.println(Thread.currentThread().getName());
}
}
}
瞧,线程调度器决定了谁跑谁停,我们可左右不了。
让线程小睡会儿
//MyRunnable.java
public class MyRunnable implements Runnable {
@Override
public void run() {
System.out.println("running in a new thread");
}
}
//Test.java
public class Test {
public static void main(String[] args){
MyRunnable runnableJob = new MyRunnable();
Thread myThread = new Thread(runnableJob);
myThread.start();
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("running in main thread");
}
}
并发性问题
多线程可能产生并发性问题,即两个或两个以上线程同时对某个单一对象的数据进行存取。
//BankAccount.java
public class BankAccount {
private int balance = 100;
public int getBalance(){
return balance;
}
public void withdraw(int amount){
balance = balance - amount;
}
}
//JohnAndLilyJob.java
public class JohnAndLilyJob implements Runnable{
public BankAccount account = new BankAccount();
public static void main(String[] args){
JohnAndLilyJob job = new JohnAndLilyJob();
Thread a = new Thread(job);
a.setName("John");
Thread b = new Thread(job);
b.setName("Lily");
a.start();
b.start();
}
@Override
public void run() {
for(int i=0;i<5;i++){
makeWithdraw(20);
System.out.println("当前余额:"+account.getBalance());
if(account.getBalance()<0){
System.out.println("已超支!"+ Thread.currentThread().getName()+" 无法提款");
}
}
}
public void makeWithdraw(int amount){
if(account.getBalance()>=amount){
System.out.println(Thread.currentThread().getName()+" 准备提款");
try {
System.out.println(Thread.currentThread().getName()+" 即将入睡");
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+" 已经醒来");
account.withdraw(amount);
System.out.println(Thread.currentThread().getName()+" 完成提款");
System.out.println("当前余额:"+account.getBalance());
}else{
System.out.println("当前余额:"+account.getBalance());
System.out.println("余额不足!" + Thread.currentThread().getName()+" 无法提款");
}
}
}
synchronized防止两个线程同时进入同一对象的同一方法
使用关键词synchronized
修改方法makeWithdraw
,使得该方法仅可被单一线程调用。
public synchronized void makeWithdraw(int amount){
if(account.getBalance()>=amount){
System.out.println(Thread.currentThread().getName()+" 准备提款");
try {
System.out.println(Thread.currentThread().getName()+" 即将入睡");
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+" 已经醒来");
account.withdraw(amount);
System.out.println(Thread.currentThread().getName()+" 完成提款");
System.out.println("当前余额:"+account.getBalance());
}else{
System.out.println("当前余额:"+account.getBalance());
System.out.println("余额不足!" + Thread.currentThread().getName()+" 无法提款");
}
}
每个Java对象都有一把锁,这把锁只有一把钥匙。
当对象具有同步化方法时,线程只有取得对象的锁的钥匙后才能进入同步化方法。
对象就算有多个同步化方法,也还是只有一个锁。一旦某个线程进入该对象的某个同步化方法,那么其他线程就无法进入该对象的任何同步化方法。