java多线程基础总结

java多线程基础总结

最近在看《Java网络编程》这本书,刚看完线程部分。书里这部分内容虽然不算多,但是十分精炼,总结的很好。把Java多线程的各个方法都做了介绍。之前看过《Java编程思想》中关于多线程的讲解,感觉更偏重于多线程编程的技巧和注意事项。拿来入门的话还是比较晦涩,包括对IO流的讲解也是一样。这本书相比之下就更加简单粗暴,更易入门~~~~

启动新线程的方法

在Java虚拟机中启动新线程,有三种方法:

1.继承Thread类

import java.io.FileInputStream;
import java.io.IOException;
import java.security.DigestInputStream;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;

import javax.xml.bind.DatatypeConverter;
/*
 1. 书中示例,每个线程根据其成员fileName表示的文件路径读取文件并生成摘要,输出到控制台。
 */
public class DigestThread extends Thread {

    private String fileName;

    public DigestThread(String fileName) {
        this.fileName = fileName;
    }

    @Override
    public void run() {
        try {
            FileInputStream in = new FileInputStream(fileName);
            MessageDigest sha = MessageDigest.getInstance("SHA-256");
            DigestInputStream din = new DigestInputStream(in, sha);
            while (din.read() != -1);
            din.close();
            byte[] digest = sha.digest();

            StringBuilder result = new StringBuilder(fileName);
            result.append(": ");
            result.append(DatatypeConverter.printHexBinary(digest));
            System.out.println(result);
        } catch (IOException ex) {
            System.err.println(ex);
        } catch (NoSuchAlgorithmException ex) {
            System.err.println(ex);
        }
    }

    public static void main(String[] args) {
        for (String fileName : args) {
            Thread t = new DigestThread(fileName);
            t.start();
        }
    }
}

2.实现Runnable接口

由于Java的单继承特性,如果希望线程类继承其他类以实现功能可以选择不继承Tread类而实现Runnable接口。

import java.io.FileInputStream;
import java.io.IOException;
import java.security.DigestInputStream;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;

import javax.xml.bind.DatatypeConverter;

/*
 1. 书中示例,每个线程根据其成员fileName表示的文件路径读取文件并生成摘要,输出到控制台。
 */
public class DigestRunnable implements Runnable {

    private String fileName;

    public DigestRunnable(String fileName) {
        this.fileName = fileName;
    }

    @Override
    public void run() {
        try {
            FileInputStream in = new FileInputStream(fileName);
            MessageDigest sha = MessageDigest.getInstance("SHA-256");
            DigestInputStream din = new DigestInputStream(in, sha);
            while (din.read() != -1);
            din.close();
            byte[] digest = sha.digest();

            StringBuilder result = new StringBuilder(fileName);
            result.append(": ");
            result.append(DatatypeConverter.printHexBinary(digest));
            System.out.println(result);
        } catch (IOException ex) {
            System.err.println(ex);
        } catch (NoSuchAlgorithmException ex) {
            System.err.println(ex);
        }
    }

    public static void main(String[] args) {
        for (String fileName : args) {
            DigestRunnable dr = new DigestRunnable(fileName);
            Thread t = new Thread(dr);
            t.start();
        }
    }
}

3.实现Callable接口

Callable接口是Java 1.5之后引入的新接口。它不同于前两者的是Callable接口执行线程可以返回一个类型的对象。

import java.util.concurrent.Callable;

/*
 * 书中示例,用于选出传入的data数组中的最大值
 */
public class FindMaxTask implements Callable<Integer> {

    private int[] data;
    private int start;
    private int end;

    FindMaxTask(int[] data, int start, int end) {
        this.data = data;
        this.start = start;
        this.end = end;
    }

    @Override
    public Integer call() throws Exception {
        // TODO Auto-generated method stub
        int max = Integer.MIN_VALUE;
        for (int i = start; i < end; i++) {
            if (data[i] > max) max = data[i];
        }
        return max;
    }

}
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

/*
 * 书中示例,将一个数组分成前后两部分,分别调用FindMaxTask类启动新线程计算各自最大值,再比较得出大数组的最大值
 */
public class MultithreadedMaxFinder {

    public static int max(int[] data) throws InterruptedException, ExecutionException {
        if (1 == data.length) {
            return data[0];
        } else if (0 == data.length) {
            throw new IllegalArgumentException();
        }

        FindMaxTask task1 = new FindMaxTask(data, 0, data.length / 2);
        FindMaxTask task2 = new FindMaxTask(data, data.length / 2, data.length);

        ExecutorService service = Executors.newFixedThreadPool(2);

        Future<Integer> future1 = service.submit(task1);
        Future<Integer> future2 = service.submit(task2);

        return Math.max(future1.get(), future2.get());
    }

}

从线程返回信息

Callable接口线程直接返回值

实现Callable接口方式实现的线程可以以Object类型返回任意类型的一个对象(获取对象后可根据需要将其转换为指定的类型,或者使用泛型接口,直接返回指定类型对象)。

Callable接口除了可以返回值以外,同前两种方法还有一个很重要的不同点。
1. 实现Callable接口的类不能通过Thread类的start方法启动线程,智能通过ExecutorService中的submit方法提交到线程池,有线程池启动线程。
2. 需调用future类的get方法获取线程的返回值,而且如果调用的线程还没有执行完成,那么get方法会阻塞当前线程直到获得返回值。

