Head First Java 15 网络联机

网络联机

Socket

  • socket连接的建立代表两台机器之间存有对方的信息,包括网络地址和TCP的端口号
Socket chatSocket = new Socket("123.1.1.1", 5000);
PrintWriter writer = new PrintWriter(chatSocket.getOutputStream());
writer.println("message to send");
writer.print("another message");
BufferedReader reader = new BufferedReader(new InputStreamReader(chatSocket.getInputStream()));
String message = reader.readLine();
  • TCP端口号是一个16位宽、用来识别服务器上特定程序的数字,每个服务器有65536个端口,其中从0~1023的TCP端口是保留给已知的特定服务使用,你不应该使用这些端口。其中FTP为20,Telnet为23,SMTP为25,Time为37,HTTP为80,POP3为110,HTTPS为443
import java.io.*;
import java.net.*;
public class DailyAdviceClient {
  public void go() {
    try {
      Socket s = new Socket(“127.0.0.1”, 4242);
      InputStreamReader streamReader = new InputStreamReader(s.getInputStream());
      BufferedReader reader = new BufferedReader(streamReader);
      String advice = reader.readLine();
      System.out.println(“Today you should: “ + advice);
      reader.close();
    }
    catch(IOException ex) {
      ex.printStackTrace();
    }
  }
  public static void main(String[] args) {
    DailyAdviceClient client = new DailyAdviceClient();
    client.go();
  }
}

import java.io.*;
import java.
public class DailyAdviceServer {
  String[] adviceList = {“Take smaller bites”, “Go for the tight jeans. No they do NOT make you look fat.”, “One word: inappropriate”, “Just for today, be honest. Tell your boss what you *really* think”, “You might want to rethink that haircut.”};
  public void go() {
    try {
      ServerSocket serverSock = new ServerSocket(4242);
      while(true) {
        Socket sock = serverSock.accept();
        PrintWriter writer = new PrintWriter(sock.getOutputStream());
        String advice = getAdvice();
        writer.println(advice);
        writer.close();
        System.out.println(advice);
      }
    }
    catch(IOException ex) {
      ex.printStackTrace();
    }
  } 
  // close go
  private String getAdvice() {
    int random = (int) (Math.random() * adviceList.length);
    return adviceList[random];
  }

  public static void main(String[] args) {
    DailyAdviceServer server = new DailyAdviceServer();
    server.go();
  }
}

线程(Thread)

  • 在需要同时执行多种任务的能力时,我们需要线程(Thread),建立一个Thread的实例便可启动一个新的线程,一个新的线程意味着一个独立的执行空间(Stack),当一个线程结束时,与之对应的堆栈也随之消失,所以我们的重点在于线程要执行的任务,Thread的构造方法中需要传入所要执行的任务

  • Thread需要任务,任务是实现过Runnable接口的实例,线程所要执行的任务在Runnable接口中的run()方法中,run()方法将是新的线程中第一个被调用的方法

  • 当有两个以上线程同时存在时,看起来是有好几件事情同时在发生,其实只是在一个进程内,执行动作在各个线程中非常快速的来回切换,线程的切换由线程调度器(scheduler)来安排,但你无法控制它的调度行为

  • 线程有三个状态:可执行(Runnable),执行中(Running),被挡住(Blocked),当启动线程时只是进入了可执行的状态,具体何时进入执行中状态由线程调度器决定,且这是一个你无法确定的行为,所以你不能依靠调度的特定行为来保证你执行的正确性。你唯一能做的就是使用Thread的sleep()方法来保证在指定的时间之前,昏睡中的线程一定不会被唤醒

  • 当线程中的run()方法执行完之后,线程就已进入dead状态,在dead状态下,Thread对象所建立的返回栈已经消失,Thread对象还在堆上,但是它实质上已经不是一个进程了,只是一个单纯的对象

  • 可以为线程取名字,并获取线程的名字

public class RunThreads implements Runnable {
  public static void main(String[] args) {
    RunThreads runner = new RunThreads();
    Thread alpha = new Thread(runner);
    Thread beta = new Thread(runner);
    alpha.setName(“Alpha thread”);
    beta.setName(“Beta thread”);
    alpha.start();
    beta.start();
  }
  public void run() {
    for (int i = 0; i < 25; i++) {
      String threadName = Thread.currentThread().getName();
      System.out.println(threadName + “ is running”);
    }
  }
}

