1、简介
多线程技术属于操作系统范围内的知识;
进程与线程
可以这么理解,一个应用程序就是一个进程,在一个进程中包含至少一个线程;进程就是线程的容器,真正工作、处理任务的是线程。
进程是操作系统分配资源的基本单位;线程是操作系统进行调度,时间分配的基本单位;
进程由内核对象和地址空间两部分构成,内核对象就是一小块记录进程信息的内存,只允许操作系统访问;地址空间就是存放数据和程序的空间;
2、多线程运行机制
对于单个CPU,在每个时间点只能只能执行一个线程,多线程的实现是基于对时间片的轮回机制的,即为每一个线程分配一个极短的时间片;时间片结束了就执行另一个线程,关于时间片的分配是由操作系统完成的,应用程序无法改变。应用程序可以设置线程的优先级。
关于线程的优先级,对于windows操作系统,线程的优先级是根据所在进程的优先级类和线程的相对优先级类进行确定的,方法如图:
注意,进程优先级一般不要设置为real-time会出问题的。
然后说一下优先级的作用,优先级越高,分配的时间片越长;优先级不等同于执行顺序,线程的执行顺序受操作系统的很多因素影响,优先级只可能是其中的一个因素,所以指望利用优先级控制线程的执行顺序是不现实的。
另外不同语言对优先级的规定也不同,而且优先级适合操作系统和平台相关的,利用优先级控制程序,在一个平台中运行很好,而在另一个平台中就可能会出问题。
3、线程的生命周期(基于Java语言)
new(创建线程)、run/start(执行线程)、block(阻塞线程)、wait(释放锁,并等待唤醒)、destroy(死亡)
对于run和start 尽量选择用start ,因为虽然run是线程真正的处理程序,但是调用run就相当于调用了一个方法,run不结束,是不会执行后面的语句的;而调用start会自动调用run,并且无论run是否运行完毕,都会执行后面的程序,这才是真正意义上启动一个线程;
4、基于Java 多线程的实现
基于Java实现多线程有两种方式,一个是通过继承thread类,另一个是继承runnable接口;两种方式本质上没有太多区别,因为thread类本身也是继承了Runnable接口的,只不过在进行了一些封装而已。两种方式在使用上是由区别的,如果一个类已经继承了其他类,便不能再集成thread类了,所以只能使用第二种方法。
继承于thread类的类,可以直接调用thread的方法;
大概是这样的
public class testThread1 extend Thread { /** *重写run方法 */ public void run() { } } //调用方式 testThread1 threadInstance = new testThread1 (); threadInstance.start(); //自动执行run方法
继承于runnable接口的类,必须实例化后,然后用实例化后的对象初始化一个thread对象;
大概是这样的
public class testThread2 implements Runnable { /** *重写run方法 */ public void run() { } } //调用方式 testThread2 threadInstance = new testThread2 (); Thread real_threaInstance = new Thread(threadInstance ); real_threaInstance .start(); //自动执行run方法
本质上都是对runnable的run方法进行重写,run中的函数就是线程要执行的函数。
5、基于Java的多线程通信
多线程之间的通信自己分类,分为三种:(1)共享变量 (2)消息机制 (3)管道机制(暂时不了解还)
(1)共享变量
各个线程共享进程的地址空间,所以就都可以访问进程中的公共地址空间,这样在公共地址空间中的变量就可以被各个线程访问;如果进程中的地址空间被分配给某个线程,那么其他线程就不可以访问了。
(2)消息机制
主要介绍sleep join wait notify synchronized的应用
synchronized:为obj对象加锁,调用方法 synchronized(obj){} //obj是一个对象 只要继承于object类即可 如: string类型 在Java中,每一个继承于object类的对象都有一个琐,当对这个对象枷锁之后,其他线程便不能够再访问这个对象了;
sleep:使线程休眠一定的时间,在休眠过程中不是释放锁
wait: 有两种调用方式 wait(1000) //指定休眠时间 wait() //不指定休眠时间,在休眠过程中 会释放锁;wait() 必须放在synchronized代码块中,因为只有加锁之后,才可以释放锁,释放的就是synchronized(obj) obj的锁;否则就会报异常;
notify:唤醒一个wait线程
从语法上来讲,wait() 与 notify()都必须位于synchronized块内;
对于一个对象的锁,wait和notify是这样配合的:首先加锁,调用锁的wait方法,阻塞线程,然后等待这个锁的notify方法被调用,被调用之后,停止wait,执行线程后面的程序。
子线程.join() 是阻塞主线程,等待子线程完成后再执行主线程;如:主线程中需要用到子线程的计算结果等情况
可以利用消息机制 控制 线程的执行顺序;
6、代码示例
以下代码实现了多个客户端与服务器端建立连接并发送消息的过程,至于服务器端对客户端发送消息,这一部分没有写好,因为多个线程在等待发送消息,所以发送的时候会随机选择一个客户端。
服务器端关键代码:
/** * * @method: jTextArea1KeyPressed() -by fjt * @TODO: 判断是否按下Enter键 * @param evt void */ private void txtAreaSendKeyPressed(java.awt.event.KeyEvent evt) { // TODO add your handling code here: if(evt.getKeyChar() == '\n') { synchronized (Sendobj) { Sendobj.notify(); } } } ServerSocket server = null; //服务器 int socketNo = 0; //客户端编号 /*** * * @method: btn_stopActionPerformed() -by fjt * @TODO: 关闭服务器 * @param evt void */ private void btn_stopActionPerformed(java.awt.event.ActionEvent evt) { // 关闭服务器 try { server.close(); MessageRecord.append("服务器关闭成功……\n"); } catch (Exception e) { // TODO: handle exception MessageRecord.append("服务器关闭失败……\n" + e + "\n"); } } /**** * * @method: btn_startActionPerformed() -by fjt * @TODO: 开启服务器 * @param evt void */ private void btn_startActionPerformed(java.awt.event.ActionEvent evt) { // 开启服务器 MessageRecord.append("服务器启动中……\n"); try { server = new ServerSocket(4700); MessageRecord.append("服务器启动成功,端口:4700\n"); } catch (Exception e) { MessageRecord.append("服务器启动失败……\n" + e + "\n"); } new Thread(new Runnable() { @Override public void run() { // TODO Auto-generated method stub creatSocket(server); } }).start(); } /*** * * @method: creatSocket() -by fjt * @TODO: 创建套接字函数 * @param server void */ Object Sendobj = new Object(); //发送线程监控器 public void creatSocket(final ServerSocket server) { try { final Socket socket = server.accept(); //等待客户端连接 如果有客户端连接,则产生一个socket 对象 final String currentSocket = Integer.toString(socketNo); socketNo++; MessageRecord.append("客户端 " + currentSocket + " 已连接\n"); //发送消息线程 Thread sendThread = new Thread(new Runnable() { @Override public void run() { // TODO Auto-generated method stub synchronized (Sendobj) { try { while(true) { PrintWriter sendMsg = new PrintWriter(socket.getOutputStream()); sendMsg.println(txtAreaSend.getText()); sendMsg.flush(); Sendobj.wait(); } } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } } } }); sendThread.start(); //如果调用run,就会阻碍后面的语句执行 //接受客户端消息线程 String line; try { final BufferedReader is = new BufferedReader(new InputStreamReader( socket.getInputStream())); PrintWriter os = new PrintWriter(socket.getOutputStream()); //接受客户端信息线程 new Thread(new Runnable() { @Override public void run() { // TODO Auto-generated method stub while (true) { try { MessageRecord.append("client " + currentSocket + " : " + is.readLine() + "\n"); } catch (Exception e) { // TODO: handle exception MessageRecord.append(currentSocket + "读取客户端消息异常\n" + e + "\n"); } } } }).start(); } catch (Exception e) { // TODO: handle exception MessageRecord.append(currentSocket + "通信出现异常\n" + e + "\n"); } //创建新的监听线程 new Thread(new Runnable() { @Override public void run() { // TODO Auto-generated method stub creatSocket(server); } }).start(); //开启发送消息线程 //sendThread.run(); } catch (Exception e) { // TODO: handle exception MessageRecord.append("客户端连接出现异常\n" + e + "\n"); } } /** * @param args the command line arguments */ public static void main(String args[]) { java.awt.EventQueue.invokeLater(new Runnable() { public void run() { new ServerWatch().setVisible(true); } }); }
客户端
package testSocketClient; import java.net.*; import java.io.*; public class client { public static void main(String[] args) { System.out.println("Hello Client!!"); 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 InputStreamReader(socket.getInputStream())); 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(); //关闭Socket输出流 is.close(); //关闭Socket输入流 socket.close(); //关闭Socket } catch(Exception e) { System.out.println("错误信息:" + e); } } }
7、多线程编程中易出现的问题
(1)资源共享,但没有处理好线程同步,读取脏数据
(2)资源互斥,导致死锁问题
(3)线程同步,即线程的执行顺序