多线程是计算机执行并发任务的一个方案,随着现在多核CPU流行起来,多线程能够更好的利用其算力资源。一般来说进程是操作系统对程序的的管理方式,而线程一般是系统内核调度的基本单位,在一个进程中可以创建多个线程。操作系统一般使用时间片轮转调度算法来调度线程。关于操作系统线程管理的内容,这里不详述。
创建线程
在Java中,可以用两种方法来创建线程,直接继承Thread类实现run()方法或者实现Runnable接口并用Thread启动。
//继承Thread的方式
public static void main(String[] args) {
new MyThread().start();
System.out.println("end main:" + System.currentTimeMillis());
}
public static class MyThread extends Thread{
@Override
public void run() {
//新线程开始
super.run();
try {
Thread.sleep(2 * 1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("end thread: " + System.currentTimeMillis());
}
}
//实现Runnable的方式,lamabda写法需要JDK8支持
public static void main(String[] args) {
new Thread(() -> {
//新线程开始
try {
Thread.sleep(2 * 1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("end thread: " + System.currentTimeMillis());
}).start();
System.out.println("end main:" + System.currentTimeMillis());
}
线程同步
使用了线程可以达到并发执行的效果,CPU资源的利用率也大大提高。但是这也面临许多问题,最主要的的就是并发下的资源竞争。比如说当一个线程在写一个日志文件时,另一个线程也要往里面写,这个时候就会竞争这个文件资源。操作系统的处理办法是让先访问文件的线程获得操作权限,后面的线程排队(挂起它或者告诉它获取失败)。线程在挂起的状态中会暂停执行,直到等待的资源能够获得,操作系统会再次调度到它。
那么Java程序中的代码块和对象该怎么处理同步呢, Java提供了synchronized 和一些列的锁(Lock)类来保证资源的同步。
public static void main(String[] args) {
new Thread(() -> {
syncMethod();
System.out.println("end thread: " + System.currentTimeMillis());
}, "my_thread").start();
syncMethod();
System.out.println("end main:" + System.currentTimeMillis());
}
/**
* 同步方法,使用synchronized关键字
*/
public static synchronized void syncMethod(){
try {
Thread.sleep(2 * 1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("curr thread:" + Thread.currentThread().getName());
}
输出
curr thread:main
end main:1512129765291
curr thread:my_thread
end thread: 1512129767305
下面是有个使用锁的例子
Lock lock = new ReentrantLock();
new Thread(() -> {
//syncMethod();
//System.out.println("end thread: " + System.currentTimeMillis());
lock.lock();
try {
Thread.sleep(2 * 1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("end thread:" + System.currentTimeMillis());
lock.unlock(); //释放锁
}, "my_thread").start();
//syncMethod();
lock.lock();
try {
Thread.sleep(2 * 1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("end main:" + System.currentTimeMillis());
lock.unlock(); //释放锁
使用锁的时候要注意的是避免死锁,即线程A在等待B持有的资源,而此时B也在等待A持有的资源,这样就造成了相互等待谁都无法继续下去。
还有一种情况是线程需要去获取自身已经得到的资源,如果不加以判断也会线程执行不下去。这样就产生了可重入锁的概念,即线程可以重复获得锁,在Java中synchronized 和 ReentrantLock都是可重入的。
线程池:
线程在程序中是会消耗计算机资源,尤其是创建线程时需要使用操作系统内核调用,不断创建线程也会给jvm垃圾回收带来一定的压力。这个时候可以使用线程池来对线程复用。一个使用线程池的例子:
public static void main(String[] args) {
ExecutorService executorService = new ThreadPoolExecutor(1, //核心线程数
Runtime.getRuntime().availableProcessors() * 2, //最大线程数
60, TimeUnit.SECONDS, //空闲时间
new LinkedBlockingDeque<>(20), //队列大小,超过则进入RejectedExecutionHandler
new ThreadPoolExecutor.AbortPolicy() //拒绝处理,AbortPolicy策略抛出一个RejectedExecutionException,是默认策略
);
for (int i = 0; i < 30; i++) {
Task task = new Task("task" + i);
try {
executorService.submit(task);
}catch (RejectedExecutionException e) {
System.out.println("task be rejected:" + task.name);
}
}
}
public static class Task implements Runnable {
private String name;
public Task(String name) {
this.name = name;
}
public String getName() {
return name;
}
@Override
public void run() {
try {
//do some things
Thread.sleep(5 * 1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("task " + name + " end");
}
}
我的机器是I5四核,运行这段代码,会发现只有两个任务被拒绝,是因为先到的任务会直接进入线程执行,剩下的任务会进入队列,所以一种执行了28个任务。
java.util.concurrent包提供了支持并发的集合和工具,常用的如AtomicInteger,ReentrantLock, ConcurrentHashMap等
网友评论