并发性(Concurrency)

  • 并发性问题是指两个以上的线程存取单一对象的数据,也就是说两个不同的执行空间上的方法都在堆上对同一个对象执行getter或setter方法

  • 并发性问题的本质是在执行必须要连贯的一系列动作时,执行的线程在执行过程中被调度器安排从Running状态转为Runnable状态,而在此期间,又有其他线程对同一对象执行操作,由此引发数据问题。例如先检查余额,余额足够才能取钱,这两个动作本应连贯,但如果刚好检查完余额后线程进入Runnable状态,并被其他线程将钱取光,随后当前线程又返回至Running状态,并取钱,就会导致余额为负数。解决这个问题的本质就是让检查余额和取钱两个动作变为一个整体,在这个整体完成之前不能切换至其他线程,这样就算当前线程执行到一半变成Runnable状态也没有关系。将一个方法变为整体我们使用synchronized关键词

  • 花了一个多小时通过程序证明了我之前的理解是错误的!!!解决并发性问题的本质确实是将应该连贯完成的动作变成一个整体,但在整个整体完成之前不能有其他线程进入的说法是错误的,之前已经学习过线程的切换是由线程调度器来安排的,你根本无法控制。用synchronized来修饰方法当然也是不能控制在方法的执行过程中不切换至其他线程的!!通过代码的实验,对于synchronized的正确理解应该是这样的:在(线程1中)synchronized方法的执行过程中(也就是说我们想要的方法整体还没执行完时),当然很有可能会切换至其他线程(线程2),切换至线程2时,普通方法会正常执行,即使它对我们正在操作的对象进行操作,但如果在线程2中要操作synchronized方法时则无法执行。

  • 简单理解:对于同一对象进行操作时,不同线程中同样带synchronized修饰符的方法互斥,普通方法不受到影响。也可以已锁的概念来理解:每个对象都有一把锁,每把锁都只有一个钥匙,并且钥匙就在旁边。这把锁对于普通方法不起作用,只有在遇到synchronized方法时才会锁上,任何线程只有拿到了那把钥匙才能进入自身线程的synchronized方法里。当一个线程开始执行并遇上synchronized方法时,它会意识到它需要这个对象的锁的那把钥匙才能执行方法,如果此时钥匙在旁边,那这个线程会拿走钥匙,并进入synchronized方法。从那时起,这个线程会一直保留着钥匙,直到synchronized方法完成之后才将其归还。在这个线程执行过程中,其他任何线程都不能进入到对于此对象的synchronized方法中,因为它们拿不到钥匙。

  • 对象换成实例变量,上述也一样成立,如果计算机在更新时以多个步骤来执行,就需要将方法synchronized来应对丢失更新(lost update)的问题

  • 同步化是有代价的,程序会因为同步并行的问题而慢下来。同步化会强制线程排队等着执行方法,这与多线程的初衷相违背,同步化还会导致死锁(deadlock)产生,并且Java没有处理死锁的机制,所以原则上最好只做最少量的同步化

  • 类的静态方法也可以被同步化,因为除了每个对象都有一个锁外,类本身自己也有一个锁,如果同一个类有两个被同步化过的静态方法,线程需要取得类的锁的钥匙才能进入这些方法

  • 把在被保护的数据中将不可分割的一部分也给同步化是个好主意

  • 线程的优先级可以对调度器产生影响,但没有绝对保证。建议利用优先级来影响执行性能,但绝不能依靠优先级来保证正确性

  • 经实测,i++、i+=之类的计算机是按照两步计算的,所以它们需要为一个整体时,也要同步化!!

public class MainActivity extends AppCompatActivity {

    private Count count = new Count();

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        Thread alpha = new Thread(new Runnable() {
            @Override
            public void run() {
                Log.i("alpha thread", "run: ");
                alpahGo();
            }
        });

        Thread beta = new Thread(new Runnable() {
            @Override
            public void run() {
                Log.i("beta thread", "run: ");
                thetaGo();
                betaGo();
            }
        });

        alpha.start();
        beta.start();


    }

    public synchronized void alpahGo() {
        try {
            Log.i("alpha thread", "alpha sleep 6s");
            Thread.sleep(6000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        count.add(10);
        Log.i("alpha thread", count.countNumber + "alpha");
    }

    public synchronized void betaGo() {
        try {
            Log.i("beta thread", "beta sleep 3s");
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        count.add(10);
        Log.i("beta thread", count.countNumber + "beta");
    }

    public void thetaGo() {
        try {
            Log.i("beta thread", "theta sleep 2s");
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        count.add(10);
        Log.i("beta thread", count.countNumber + "theta");
    }
}

//运行结果
08-08 23:00:48.376 9810-10183/com.cuji.threadtest I/alpha thread: run: 
08-08 23:00:48.376 9810-10183/com.cuji.threadtest I/alpha thread: alpha sleep 6s
08-08 23:00:48.377 9810-10184/com.cuji.threadtest I/beta thread: run: 
08-08 23:00:48.377 9810-10184/com.cuji.threadtest I/beta thread: theta sleep 2s
08-08 23:00:50.377 9810-10184/com.cuji.threadtest I/beta thread: 10theta
08-08 23:00:54.377 9810-10183/com.cuji.threadtest I/alpha thread: 20alpha
08-08 23:00:54.379 9810-10184/com.cuji.threadtest I/beta thread: beta sleep 3s
08-08 23:00:57.379 9810-10184/com.cuji.threadtest I/beta thread: 30beta

//当只把两个调用synchronized之后,效果相同
            synchronized (this) {
                Thread.sleep(6000);
                count++;
            }
08-08 23:39:52.848 1210-2328/com.cuji.threadtest I/alpha thread: run: 
08-08 23:39:52.848 1210-2328/com.cuji.threadtest I/alpha thread: alpha sleep 6s
08-08 23:39:52.848 1210-2329/com.cuji.threadtest I/beta thread: run: 
08-08 23:39:52.848 1210-2329/com.cuji.threadtest I/beta thread: theta sleep 2s
08-08 23:39:54.849 1210-2329/com.cuji.threadtest I/beta thread: 1theta
08-08 23:39:58.849 1210-2328/com.cuji.threadtest I/alpha thread: 2alpha
08-08 23:39:58.850 1210-2329/com.cuji.threadtest I/beta thread: beta sleep 3s
08-08 23:40:01.851 1210-2329/com.cuji.threadtest I/beta thread: 3beta

你可能感兴趣的:(Head First Java 15 网络联机)