JAVA总结(四)-----|JAVA总结(四)----- 线程(一)
注:以下程序和概念均参考自《java编程思想》、《Effective Java》、《java并发编程实战》
目录
一、何为并发
二、线程机制
三、java线程的简单使用
定义任务
①、使用Runnable接口定义任务
②、使用Callable接口定义任务
创建线程
①、继承Thread类创建线程
②、向Thread构造器传入Runnable引用
③、线程和任务的区别
线程池的使用
后台线程
①、通过调用Thread.setDaemon()设置后台线程
②、通过ThreadFactory编写定制的Thread属性(如后台、优先级、名称)
yeild()、sleep()、join()、优先级使用
①、yeild()方法
②、sleep()方法
③、join()方法
④、优先级
一、何为并发 我们为什么要使用并发,必然是想提高程序的“运行速度”,可是使用多个“任务”执行程序这不是增加了处理器的开销(因为要进行上下文切换),所以使用顺序编程开销更小,速度更快。但一旦程序进行阻塞(或者因为I/O,或者因为网络)此时,程序将无法继续进行下去,这样的话开销岂不是更大?可一旦使用并发,程序的一部分任务若进行阻塞,那么也还有其他的部分还能运行,这个程序并没有就此终结。
java编程语言使用线程实现并发,原因是:线程比进程更轻量级,使用较小的内存空间完成更多的任务。线程之间共享同一地址空间和所有可用数据的能力。
二、线程机制 java线程调度机制是抢占式,这表示调度机制会周期性中断线程,将上下文切换到另一个线程,从而为每个线程都提供时间片,使得每个线程线程都会分配到数量合理的时间去驱动任务。
java线程机制将程序划分为多个分离,独立运行的任务。通过使用多线程机制,这些独立的任务每一个都将由相应的执行线程进行驱动。
三、java线程的简单使用
- 定义任务
Runnable接口是java常用定义任务的一个接口,实现run()方法,来描述任务。
public class LiftOff implements Runnable { protected int countDown = 10;
private static int taskCount = 0;
private final int id = taskCount++;
public LiftOff() { }
public LiftOff(int countDown) {
this.countDown = countDown;
}
public String status() {
return "#" + id + "(" + (countDown > 0 ? countDown : "Liftoff!") + ")" +Thread.currentThread().getName() + ", ";
}
@Override
public void run() {
while(countDown-- > 0) {
System.out.println(status());
Thread.yield();
}
}
}
②、使用Callable接口定义任务
Runnable是执行工作的独立任务,它没有任何返回值。而Callable接口允许你完成任务的时候能够返回一个值。
Callable接受泛型参数,实现call()方法。使用ExecutorService.submit()方法调用实现该接口的任务,submit()方法
会产生一个Future对象,它将Callable返回的结果的特定类型进行参数化。然后可通过Future.get()获取。
import java.util.ArrayList;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
class TaskWithResult implements Callable { private int id;
public TaskWithResult(int id) {
this.id = id;
}
@Override
public String call() throws Exception {
return "result of TaskWithResult " + id;
}
}public class CallableDemo {
public static void main(String [] args) {
ExecutorService exec = Executors.newCachedThreadPool();
ArrayList results = new ArrayList();
for(int i = 0;
i < 10;
i++)
results.add(exec.submit(new TaskWithResult(i)));
for (Future future : results) {
try {
System.out.println(future.get());
} catch(InterruptedException e) {
System.out.println(e);
return;
} catch(ExecutionException e) {
System.out.println(e);
} finally {
exec.shutdown();
}
}
}
}
- 创建线程
编写子类继承自Thread,重写run()方法。实例化它,再调用start()方法,便可以创建一个线程。
?
public class SimpleThread extends Thread { private int countDown = 5;
private static int threadCount = 0;
public SimpleThread() {
super(Integer.toString(++threadCount));
start();
} public String toString() {
return "#" + getName() + "(" + countDown + "), ";
}
@Override
public void run() {
while(true) {
System.out.println(this);
if(--countDown == 0)
return ;
}
} public static void main(String[] args) {
for(int i = 0;
i < 5;
i++)
new SimpleThread();
}}?
②、向Thread构造器传入Runnable引用
通过Thread类的构造传递Runnable引用,并实例化该Thread对象也可以创建线程(注:LiftOff类在定义任务的第一个程序)
public class BasicThreads { public static void main(String[] args) {
Thread t = new Thread(new LiftOff());
t.start();
System.out.println("Waitting for LiftOff");
System.out.println("Waitting for LiftOff");
System.out.println("Waitting for LiftOff");
}
}
③、线程和任务的区别
在我直观印象中,“线程就是任务”。可是事实并不是这样。 任务必须要依附在某个线程上,使得这个线程能够驱动任务,完成任务。这是第一点。线程是Thread类(或者通过线程池创建线程)无论哪一种,我们最终希望能够用线程做能做到的事,而不是控制线程(实际上我们也控制不了线程)。任务是实现Runnable接口的对象,这是我们真正可以控制,编写可用代码的部分。这是第二点。
在实际创建线程上,常常通过创建Thread对象传递Runnable引用。而不是直接继承自Thread。原因有二、其一,浪费类的继承机会,线程-任务高耦合,这是我们不愿意看到的。
- 线程池的使用
java.util.concurrent提供了这种灵活的线程池。它是Executor框架的一部分(Executor框架基于生产者-消费者模式,提交任务操作相当于生产者,执行任务的线程相当于消费者)这里列举java类库提供的几种线程即它们各自的特点(通过Executors静态工厂方法调用)
newFixedThreadPool() | newFixedThreadPool将创建一个固定长度的线程池,每当提交一个任务时就创建一个线程,直到达到线程池的最大数量,这时线程池的规模将不再变化。当有多于线程数量的任务,任务将会在工作队列中等待有工作线程空闲(如果某个线程由于发生了未预期的Exception而结束,那么线程池会补充一个新的线程) |
newCachedThreadPool() | newCachedThreadPool将创建一个可缓存的线程池,如果线程池的当前规模超过了处理需要时,那么将回收空闲的线程,而当需求增加是,则可以添加新的线程,线程池的规模不存在任何限制。 |
newSingleThreadExecutor() | newSingleThreadExecutor是一个单线程的Executor,它创建单个工作者线程来执行任务,如果这个线程异常结束,会创建另外一个线程来代替。newSingleThreadExecutor能确保任务在队列中的顺序来串行执行 |
newScheduledThreadPool() | newScheduledThreadPool创建一个固定长度的线程池,而且以延迟或定时的方式来执行任务。 |
下面程序中创建5个固定大小的newFixedThreadPool()(返回ExecutorService对象),并且执行7个任务。随后main线程调用shutdown()方法,该方法的作用是平缓关闭线程池----不再接受新的任务,同时等待已经提交的任务执行完成。在随后的在接受线程池的方法
public class FixedThreadPool { public static void main(String[] args) {
ExecutorService exec = Executors.newFixedThreadPool(5);
for(int i = 0;
i < 7;
i++)
exec.execute(new LiftOff());
exec.shutdown();
}
}
- 后台线程
①、通过调用Thread.setDaemon()设置后台线程
在该程序中,通过创建10个线程,并在线程启动前全部设置成后台线程。
public class SimpleDaemons implements Runnable { @Override
public void run() {
try {
while(true) {
TimeUnit.MILLISECONDS.sleep(100);
System.out.println(Thread.currentThread() + " " + this);
}
} catch(InterruptedException e) {
System.out.println("sleep() interrupted");
}
}
public static void main(String [] args) throws InterruptedException {
for(int i = 0;
i < 10;
i++) {
Thread daemon = new Thread(new SimpleDaemons());
daemon.setDaemon(true);
daemon.start();
}
System.out.println("All daemons started");
TimeUnit.MILLISECONDS.sleep(100);
}
}
②、通过ThreadFactory编写定制的Thread属性(如后台、优先级、名称)
实现ThreadFactory接口,重写newThread()方法,即可产生定制的Thread。再将其作为参数传递给线程池(Executors.newCachedThreadPool(实现ThreadFactory接口的实例)),就可以产生定制的Thread线程
// 定制线程池的线程样式,这里定制了线程都是后台线程
public class DaemonThreadFactory implements ThreadFactory { @Override
public Thread newThread(Runnable r) {
Thread t = new Thread(r);
t.setDaemon(true);
return t;
}
}
public class DaemonFromFactory implements Runnable { @Override
public void run() {
try {
while(true) {
TimeUnit.MILLISECONDS.sleep(100);
System.out.println(Thread.currentThread() + " " + this);
}
} catch(InterruptedException e) {
System.out.println(e);
}
}
public static void main(String [] args) throws InterruptedException {
// 创建具有定制线程的线程池
ExecutorService exec = Executors.newCachedThreadPool(new DaemonThreadFactory());
for(int i = 0;
i < 10;
i++)
// 线程池执行10个任务
exec.execute(new DaemonFromFactory());
System.out.println("All daemons started");
TimeUnit.MICROSECONDS.sleep(100000);
}
}
③、通过调用Thread.isDaemon()判断线程是否为后台线程
Daemon线程被设置成后台线程,从该线程派生的子线程,尽管没有显示设置为后台线程,但也被隐式的设置成后台线程。
class Daemon implements Runnable {
private Thread[] t = new Thread[10];
@Override
public void run() {
for(int i = 0;
i < t.length;
i++) {
t[i] = new Thread(new DaemonSpawn());
t[i].start();
System.out.println("DaemonSpanw" + i + " started");
}for(int i = 0;
i < t.length;
i++)
System.out.println("t[" + i + "].isDaemon() = " + t[i].isDaemon() + ", ");
while(true)
Thread.yield();
}
}class DaemonSpawn implements Runnable { @Override
public void run() {
while(true)
Thread.yield();
}
}public class Daemons { public static void main(String[] args) throws InterruptedException {
Thread d = new Thread(new Daemon());
d.setDaemon(true);
d.start();
System.out.println("d.isDaemon() = " + d.isDaemon() + ", ");
// 由Daemons线程产出DaemonSpawn任务的线程
// 这些线程并没有显示的设置成后台线程,但因为Daemons是后台线程,所以他们也是后台线程
TimeUnit.MICROSECONDS.sleep(10000);
}
}
文章图片
- yeild()、sleep()、join()、优先级使用
yeild()方法表示线程让步,指当前线程的任务已经做得差不多,可以让别的线程获得CPU使用权。(但这只是一个“暗示”,实际上没有任何机制可以保证它将会被采纳)
当调用yeild()方法时,只是在做建议具有“相同优先级”的其他线程可以运行。总之,对于任何重要的控制或者调用整体运用时,最好不要依赖于yeild()
②、sleep()方法
sleep()方法可以让线程休眠指定的时间(在这段时间内,任务不会被执行)。当休眠时,它不会释放当前线程获取的对象锁。并且线程会处于阻塞状态。对sleep()的调用将会抛出InterruptedException异常。(线程调用抛出的异常,并不会回到main()中,因为异常不能跨线程传播。)
对于sleep()方法的调用,采用的是JavaSE5引入的TimeUnit类的sleep()版本,而不推荐使用Thread.sleep()方法。就如下面这样
public class SleepingTask extends LiftOff { @Override
public void run() {
try {
while(countDown-- > 0) {
System.out.println(status());
// 新的线程休眠的方法
TimeUnit.MILLISECONDS.sleep(100);
}
} catch(InterruptedException e) {
System.err.println("Interrupted");
}
} public static void main(String [] args) {
ExecutorService exec = Executors.newCachedThreadPool();
for(int i = 0;
i < 5;
i++)
exec.execute(new SleepingTask());
exec.shutdown();
}
}
③、join()方法
一个线程可以在其它线程之上调用join()方法,其效果是等待一段时间直到第二个线程结束才继续执行。
如果某个线程在另一个线程t调用t.join(),此线程被挂起,直到目标线程t结束才恢复
class Sleeper extends Thread {
private int duration;
public Sleeper(String name, int sleepTime) {
super(name);
duration = sleepTime;
start();
}
@Override
public void run() {
try {
sleep(duration);
} catch(InterruptedException e) {
// 当线程被挂起时,catch将会捕捉到isInterupted中断标志,随后进行语句块中,并且消除这个标志,因此catch语句块中的isInterrupted标志都为false
System.out.println(getName() + " was interrupted. " + "isInterrupted(): " + isInterrupted());
return ;
}
System.out.println(getName() + " has awakened");
}
}class Joiner extends Thread {
private Sleeper sleeper;
public Joiner(String name, Sleeper sleeper) {
super(name);
this.sleeper = sleeper;
start();
}
@Override
public void run() {
try {
// 在joiner线程调用sleeper线程的join方法(),将会导致,当前(joiner线程)被挂起,而sleepr将会被唤醒(正常的话则继续执行)
// 然后等待sleeper线程执行结束才会继续执行joiner线程
sleeper.join();
} catch(InterruptedException e) {
System.out.println("Interrupted");
}
System.out.println(getName() + " join completed");
}
}public class Joining { public static void main(String[] args) {
Sleeper
sleepy = new Sleeper("Sleepy", 1500),
grumpy = new Sleeper("Grumpy", 1500);
Joiner
dopey = new Joiner("Dopey", sleepy),
doc = new Joiner("Doc", grumpy);
grumpy.interrupt();
}
}
*结果说明:由于Grumpy线程调用中断方法,因此导致该线程直接被中断结束。而与之相关的Doc线程调用Grumpy线程的join()方法也不会导致自己被挂起。直接完成。
在Dopey线程中由于调用了Sleepy线程的join()方法,导致自己被挂起,原来睡眠的Sleepy线程被唤醒。继续执行直至死亡,此时Dopey线程才得到运行。
文章图片
④、优先级
线程的优先级将线程的重要性传递给调度器。然而,这并不会意味着优先级低就不能执行,仅仅是执行的频率较低。在大多时候,所有线程都应该按照默认的优先级运行。操作线程优先级通常是一种错误的做法。
设置线程优先级通常通过Thread.setPriority()来修改它,Thread.getPriority()获取线程的优先级。java中总共有10个优先级可以设置。
public class SimplePriorities implements Runnable { private int countDown = 5;
private volatile double d;
private int priority;
public SimplePriorities() { }
public SimplePriorities(int priority) {
this.priority = priority;
}
public String toString() {
return Thread.currentThread() + ": " + countDown;
}
public static void main(String[] args) {
ExecutorService exec = Executors.newCachedThreadPool();
for(int i = 0;
i < 5;
i++)
exec.execute(new SimplePriorities(Thread.MIN_PRIORITY));
exec.execute(new SimplePriorities(Thread.MAX_PRIORITY));
exec.shutdown();
} @Override
public void run() {
Thread.currentThread().setPriority(priority);
while(true) {
for(int i = 1;
i < 100000;
i++) {
d += (Math.PI + Math.E) / (double)i;
if(i % 1000 == 0)
Thread.yield();
}System.out.println(this);
// 这其实是循环终止的条件
if(--countDown == 0) return ;
}
}
}
【JAVA总结(四)-----|JAVA总结(四)----- 线程(一)】
推荐阅读
- JAVA(抽象类与接口的区别&重载与重写&内存泄漏)
- 跌跌撞撞奔向你|跌跌撞撞奔向你 第四章(你补英语,我补物理)
- 奔向你的城市
- 7.9号工作总结~司硕
- 四首关于旅行记忆的外文歌曲
- CET4听力微技能一
- 事件代理
- 亲子日记第186篇,2018、7、26、星期四、晴
- Java|Java OpenCV图像处理之SIFT角点检测详解
- java中如何实现重建二叉树