1.初始线程:线程的基本操作
1.新建线程
2.终止线程
- stop
造成数据不一致
3.线程中断
public void Thread.interrupt() // 中断线程
public boolean Thread.isTnterrupted() // 判断是否被中断
public static boolean Thread.interrupted() // 判断是否被中断,并清除当前中断状态
4.等待(wait)和通知(notify)
public final void wait() throws InterruptedException
public final native void notify()
//
public static native void sleep(long millis) throws InterruptedException
Object.wait()和Thread.sleep()方法都可以让线程等待若干时间。除了wait()可以被唤醒外,另外一个主要区别就是wait()方法会释放目标对象的锁,而Thread.sleep()方法不会释放任何资源。
Object.wait()方法并不是可以随便调用的。它必须包含在对应的synchronzied语句中,无论是wait()或者notify()都需要首先获得目标对象的一个监视器。
5.挂起(suspend)和继续执行(resume)线程
suspend() 在导致线程暂停的同时,并不会取释放任何锁资源。
6.等待线程结束(join)和谦让(yield)
join
public final void join() throws InterruptedException
public final synchronized void join(long millis) throws InterruptedException
join:一个线程的输入可能非常依赖另外一个或者多个线程的输出,此时这个线程就需要等待依赖线程执行完毕,才能继续执行。
- join():无限等待,它会一致阻塞当前线程,直到目标线程执行完毕。
- join(long millis):给出一个最大等待时间,如果超过给定时间目标线程还在执行,当前线程也会因为“等不及了”,而继续往下执行。
join() 的本质是让调用线程 ==**wait()**== 在当前线程对象实例上
//核心代码 while (isAlive()) { wait(0); }
它让调用线程在当前线程对象上进行等待。当线程执行完成后,被等待的线程会在退出前调用notifyAll() 通知所有的等待线程继续执行。因此,值得注意的一点是:不要在应用程序中,在Thread对象实例上使用类似wait()或者notify()等方法,因此这很有可能会影响系统API的工作,或者被系统API所影响
yield
public static native void yield();
2. volatile与Java内存模型(JMM)
volatile:使用volatile申明一个变量时,就等于告诉虚拟机,这个变量极有可能会被某些程序或者线程修改。为了确保这个变量被修改后,应用程序范围内的所有线程都能够“看到”这个改动,虚拟机就必须采用一些特殊的手段,保证这个变量的可见性等特点。
注意:volatile并不能代替锁🔒,它也无法保证一些符合操作的原子性。 例如:👇
public class VolatileTest {
volatile static int i = 0;
public static class PlusTask implements Runnable{
public void run(){
for(int k = 0;k<10000;k++){
i++;
}
}
}
public static void main(String[] args) throws InterruptedException {
Thread[] threads = new Thread[10];
for (int i = 0;i<10;i++){
threads[i] = new Thread(new PlusTask());
threads[i].start();
}
for (int i = 0;i<10;i++){
threads[i].join();
}
System.out.println(i);
}
}
volatile 也能保证数据的可见性和有序性
3.分门别类的管理:线程组
// 创建线程组
ThreadGroup tg = new ThreadGroup("PrintGroup");
// 使用Thread构造方法指定线程所属的县城组
Thread t = new Thread(tg,new ThreadTest(),"T1");
stop:慎用,它会停止线程组中所有的线程。和Thread.stop()一样。
4.驻守后台:守护线程(Daemon)
比如:垃圾回收线程、JIT线程
与之对应的是用户线程,当一个Java内,只有守护线程时,Java虚拟机就会自然退出。
public class DaemonDemo {
public static class DaemonT extends Thread{
public void run(){
while(true){
System.out.println("I'm alive");
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public static void main(String[] args) throws InterruptedException {
Thread t = new DaemonT();
t.start();
// 将t设置为守护线程
t.setDaemon(true);
Thread.sleep(3000);
}
}
5.先干重要的事:线程优先级
使用1-10表示线程优先级。一般使用内置的三个静态标量表示:
数字越大则优先级越高。
/**
* The minimum priority that a thread can have.
*/
public final static int MIN_PRIORITY = 1;
/**
* The default priority that is assigned to a thread.
*/
public final static int NORM_PRIORITY = 5;
/**
* The maximum priority that a thread can have.
*/
public final static int MAX_PRIORITY = 10;
6.线程安全的概念与synchronized
synchronized:是实现线程间的同步。它的工作是对同步的代码加锁,使得每一次,只能有一个线程进入同步块,从而保证线程间的安全性。
用法:
- 指定加锁对象:对给定对象加锁,进入同步代码前要获得给定对象的锁。
- 直接作用于实例方法:相当于对当前实例加锁,进入同步前要获得当前实例的锁
- 直接作用于静态方法:相当于对当前类加锁,进入同步代码前要获得当前类的锁。
7.程序中的幽灵:隐蔽的错误
ArrayList 线程不安全 HashMap 线程不安全. ConcurrentHashMap代替
8.初学者常见问题:错误的加锁
参考文献: 《Java高并发设计》