基本的线程机制
创建线程
- 实现Runnable接口,重写run方法,将Runnable实例传给Thread构造器
package com.alpha.concurrent.test;
public class RunnableTest implements Runnable {
private static int taskId = 0;
private int id = taskId++;
public RunnableTest() {
System.out.println("construct task:" + id);
}
@Override
public void run() {
System.out.println("task " + id + " run complete.");
}
public static void main(String[] args) {
new Thread(new RunnableTest()).start();
System.out.println("all thread has already started.");
}
}
- 继承Thread,重写run方法
package com.alpha.concurrent.test;
public class ThreadTest extends Thread {
@Override
public void run() {
super.run();
System.out.println("thread run");
}
public static void main(String[] args) {
new ThreadTest().start();
System.out.println("thread has already started.");
}
}
- 使用Executor,可以用Executors.newCachedThreadPool()、Executors.newFixedThreadPool(5)和Executors.newSingleThreadExecutor()创建ExecutorService实例,然后通过ExecutorService 的execute方法执行线程,这个方法接受一个Runnable作为参数。
package com.alpha.concurrent.test;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class RunnableTestWithExecutor {
private static void cachedExecute() {
ExecutorService execute = Executors.newCachedThreadPool();
for (int i = 0; i < 20; i++) {
execute.execute(new RunnableTest());
}
execute.shutdown();
}
private static void fixedExecute() {
ExecutorService execute = Executors.newFixedThreadPool(5);
for (int i = 0; i < 20; i++) {
execute.execute(new RunnableTest());
}
execute.shutdown();
}
private static void singleThreadExecute() {
ExecutorService execute = Executors.newSingleThreadExecutor();
for (int i = 0; i < 20; i++) {
execute.execute(new RunnableTest());
}
execute.shutdown();
}
public static void main(String[] args) {
// cachedExecute();
fixedExecute();
// singleThreadExecute();
System.out.println("all thread already started.");
}
}
- 实现Callable接口,重写call方法,使用ExecutorService的submit执行,且返回一个Future<T>对象,其中泛型是call()返回的结果类型,可以使用Future的get方法获取该返回值,get方法会被阻塞直到结果准备就绪。
package com.alpha.concurrent.test;
import java.util.ArrayList;
import java.util.concurrent.*;
public class CallableTest implements Callable<String> {
private static int taskId = 0;
private int id = taskId++;
private int n;
public CallableTest(int n) {
this.n = n;
}
private int fib(int n) {
if (n < 2) return 1;
return fib(n - 2) + fib(n - 1);
}
@Override
public String call() throws Exception {
int sum = 0;
for (int i = 0; i < n; i++) {
sum += fib(i + 1);
}
return "task " + id + ", sum: " + sum;
}
public static void main(String[] args) {
ExecutorService executor = Executors.newCachedThreadPool();
ArrayList<Future<String>> results = new ArrayList<>();
for (int i = 0; i < 20; i++) {
results.add(executor.submit(new CallableTest(i + 1)));
}
for (Future<String> future : results) {
try {
System.out.println(future.get());
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
} finally {
executor.shutdown();
}
}
}
}
休眠
有如下两种方式调用sleep,方式1中以毫秒为单位,而方式2使用TimeUnit类显式指定时间单位。sleep可能会抛出InterruptedException,所以要处理这个异常。
try {
//方式1
Thread.sleep(100);
//方式2
TimeUnit.MILLISECONDS.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
优先级
jdk有是个优先级,但是因为和多数操作系统没有很好的映射,因此应该选择使用下面三个:
MAX_PRIORITY、NORM_PRIORITY和NORM_PRIORITY,他们都是Thread的静态属性。
可以使用Thread的setPriority()方法设置线程优先级,如下:
Thread t = new Thread(new RunnableTest());
t.setPriority(Thread.MAX_PRIORITY);
线程调度器倾向于让优先级最高的线程先执行,最好尽量不要操作线程的优先级,让他保持默认的优先级。
让步
Thread.yield();
通知线程调度器,我的工作已经做得差不多了,你可以让别的线程使用CPU,但是不一定会被线程调度器采纳。任何重要的控制都不能依赖yield。
后台线程
通过Thread的setDaemon(true)将线程设置为后台线程。
一旦main方法执行完成后,若只有后台线程在运行,程序就会终止。
可以通过isDaemon()方法判断线程是不是后台线程。
join
如果在某个线程a上调用另一个线程b的join()方法,那么当前线程a将被挂起,知道目标线程b结束才恢复执行。
捕获异常
实现Thread.UncaughtExceptionHandler接口,并重写uncaughtException方法,调用Thread的setUncaughtExceptionHandler方法设置异常捕获:
class MyUncaughtExceptionHandler implements Thread.UncaughtExceptionHandler {
@Override
public void uncaughtException(Thread t, Throwable e) {
System.out.println("caught: " + e);
}
}
Thread t = new Thread(new RunnableTest());
t.setUncaughtExceptionHandler(new MyUncaughtExceptionHandler());
Thread中有静态方法setDefaultUncaughtExceptionHandler(),可以使用这个方法设置默认的异常异常处理器。
共享资源
解决资源竞争
synchronized
将方法标记为synchronized防止冲突,所有对象都含有单一的锁,当在对象上调用synchronized方法时,此对象会被加锁,此时该对象上的其他synchronized方法只有等到前一个方法调用完毕并释放锁之后才能被调用。
一个线程可以多次获得对象的锁。
Brian的同步规则:
如果你正在写一个变量,它可能接下来被另一个线程读取,或者正在读取一个已经被另一个线程写过的变量,那么你必须使用同步,并且,读写线程都必须用相同的监视器锁同步。
Lock
可以创建显式的Lock对象警醒锁定和释放:
Lock lock = new ReentrantLock();
lock.lock();
try {
i++;
} finally {
lock.unlock();
}
可以在finally子句中重置状态或者进行清理等操作。
原子性和易变性
首先,要知道一件事,依赖于原子性是很棘手且很危险的。
原子性:不可分的操作
可视性: 某个线程所做的修改,在其他线程中可能是不可视的(例如,修改知识暂时存储在本地处理器缓存中),因此可视性就是要保证某个线程所做的修改要能被其他线程看到。
volatile:将域声明为volatile的,那么只要对这个域进行了写入,那么所有的读操作就都可以看到这个修改。volatile域会立即被写入到主存中,而服务操作就发生在主存中。
当一个值依赖于他之前的值时(例如递增一个计数器),volatile就无法工作了。使用volatile而不是synchronized的唯一安全情况是类中只有一个可变的域。
原子类
AtomicInteger、AtomicLong、AtomicReference等
临界区
有时只是希望防止多个线程同时访问方法内部的部分代码而不是防止访问整个方法,这样的代码段称作临界区。使用synchronized指定某个对象来创建临界区:
synchronized (obj) {
i++;
}
这也被成为同步控制块,进入这段代码前,必须获得obj对象的锁,当其他线程想要访问这段代码,必须等到obj锁被释放后才能访问。
通过使用同步代码块,可以提高程序的性能。
线程本地存储
线程本地存储就是为使用相同变量的不同线程都创建存与线程内部的存储。可以有ThreadLocal来实现:
package com.alpha.concurrent.test;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
class Accesser implements Runnable {
private int id;
public Accesser(int idn) {
id = idn;
}
@Override
public void run() {
while (!Thread.currentThread().isInterrupted()){
ThreadLocalTest.increment();
System.out.println(this);
Thread.yield();
}
}
@Override
public String toString() {
return "#" + id + ":" + ThreadLocalTest.get();
}
}
public class ThreadLocalTest {
private static ThreadLocal<Integer> threadLocal = new ThreadLocal<Integer>(){
@Override
protected Integer initialValue() {
return 20;
}
};
public static void increment() {
threadLocal.set(threadLocal.get() + 1);
}
public static int get() {
return threadLocal.get();
}
public static void main(String[] args) {
ExecutorService exec = Executors.newCachedThreadPool();
for (int i = 0; i < 5; i++) {
exec.execute(new Accesser(i));
}
try {
TimeUnit.MILLISECONDS.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
exec.shutdownNow();
}
}
ThreadLocal通常当作静态域存储,通过get、set读取或写入数据。
网友评论