多线程笔记
创建线程
1、通过Runnable创建线程
实现Runnable接口(它只有一个void run()方法),然后将此类实例对象传给Thread对象,再调用Thread对象的start()方法
class MyThread implements Runnable{
…………..
public void run(){
…………..
}
}
在客户端:
MyThread mt = new MyThread();
Thread thread = new Thread(mt);或者Thread thread = new Thread(mt,name);
thread.start();
其中thread.start()方法调用mt是run()方法
2、通过Thread创建线程
继承Thread,重写run()方法
class MyThread extends Thread{
…………..
public void run(){
.........
}
}
在客户端:
MyThread myThread = new MyThread();
myThread.start();
其中myThread.start()调用重写的run()方法。
线程同步
当一个对象或者方法被多个线程访问或者共享时,此对象或者方法就必须同步,当一个线程进入同步方法或者同步代码块中时,其他线程就无法访问共享资源了。
(1)同步方法:
synchronized 返回类型 method(argList){
//同步方法体
}
一个线程执行同步方法是时,就获得了对象的监视器,且对象会加锁,其他线程无法对此对象执行同步方法,除非该线程执行完毕或者调用了wait()方法释放锁。
(2)同步代码块
synchronized(引用对象){
//同步语句
}
一旦一个线程进入同步代码块,线程就获得锁,引用对象就会被锁住,其他线程无法执行该对象的方法,除非同步代码块执行结束。
线程间通信
当线程1执行同步代码快时,它需要访问其他资源,但是资源暂时无法访问,如果线程1一直等待资源,而它占有对象的锁,那么其他线程就会一直等待。一个好的方式是,让线程1进入等待状态,等到资源可用时,用另外一个线程唤醒它。
在同步方法或者同步代码块中调用wait()进入等待,让其他线程执行;
在同步方法或者同步代码块中调用notify()唤醒等待线程,让其继续执行;
注意:线程间通信都是在同一个线程中进行,且两个方法都是Object对象的。
class SynObj{
private boolean ready = false;
synchronized void waitFor(){
String thrdName = Thread.currentThread().getName();
System.out.println(thrdName+"进入waitFor()方法。");
System.out.println(thrdName+"调用wait()方法,等待唤醒。");
try{
while(!ready)wait();
}catch(InterruptedException e){
System.out.println("线程中断");
}
System.out.println(thrdName+"收到通知,恢复执行。");
}
synchronized void goHead(){
String thrdName = Thread.currentThread().getName();
System.out.println(thrdName+"进入goHead()方法,调用notify()方法唤醒MyThread。");
ready = true ;
notify();
}
}
class MyThread implements Runnable{
SynObj synObj;
public MyThread(String name,SynObj so){
synObj = so;
new Thread(this,name).start();
}
public void run(){
synObj.waitFor();
}
public static void main(String[] args) {
System.out.println("Hello World!");
}
}
public class MainClass{
public static void main(String[] args) {
try{
SynObj so = new SynObj();
new MyThread("MyThread",so);
for(int i=0;i<50;i++){
Thread.sleep(500);
System.out.print(".");
}
System.out.println();
so.goHead();
}catch(InterruptedException e){
System.out.println("主线程线程中断");
}
}
}
线程暂停、恢复及停止
Java本身的suspend()、resume()、stop()方法可能导致死锁。我们可以通过两个boolean变量表示线程状态,根据值来决定是否暂停、等待、结束。
class MyThread2 implements Runnable{
Thread thrd ;
private volatile boolean suspended;//是否暂停线程
private volatile boolean stopped;//是否停止线程
public MyThread2(String name){
thrd = new Thread(this,name);
suspended= false;
stopped = false ;
thrd.start();
}
public void run(){
System.out.println(thrd.getName()+" 启动。");
try{
for(int i=0;i<1000;i++){
System.out.print(".");
Thread.sleep(400);
synchronized (this){
while(suspended)wait();
if(stopped)break;
}
}
}catch(InterruptedException e){
System.out.println(thrd.getName()+"异常程线程中断");
}
System.out.println(thrd.getName()+"线程退出。");
}
synchronized void stopThread(){
stopped = true ;
suspended = false ;
notify();
}
synchronized void suspendThread(){
suspended = true ;
}
synchronized void resumeThread(){
suspended = false ;
notify();
}
}
public class MainClass2{
public static void main(String[] args) {
MyThread2 mt = new MyThread2("MyThread2");
try{
Thread.sleep(3000);//主线程睡眠
System.out.println("/n暂停MyThread2线程");
mt.suspendThread();
Thread.sleep(3000);//主线程睡眠
System.out.println("/n恢复MyThread2线程");
mt.resumeThread();
Thread.sleep(3000);//主线程睡眠
System.out.println("/n停止MyThread2线程");
mt.stopThread();
}catch(InterruptedException e){
System.out.println("主线程线程中断");
}
}
}
守护线程
Java中有用户线程和守护线程。守护线程一般运行在后台提供服务,直到程序结束,它会自动终止,不需要手工停止。
如果Thread实例是由守护线程创建的,那么此实例也是守护线程,如果是由用户线程创建的,那么就是用户线程,但是可以通过调用它的setDaemon()将它设置成守护线程。注意此方法必须在线程启动前调用才有效。可以通过isDaemon()查看线程是否为守护线程。
例子:通过守护线程实现行程提醒的功能
import java.util.*;
class Reminder implements Runnable{
private Calendar reminderTime;//提醒时间
private String message ;//提醒消息
public Reminder(String msg,int delay){//delay:延迟的时间(秒)
message = msg;
reminderTime = Calendar.getInstance();
reminderTime.add(Calendar.SECOND,delay);
System.out.println("设置提醒:"+ message);
Thread thrd = new Thread(this);
thrd.setDaemon(true);
thrd.start();
}
public Reminder(String msg,Calendar cal){//delay:延迟的时间(秒)
message = msg;
reminderTime = cal;
System.out.println("设置提醒:"+ message);
Thread thrd = new Thread(this);
thrd.setDaemon(true);
thrd.start();
}
public void run(){
try{
for(;;){
Calendar curTime = Calendar.getInstance();
if(curTime.compareTo(reminderTime) >= 0){
System.out.println("/n"+message+"/n");
break ;
}
}
Thread.sleep(1000);
}catch(InterruptedException e){
System.out.println("Reminder异常中断");
}
}
}
public class MainClass3{
public static void main(String[] args) {
Reminder mt = new Reminder("给XX打电话",10);
Reminder mt2 = new Reminder("与XX见面",new GregorianCalendar(2011,5,20,10,39));
for(int i =0;i<50;i++){
try{
Thread.sleep(1000);
}catch(InterruptedException e){
System.out.println("主线程异常中断");
}
System.out.print(".");
}
System.out.println("主线程执行完毕");
}
}
线程中断
要中断一个线程,可以在另一个线程中调用那个线程的interrupt()方法;
一个例子:多线程下载
import java.io.*;
import java.net.*;
public class MulThreadDownload {
public static void main(String[] args) {
String path = "http://softdl1.tech.qq.com/soft/38/QQPinyin_Setup_43_1080.exe";//下载的文件地址
try {
new MulThreadDownload().download(path, 3);
} catch (Exception e) {
e.printStackTrace();
}
}
//从路径中获取文件名称
public static String getFilename(String path){
return path.substring(path.lastIndexOf('/')+1);
}
//下载文件的方法;path:下载路径;threadsize:线程数
public void download(String path, int threadsize) throws Exception{
URL url = new URL(path); //根据路径创建URL对象
HttpURLConnection conn = (HttpURLConnection)url.openConnection();
conn.setRequestMethod("GET");//GET
conn.setConnectTimeout(10 * 1000);//连接超时时间
int filelength = conn.getContentLength();//通过连接获取要下载的文件的长度
String filename = getFilename(path);//从路径中获取文件名称
File saveFile = new File(filename);//创建下载的文件(相对路径,文件名相同)
RandomAccessFile accessFile = new RandomAccessFile(saveFile, "rw");//可读写的随机访问文件
accessFile.setLength(filelength);//设置本地文件的长度和下载文件相同
accessFile.close();
//计算每条线程下载的数据长度
int block = filelength%threadsize==0? filelength/threadsize : filelength/threadsize+1;//每条线程下载的数据长度
for(int threadid=0 ; threadid < threadsize ; threadid++){
new DownloadThread(url, saveFile, block, threadid).start();//启动各个线程
}
System.out.println( "下载完成");
}
//内部线程类
private class DownloadThread extends Thread{
private URL url;
private File saveFile;
private int block;//每条线程下载的数据长度
private int threadid;//线程id
public DownloadThread(URL url, File saveFile, int block, int threadid) {
this.url = url;
this.saveFile = saveFile;
this.block = block;
this.threadid = threadid;
}
public void run() {
//线程开始位置:线程id*每条线程下载的数据长度
//线程结束位置:(线程id +1)*每条线程下载的数据长度-1
int startposition = threadid * block;
int endposition = (threadid + 1 ) * block - 1;
try {
RandomAccessFile accessFile = new RandomAccessFile(saveFile, "rw");
accessFile.seek(startposition);//设置从什么位置开始写入数据
HttpURLConnection conn = (HttpURLConnection)url.openConnection();
conn.setRequestMethod("GET");
conn.setConnectTimeout(10 * 1000);
conn.setRequestProperty("Range", "bytes="+ startposition+ "-"+ endposition);//设置请求Range属性
InputStream inStream = conn.getInputStream();
byte[] buffer = new byte[1024];
int len = 0;
while( (len=inStream.read(buffer)) != -1 ){
accessFile.write(buffer, 0, len);//通过accessFile向文件写数据
}
inStream.close();
accessFile.close();
} catch (Exception e) {
System.out.println("出现异常");
}
}
}
}