回顾
-
图解:
其中:
- start()并不代表运行,只是表示我准备好了
- 运行状态可进入由用户或代码而进入阻塞/等待状态,死锁时进入锁池状态
- yield()暗示该线程已经做完了工作,可以让资源给其他角色(不保证立马结束)
- 等待队列需要释放语句才能进入锁竞争队列,从而重新进入可运行状态
- run结束或run背后的主线程结束时,over
- 常用实现
- 实现Runnable接口(推荐)
public class Test implements Runnable {
public void run() {
// 只需要实现run方法即可
}
class test extends Thread {
// 重写从thread继承的 run方法
public void run() {
}
}
public static void main(String[] args) {
Test t = new Test();
t.run();
test t2 = t.new test();//使用.new从外部类访问内部类
t2.start(); //继承Thread的话 需调用start()方法先进入就绪状态
}
- 使用Executors线程池
public class CachedThreadPool {
public static void main(String[] args) {
// 创建一个缓冲线程池服务,输入参数则创建固定线程个数的线程池
ExecutorService exec = Executors.newCachedThreadPool();
for (int i = 0; i < 5; i++) {
// 线程池服务启动线程
final int j = i;
exec.execute(new Runnable() {
// 匿名内部类实现线程
public void run() {
System.out.println("Thread " + j + " is running");
}
});
// 线程池服务停止
}
exec.shutdown();
}
}
- 使用Executors.newSingleThreadPool()可创建单线程池,一般用于长时间存活的单任务,例如网络socket连接
- 获取线程返回值,实现Runnable接口的线程没有返回值, 需要额外实现Callable接口.
public class CachedThreadPool {
//创建一个继承自Callable的线程,使其能够返回线程值,
//call方法相当于带有返回值的run方法
class TakWithResult implements Callable<String> {
private int id;
public TakWithResult(int id) {
// TODO Auto-generated constructor stub
this.id = id;
}
@Override
public String call() throws Exception {
// TODO Auto-generated method stub
return "result of TaskWhithr " + id;
}
}
public static void main(String[] args) {
// 创建一个缓冲线程池服务,输入参数则创建固定线程个数的线程池
ExecutorService exec = Executors.newCachedThreadPool();
List<Future<String>> results = new ArrayList<Future<String>>();
CachedThreadPool ca = new CachedThreadPool();
for (int i = 0; i < 5; i++) {
results.add(exec.submit(ca.new TakWithResult(i)));
// 线程池服务启动线程
final int j = i;
exec.execute(new Runnable() {
// 匿名内部类实现线程
public void run() {
System.out.println("Thread " + j + " is running");
}
});
// 线程池服务停止
}
//通过call方法在线程外部获取线程返回值
for(Future<String> fs : results) {
try {
System.out.println(fs.get());
}catch (Exception e) {
System.out.println(e);// TODO: handle exception
}finally {
exec.shutdown();
}
}
}
}
5.线程休眠Thread.sleep(1000)
- 线程休眠的方法是TimeUnit枚举类型中的方法,等同于jdk5之后的新方法,效果都为休眠一秒
TimeUnit.SECONDS.sleep(1);
- 线程休眠方法都要捕获InterruptedExecutors异常
- 线程优先级
- 指线程被线程调度器调度执行的优先级顺序,优先级越高,表示获取cpu允许时间的概率就越大,但是不是绝对,因为线程调度器调度不可控,最高10,最低1
- 通过方法yield()/setPriority()来给县城调度器提供建议
- 守护线程
- 是某些提供通用服务的在后台运行的程序,是优先级最低的线程,当所有非守护线程结束后,程序会结束所有的守护线程而终止程序运行,如果当前还有非守护线程正在于心,则不会进行终止操作
public class SimpleDaemons implements Runnable {
@Override
public void run() {
// TODO Auto-generated method stub
try {
System.out.println("start daemons ");
TimeUnit.SECONDS.sleep(1);
}catch (Exception e) {
// TODO: handle exception
System.out.println("sleep() interrupted");
}finally {
System.out.println("finally go ");
}
}
public static void main(String[] args) {
Thread daemon = new Thread(new SimpleDaemons());
//去除注释即可设置守护线程
//daemon.setDaemon(true);
daemon.start();
}
}
- 设置该线程为守护线程后,main()则成为唯一的非守护线程,当线程在休眠时,main执行结束,jvm检测到只有守护线程在运行,则终止程序,无法执行finally的内容
- 使用setDaemon方法设置守护线程,通过isDaemon方法判断一个线程是否为守护线程
- 由守护线程创建的线程对象不论有没有通过setDaemon方法显示设置,都是守护线程(也说明了能设置多个守护线程)
- synchronized 线程同步
- 用于对共享资源加锁,防止同时访问
a. 方法同步
public synchronized void methodA(){
syso(this);
}
-对方法加锁后,通对象的不同线程,在同一时刻,只能有一个线程能够调用methodA方法
b. 静态方法(类对象)同步
public synchronized static void methodA(){
syso(this);
}
- 静态方法的线程同步锁对类的所有对象都起作用,即所有对象的线程在同一时刻,只有一个类的一个线程能够调用该方法
c. 对象同步代码块
public void methodC(){
synchronized(this){
syso(this);
}
}
- 使用当前对象作为线程的同步锁,同一个对象的不同线程,在同一时刻只能有一个线程调用methodc方法, 好像和a一样
d. 类同步代码块
public void methodD(){
synchronized(className.class){
syso(this);
}
}
- 使用类字节码对象作为线程同步锁,类所有对象的所有线程,在同一时间内,只能有一个类的一个线程调用methodD的同步代码块
注意: 线程的同步针对对象, 不论同步方法还是同步代码块,都是锁定的对象,而非代码块和方法本身,每一个对象只能有一个线程同步锁与之关联
如果一个对象有多个线程同步方法, 只要有一个线程访问了其中一个线程同步方法,其他线程就不能同时访问这个对象中任何一个线程同步方法
同步代码块和同步方法的区别在于前者可以使多个任务访问对象的时间性能得到显著提升
- 线程锁
- 在java.util.concurrent.locks包中引入了线程锁机制,编程中可以显式锁定确保线程间
- ReentrantLock可以通过lock()锁定对象,也可通过tryLock()方法来锁定对象,对于显式使用线程锁的方法体或代码块必须放在try-catch-finally块中,必须在finally中释放对象锁.
- 与Synchronized相似,但是后者更复杂,能手动控制锁定状态与实践,功能更全面
- volatile关键字
- 确保变量跨程序可见性,告诉编译器移除线程中的读写缓存,直接从内存中读写,使用volatile声明的域,只要发生了写改动,会立即写入内存,所有读取该字段的值都会跟着改变,即使使用了本地缓存任然会改变
- 不使用状况
a. 一个域完全由synchronized方法或语句块防护,就不必使用volatile.
b. 当一个域的值依赖于它之前的值时(递增,设计一个读与一个写,不是原子性)
c. 如果某个域的值受到其他域的限制(range类的lower和upper边界必须遵循lower <= upper的限制) - 第一选择是使用synchronized方式,最简洁最安全
代码目测记事本手撸,小错误很多,都修正了下
原帖传送门