深入理解Java多线程:概念与实现

需积分: 35 37 下载量 38 浏览量 更新于2024-07-18 4 收藏 977KB PDF 举报
"此资源详细介绍了Java多线程的相关概念,包括线程的创建、线程间的同步与通信以及线程阻塞机制。" 在Java编程中,多线程是一个关键特性,它使得程序能够同时执行多个任务,从而提高系统效率。Java提供了多种创建线程的方法,包括继承Thread类、实现Runnable接口以及使用ExecutorService。 一、线程概念 1. 进程:是操作系统分配资源的基本单位,同一进程中的线程共享进程的所有资源。每个进程都有自己的独立内存空间,包括堆和栈。 2. 线程:是CPU调度的最小单位,是进程中执行运算的最小单元。线程之间共享进程的内存空间,但拥有各自的程序计数器、栈和一部分寄存器状态。 3. 并行和并发:并行是指多个线程在同一时刻运行,通常发生在多核CPU环境下;并发则是在单个CPU核心上通过时间片轮转的方式,让多个线程看起来像是同时运行。 二、线程创建 Java中创建线程主要有以下三种方式: 1. 继承Thread类:重写run()方法,然后创建Thread对象并调用start()启动线程。 2. 实现Runnable接口:实现run()方法,然后将Runnable对象作为参数传递给Thread构造函数,创建Thread对象并调用start()。 3. 使用ExecutorService:通过Executor框架,如ThreadPoolExecutor,可以更灵活地管理和控制线程。 三、线程同步与通信 Java提供了多种线程同步机制,防止多个线程对共享资源的不正确访问,如synchronized关键字、Lock接口、Semaphore信号量等。此外,线程间通信可以通过wait()、notify()和notifyAll()方法,或者使用BlockingQueue阻塞队列实现。 四、线程阻塞 线程阻塞是指线程在等待某个条件满足或资源释放时暂停执行的状态。Java提供了多种阻塞机制,如sleep()方法使线程休眠,join()方法等待其他线程结束,wait()方法让线程进入等待池,以及wait/notify机制实现线程间的协作。 五、线程调度 Java线程调度是由JVM和操作系统的线程调度器共同完成的。线程的优先级可以影响调度,但具体调度策略依赖于平台。线程可以设置为守护线程(daemon thread),当所有非守护线程结束时,整个Java应用会退出。 六、线程安全问题 线程安全问题主要包括竞态条件、死锁、活锁和饥饿等。Java提供了一些内置的线程安全类,如Atomic类、Collections.synchronizedXXX()方法包装的集合等,来帮助开发者解决这些问题。 Java多线程技术是构建高效并发应用的基础,理解和掌握这些概念及机制对于开发高性能的Java应用程序至关重要。通过合理地设计和使用线程,可以有效地利用系统资源,提高软件的响应速度和并发处理能力。
2009-12-24 上传
java多线程机制: 例子 1 public class Example1 { static Lefthand left;static Righthand right; public static void main(String args[]) { left=new Lefthand(); //创建两个线程。 right=new Righthand(); left.start(); right.start(); } } class Lefthand extends Thread { public void run() { for(int I=0;I<=5;I++) { System.out.println("I am a student"); try{sleep(500);} catch(InterruptedException e){} } } } class Righthand extends Thread { public void run() { for(int I=0;I<=5;I++) { System.out.println("I am oookkk"); try{sleep(300);} catch(InterruptedException e){} } } } 在上述例子中,我们在main主线程中创建了两个新的线程lefthand和righthand。当lefthand调用start()开始运行时,类Lefthand中的run()将自动被执行。 我们来分析一下上面程序的输出结果。Left线程首先开始执行,这时Lefthand类中的run方法开始执行,输出”I am a student”后,left主动“休息”500毫秒,让出了CPU。这时正在排队等待CPU的right线程的run方法马上被执行,输出“I am ookk”,right在主动让出CPU300毫秒后又来排队等待CPU服务,这时right发现left还没有“醒来”,即没有来排队抢占CPU,因此left的run方法被执行,又输出“I am oookkk“… …。程序的执行结果是: E:\dd>java Example1 I am student I am oookkk I am oookkk I am student I am oookkk I am oookkk I am student I am oookkk I am student I am oookkk I am student I am student 2.实现Runnable接口 例子 2 import java.applet.*; import java.awt.*; public class Example2 extends java.applet.Applet implements Runnable { Thread circleThread; public void start() { if (circleThread==null) { circleThread=new Thread(this); circleThread.start(); } } public void run() { while(circleThread !=null) { repaint(); try{ circleThread.sleep(1000); } catch(InterruptedException e){} } } public void paint(Graphics g) { double i=Math.random(); if(i<0.5) g.setColor(Color.red); else g.setColor(Color.blue); g.fillOval(100,100,(int)(100*i),(int)(100*i)); } public void stop() { circleThread.yield(); circleThread=null; } } 在上述例子2中,我们在小程序这个主线程中用构造方法Thread(this)创建了一个新的线程。This代表着小程序作为这个新的线程的目标对象,因此我们的小程序必须为这个新创建的线程实现Runnable接口,即小程序利用Runnable接口为其中创建的这个新线程提供run()方法,给出该线程的操作。 首先,在小程序的start()方法中构造了一个名为circleThread的线程并调用线程类的start()方法来启动这一线程,即在小程序的主线程中又开始了一个线程:circleThread。下面的语句建立了一个新的线程: circlethread =new Thread(this); 其中this作为该线程的目标对象,它必须实现Runnable接口。线程被启动以后,自动调用目标对象的run()方法,除非线程被停止。在run()方法的第十一中,Applet重绘本身,然后睡眠1秒,同时要捕获异常事件并进行处理。 如果你离开这一页,程序将调用stop()方法,将线程置空。当你返回时,又会创建一个新的线程。在具体应用中,采用哪种方法来构造线程体要视具体情况而定。通常,当一个新的线程已继承了另一个类,而想在该线程中创建一个新的线程时,就应该用第二种方法来构造,即实现Rennable接口。 需要理解的是,我们的小应用程序实际上是浏览器的一个线程,这个线程由浏览器启动执行,浏览器自动调用执行小程中的init()、start()方法等。因此我们要创建一个新的线程最好把新线程的启动放在小程序的start()方法中。 下面的例子3是一个应用程序,这个应用程序在创建窗口的同时又创建了一个新的线程,该线程负责让窗口中的一个按钮改变它的大小。 例子 3 import java.awt.*; import java.awt.event.*; public class Example3 { public static void main(String args[]) { Mywin win=new Mywin(); win.pack(); } } class Mywin extends Frame implements Runnable { Button b=new Button("ok"); int x=5; Thread bird=null; Mywin() { setBounds(100,100,120,120); setLayout(new FlowLayout()); setVisible(true); add(b); b.setBackground(Color.green); addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent e) {System.exit(0);} }); bird=new Thread(this); //创建一个新的线程,窗口做目标对象, //替线程bird实现接口Runnable。 bird.start(); //在创建窗口时又开始了线程dird. } public void run() { while(true) { x=x+1; if(x>100) x=5; b.setBounds(40,40,x,x); try{bird.sleep(200);} catch(InterruptedException e){} } } } 滚动字幕线程。 例子 4 import java.applet.*; import java.awt.*; public class Example4 extends java.applet.Applet implements Runnable { int x=0; Thread Scrollwords=null; public void init() { setBackground(Color.cyan); setForeground(Color.red); setFont(new Font("TimesRoman",Font.BOLD,18)); } public void start() { if(Scrollwords==null) { Scrollwords=new Thread(this); Scrollwords.start(); } } public void run() { while (Scrollwords!=null) { x=x+5; if(x>500) x=0; repaint(); try{Scrollwords.sleep(80);} catch(InterruptedException e){} } } public void paint(Graphics g) { g.drawString("欢 迎 使 用 字 典 ",x ,80); } public void stop() { Scrollwords.yield(); Scrollwords=null; } } 带滚动字幕的小字典。 例子 5 import java.applet.*; import java.awt.*; import java.awt.event.*; public class Example5 extends Applet implements ActionListener,Runnable { TextField text1,text2; int x=0; Thread Scrollwords=null; public void init() { setBackground(Color.cyan); setForeground(Color.red); setFont(new Font("TimesRoman",Font.BOLD,18)); text1=new TextField(10); text2=new TextField(10); add(new Label("输入一个英文单词:")); add(text1); add(new Label("汉语意思:")); add(text2); text1.addActionListener(this); } public void start() { if(Scrollwords==null) { Scrollwords=new Thread(this); Scrollwords.start(); } } public void run() { while (Scrollwords!=null) { x=x+5; if(x>500) x=0; repaint(); try{Scrollwords.sleep(80);} catch(InterruptedException e){} } } public void paint(Graphics g) { g.drawString("欢 迎 使 用 字 典 ",x ,120); } public void stop() { Scrollwords.yield(); Scrollwords=null; } public void actionPerformed(ActionEvent e) { if((e.getSource()==text1)&&(text1.getText().equals("boy"))) { text2.setText("男孩"); } else if((e.getSource()==text1)&&(text1.getText().equals("sun"))) { text2.setText("太阳"); } else { text2.setText("没有该单词"); } } } 下面是一个左手画圆右手画方的例子。我们在主线程中创建了两个线程:left、right,其中一个负责画圆,另一个负责画方。在这个例子中我们使用了容器类的方法getGraphics()来获取一个Graphics对象(可以理解为一个画笔)。 例子 6 (效果如图1所示) 图1 双线程绘画程序 import java.applet.*; import java.awt.*; import java.awt.event.*; public class Example6 extends Applet implements Runnable { Thread left,right; Graphics mypen; int x,y; public void init() { left=new Thread(this); right=new Thread(this); x=10; y=10; mypen=getGraphics(); } public void start() { left.start(); right.start(); } public void run() { while(true) if (Thread.currentThread()==left) { x=x+1; if(x>240) x=10; mypen.setColor(Color.blue); mypen.clearRect(10,10,300,100); mypen.drawRect(10+x,10,50,50); try{left.sleep(60);} catch(InterruptedException e){} } else if(Thread.currentThread()==right) { y=y+1; if(y>240) y=10; mypen.setColor(Color.red); mypen.clearRect(10,110,300,100); mypen.drawOval(10+y,110,50,50); try{right.sleep(60);} catch(InterruptedException e){} } } public void stop() { left=null; right=null; } }
2014-05-27 上传
1.得到服务器下载文件的大小,然后在本地设置一个临时文件和服务器端文件大小一致 a)获得访问网络地址 b)通过URL对象的openConnection()方法打开连接,返回一个连接对象 c)设置请求头 i.setRequestMethod ii.setConnectTimeout iii.setReadTimeout d)判断是否响应成功 e)获取文件长度(getContentLength()) f)随机访问文件的读取与写入RandomAccessFile(file, mode) g)设置临时文件与服务器文件大小一致(setLength()) h)关闭临时文件 2.计算出每个线程下载的大小(开始位置,结束位置) a)计算出每个线程下载的大小 b)for循环,计算出每个线程的开始、结束位置 c)最后一个线程处理 3.每创建好一次就要开启线程下载 a)构造方法 b)通过URL对象的openConnection()方法打开连接,返回一个连接对象 c)设置请求头 i.setRequestMethod ii.setConnectTimeout d)判断是否响应成功(206) e)获取每个线程返回的流对象 f)随机访问文件的读取与写入RandomAccessFile(file, mode) g)指定开始位置 h)循环读取 i.保存每个线程下载位置 ii.记录每次下载位置 iii.关闭临时记录位置文件 iv.随机本地文件写入 v.记录已下载大小 i)关闭临时文件 j)关闭输入流 4.为了杀死线程还能继续下载的情况下,从本地文件上读取已经下载文件的开始位置 a)创建保存记录结束位置的文件 b)读取文件 c)将流转换为字符 d)获取记录位置 e)把记录位置赋给开始位置 5.当你的n个线程都下载完毕的时候我进行删除记录下载位置的缓存文件 a)线程下载完就减去 b)当没有正在运行的线程时切文件存在时删除文件