回调方式得到线程信息

前两种方式启动的线程要返回信息最好的办法是使用回调方法。即类似于Java Swing中监听器机制的做法,在线程启动前使其持有对该线程运行结果感兴趣的对象的引用,在线程运行过程中得到结果后主动调用持有的对象中的方法,将结果传出。示例代码如下:

import java.io.FileInputStream;
import java.io.IOException;
import java.security.DigestInputStream;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;

import javax.xml.bind.DatatypeConverter;

public class CallbackDigest implements Runnable {

    private String fileName;

    public CallbackDigest(String fileName) {
        this.fileName = fileName;
    }

    @Override
    public void run() {
        try {
            FileInputStream in = new FileInputStream(fileName);
            MessageDigest sha = MessageDigest.getInstance("SHA-256");
            DigestInputStream din = new DigestInputStream(in, sha);
            while (din.read() != -1);
            din.close();
            byte[] digest = sha.digest();
            //此处调用了CallbackDigestUserInterface类的静态方法,因此无需持有其对象
            CallbackDigestUserInterface.receiveDigest(digest, fileName);
        } catch (IOException ex) {
            System.err.println(ex);
        } catch (NoSuchAlgorithmException ex) {
            System.err.println(ex);
        }
    }
}

上面线程计算结束后调用的方法:

import javax.xml.bind.DatatypeConverter;

public class CallbackDigestUserInterface {

    public static void receiveDigest(byte[] digest, String name) {
        StringBuilder result = new StringBuilder(name);
        result.append(": ");
        result.append(DatatypeConverter.printHexBinary(digest));
        System.out.println(result);
    }

    public static void main(String[] args) {
        for (String fileName : args) {
            CallbackDigest cb = new CallbackDigest(fileName);
            Thread t = new Thread(cb);
            t.start();
        }
    }
}

轮询方式得到线程信息

此方法需将线程类中感兴趣的信息声明为成员变量,通过调用其get方法得到值。但是需不断地循环检测返回值是否为空,不推荐使用。

线程的同步

主要通过synchronized关键字实现。这里有一点需要明确,引用书中原文:

Java没有提供任何方法来阻止其他线程使用共享资源。它只能防止对同一个对象同步的其他线程使用这个公共资源

也就是说:同步锁只对代码中声明了同步的代码有用。不能阻止未声明同步的代码使用被锁的资源。试验代码:

import java.util.Date;

public class PublicSource {

    private String str = "unlock";

    public String getStr() {
        return str;
    }

    public static void main(String[] args) {
        // TODO Auto-generated method stub
        PublicSource ps = new PublicSource();
        ThreadOne to = new ThreadOne(ps);
        ThreadTwo tt = new ThreadTwo(ps);
        Thread t1 = new Thread(to);
        Thread t2 = new Thread(tt);
        t1.start();
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        t2.start();
    }

}
class ThreadOne implements Runnable {

    private PublicSource ps;

    public ThreadOne(PublicSource ps) {
        this.ps = ps;
    }

    @Override
    public void run() {
        // TODO Auto-generated method stub
        synchronized (ps) {
            System.out.println("ThreadOne : " + ps.getStr() + " time: " + new Date());
            try {
                Thread.sleep(5000);
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }

    }
}

class ThreadTwo implements Runnable {

    private PublicSource ps;

    public ThreadTwo(PublicSource ps) {
        this.ps = ps;
    }

    @Override
    public void run() {
        // TODO Auto-generated method stub
        synchronized (ps) {
            System.out.println("ThreadTwo : " + ps.getStr() + " time: " + new Date());
        }

    }
}

可比较将ThreadTwo中run方法里的synchronized声明去掉之后和去掉之前的运行结果。

同步的替代方式

synchronized修饰符会影响JVM的性能,增加死锁发生的可能性,而且,多线程程序不同于单线程程序,排错十分困难。因此在遇到同步问题时应先考虑是否有替代方案。
1. 尽量使用局部变量而不是类的成员变量;
2. 基本数据类型也可以在线程中安全的修改;
3. 可将类型设置为不可变类(类似于String类型的设计);
4. 将非线程安全的类用作为线程安全的类的私有字段

死锁

  1. 要防止死锁,最重要的技术是避免不必要的同步;
  2. 如果多个对象需要操作相同的共享资源集,要确保以相同的顺序请求这些资源。

线程的调度

  1. 抢占式线程调度
    线程是否暂停由java虚拟机决定。
  2. 协作式线程调度
    java虚拟机等待正在运行的线程自己暂停。

所有Java虚拟机都确保在不同优先级之间使用抢占式线程调度。
在协作式线程调度机制中,应确保线程自身定期暂停。线程自身暂停的方式:

  • I/O阻塞 不释放已拥有的锁
  • 同步对象阻塞 不释放已拥有的锁
  • 放弃:Thread.yield() 不释放已拥有的锁
  • 休眠:Thread.sleep() 不释放已拥有的锁
  • 连接另一个线程:如线程B要在线程A运行结束后执行,需在线程B中调用线程A的join方法
  • 等待一个对象:wait方法,此方法是Object类的方法,会释放已拥有的当前对象的锁。因此只能对已拥有的锁对象调用
  • 结束
  • 被更高优先级线程抢占

线程池

这部分内容书中讲解很简略,等查阅其他资料之后再补充吧。。。

道行尚浅,欢迎拍砖!!

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值