线程的几种状态:
new指的是创建一个thread或者runnable对象;dead是指线程线程无法再占用cpu和内存,不存在了。runnable指的是线程再线程池中等带cpu给他分配资源,等待执行的状态;running就是线程在执行。值得注意的是blocked(阻塞)状态。我们有时候为了线程安全考虑,需要在一些情况下停止线程运行,在需要的时恢复线程运行,为了这个目标引入阻塞的概念。
线程在Running的过程中可能会遇到阻塞(Blocked)情况
- 调用join()和sleep()方法,sleep()时间结束或被打断,join()中断,IO完成都会回到Runnable状态,等待JVM的调度。
- 调用wait(),使该线程处于等待池(wait blocked pool),直到notify()/notifyAll(),线程被唤醒被放到锁定池(lock blocked pool ),释放同步锁使线程回到可运行状态(Runnable)
- 对Running状态的线程加同步锁(Synchronized)使其进入(lock blocked pool ),同步锁被释放进入可运行状态(Runnable)。
此外,在runnable状态的线程是处于被调度的线程,此时的调度顺序是不一定的。Thread类中的yield方法可以让一个running状态的线程转入runnable。
当一个进程内有多个线程同时运行,多个线程之间共享数据时,就会出现线程安全问题,线程安全问题遵循这三个规律:是否是多线程;是否有共享数据;是否有多个线程操作共享数据。
以下列举了三种解决线程安全的方法:
1.synchronize同步代码块
同步代码块把进入的线程锁住,只有获得锁的线程能执行同步代码块内的程序,这个把线程锁住的锁是内置锁(java中的内置锁是互斥锁),也就意味着只有一个线程能去获得这个锁,另一个线程想要获得这个锁,必须等上一个线程释放锁,因此如果这个线程不释放锁,那么另一个线程会一直等下去。
格式:
synchronized(对象){
需要同步的代码;
}
这个对象其实就是一把锁,也可以称之为监视器,监视进程的进出,它是同步代码块保证数据的安全性的一个主要因素。要注意的是这个对象必须被所有线程共享,不能在里面new对象,这样每把锁都不一样了,这个锁也就没有意义了。
由于多个线程共享同一把对象锁,当某个线程拥有对象锁时,其他线程无法执行该对象锁对应的同步代码块里的程序,实现了多并发时的线程安全。当某个同步代码块程序执行完成以后,自动释放对象锁,然后在锁定池内的其他线程争抢该锁,争到该锁的线程即可由runnable状态变为running状态。
有的时候我们要给线程的执行再加上限制条件,使得线程在不满足限制条件时即使抢到对象锁也不执行,这就需要wait()方法,wait方法只能在synchronized{}内部使用,效果是让线程进入阻塞状态,进入等待池,同时释放对象锁。notify和notifyall方法可以将等待池内的线程加入到锁定池争抢锁。从而实现对线程的控制。常见的使用场景:
生产者消费者模型。
使用2个线程,分别代表:生产者、消费者。让他们并发的去生产、消费产品。这里我们使用的是使用信号量去控制线程的生产消费,通过释放令牌的形式去控制生产者消费者的上限。使用互斥锁保证每次最多只有一个角色去修改共享变量。
//生产者线程
public class ProducerThread implements Runnable{
private int maxorder=100;
private int order[];
private Object obj;
public void produce() {
order[0]++;
}
public ProducerThread(int order[],Object obj) {
this.order=order;
this.obj=obj;
}
public void run() {
while(true) {
synchronized (obj) {
//判断仓库的容量
if(order[0]<100) {
//生产产品
produce();
System.out.println(Thread.currentThread().getName()+"生产者"+":"+order[0]);
}
else {
//唤醒消费者
obj.notify();
try {
obj.wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
}
}
//消费者线程
public class ConsumeThread implements Runnable{
private int[] order;
private Object obj;
public Lock lock = new ReentrantLock();
public void consume() {
order[0]--;
}
public ConsumeThread(int[] order,Object obj) {
this.order=order;
this.obj=obj;
}
public void run() {
while(true) {
synchronized (obj) {
//判断仓库的容量
if(order[0]>0) {
consume();
System.out.println(Thread.currentThread().getName()+"消费者"+":"+order[0]);
}
else {//进入等待状态
obj.notify();
try {
obj.wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
//唤醒生产者
}
}
}
}
//主线程
public class Main {
public static void main(String[] args) {
Ticket tk=new Ticket();
int[] t=new int[1];
t[0]=tk.tk;
Object obj=new Object();
ProducerThread c = new ProducerThread(t,obj);
ConsumeThread b = new ConsumeThread(t,obj);
new Thread(c,"B").start();
new Thread(b,"A").start();
//new Thread(b,"A").start();
//new Thread(b,"B").start();
//new Thread(b,"C").start();
}
}
//输出结果
B生产者:98
B生产者:99
B生产者:100
A消费者:99
A消费者:98
A消费者:97
2.Lock接口
用同步代码块我们可以给线程上锁,但是具体在哪里上锁和解锁,我们都不知道,因此为了让我们更加清楚如何加锁和解锁,在JDK5以后提供了Lock接口和它的ReentrantLock子类,这个锁与synchronized不同,需要手动去加锁和解锁。
void lock() | 加锁 |
void unlock() | 解锁 |