线程安全与线程通信
线程状态
创建:当new出一个线程对象时即为创建线程
就绪:线程创建完成后进入就绪状态
运行:线程获得CPU资源后进入运行状态,释放CPU资源后进入就绪状态
阻塞:运行中线程可以被sleep方法等阻塞,进入阻塞状态,阻塞解除后进入就绪状态
死亡:线程执行完毕后或外部干涉终止线后线程被杀死
程序休眠sleep:使线程阻塞若干毫秒,使用时需要捕获异常
- 形式:Thread.sleep(毫秒数);
程序礼让yield:使线程回到就绪状态,CPU重新调用,重新调用时不一定会调用其他线程,也就是不一定礼让成功
- 形式:Thread.yield();
线程强制执行join:让一个线程强制执行,正在执行的线程进入阻塞
- 形式:thread.join();
观测线程状态State:其方法返回值为线程状态,可以使用一个变量储存其返回值
- 形式:Thread.State state = thread.getState();
线程优先级:线程存在优先级,优先级越高CPU给予的资源越多,调用的概率越高
-
形式:获取优先级:getPriority()
设置优先级:setPriority(int )
守护线程:线程分为用户线程与守护线程,守护线程会与用户线程一起执行一起结束
- 形式:将线程设置为守护线程:thread.setDaemon(true)
线程安全
线程不安全:
当多个线程操作同一对象时,会出现多个线程获取同一资源的情况,导致线程不安全
例如下面的一段代码:
public class Threadsafe {
public static void main(String[] args) {
Buyticket ticket = new Buyticket();
new Thread(ticket, "people").start(); //买票的人
new Thread(ticket, "people2").start();
new Thread(ticket, "people3").start();
}
}
class Buyticket implements Runnable{
int ticketnumber = 10; //票数
@Override
public void run() {
while (true) {
if (ticketnumber <= 0) { //票数为0则停止买票
return;
}
try {
Thread.sleep(100); //设置睡眠使不安全效果增强
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "抢到了第" + ticketnumber-- + "张票"); //输出买票的人与买到第几张票,买一张少一张
}
}
}
执行后会发现会有不同的人买到同一张票,线程不安全
线程安全:
-
使用同步方法及同步块对资源上锁:synchronized关键字,使线程安全
public class Threadsafe { public static void main(String[] args) { Buyticket ticket = new Buyticket(); new Thread(ticket, "people").start(); //买票的人 new Thread(ticket, "people2").start(); new Thread(ticket, "people3").start(); } } class Buyticket implements Runnable { private int ticketnumber = 10; //票数 boolean flag = true; @Override public void run() { while (flag) { synchronized (this) { //同步块 if (ticketnumber <= 0) { //票数为0则停止买票 return; } try { Thread.sleep(100); //设置睡眠使不安全效果增强 } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + "抢到了第" + ticketnumber-- + "张票"); //输出买票的人与买到第几张票,买一张少一张 } } } }- 同步方法与同步块会给方法或包裹的代码上锁,使一个资源只能被一个线程操
- synchronized是隐式锁,出了作用域自动释放
-
Lock锁:
import java.util.concurrent.locks.ReentrantLock; public class Threadsafe { public static void main(String[] args) { Buyticket ticket = new Buyticket(); new Thread(ticket, "people").start(); //买票的人 new Thread(ticket, "people2").start(); new Thread(ticket, "people3").start(); } } class Buyticket implements Runnable { private int ticketnumber = 10; //票数 boolean flag = true; ReentrantLock lock = new ReentrantLock(); //创建lock对象 @Override public void run() { while (flag) { try { //可以使用try,catch捕获异常 lock.lock(); //给资源上锁 if (ticketnumber <= 0) { //票数为0则停止买票 return; } try { Thread.sleep(100); //设置睡眠使不安全效果增强 } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + "抢到了第" + ticketnumber-- + "张票"); //输出买票的人与买到第几张票,买一张少一张 }finally { lock.unlock(); //最后使用finally释放锁 } } } }- Lock锁只有代码块锁
- Lock是显示锁需要手动开启与关闭
- Lock花费更少时间调度,性能更好,并且有良好的扩展性
死锁
定义:当多个线程相互申请对方的资源,又不释放自己的资源时就会形成死循环,称为死锁
产生死锁的条件:
- 互斥条件:一个资源每次只能被一个进程使用
- 请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放
- 不剥夺条件:进程已获得的资源,在未使用完之前,不能强行剥夺
- 循环等待条件:若干进程之间形成一种头尾相连的循环等待资源关系
只要解决其中一个或多个条件就能避免死锁的发生
线程通信
定义:线程之间的交互称为线程通信
方法:java中提供了几个方法解决线程间通信问题
- wait():表示线程等待直到其他线程通知,wait()会释放锁
- wait(long timeout):指定等待的毫秒数
- notify():唤醒一个等待状态的线程
- notifyAll():唤醒同一个对象上所有调用wait()方法的对象
生产者消费者问题:生产者生产资源,由消费者来消费,协调两个线程之间调度问题
-
管程法:通过建立缓冲区来存放生产者生产的资源来达到协调调度问题
import java.util.Arrays; public class Threadcom { public static void main(String[] args) { SynContainer container = new SynContainer(); new producter(container).start(); new consumer(container).start(); } } //生产者 class producter extends Thread{ SynContainer container; public producter(SynContainer container){ this.container = container; } //生产 @Override public void run() { for (int i = 1; i < 100; i++) { container.push(new produce(i)); System.out.println("生产了第"+i+"号产品"); } } } //消费者 class consumer extends Thread{ SynContainer container; public consumer(SynContainer container){ this.container = container; } //消费 @Override public void run() { for (int i = 1; i < 100; i++) { System.out.println("消费了第"+container.pop().id+"号产品"); } } } //产品 class produce{ int id; public produce(int id){ this.id = id; } } //缓冲区 class SynContainer{ //产品容器 produce produces[] = new produce[10]; //容器计数 int count = 0; //生产者放入产品 public synchronized void push(produce produce){ //如果容器满了 if (count == produces.length-1){ //生产者等待 try { this.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } //如果没满,生产者放入产品 produces[count] = produce; count++; //通知消费者消费 this.notifyAll(); } //消费者消费产品 public synchronized produce pop(){ //如果没有产品 if (count == 0){ //消费者等待 try { this.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } //如果可以消费 count--; produce produce = produces[count]; //通知生产者生产 this.notifyAll(); return produce; } } -
信号灯法:设置一个flag作为信号灯,根据flag的状态调整线程状态
public class Threadcom2 { public static void main(String[] args) { TV tv = new TV(); new preformer(tv).start(); new watcher(tv).start(); } } //生产者 --->演员 class preformer extends Thread{ TV tv; public preformer(TV tv){ this.tv = tv; } @Override public void run() { for (int i = 0; i < 20; i++) { if (i%2 == 0) { tv.preform("CCTV6"); }else { tv.preform("CCTV10"); } } } } //消费者 --->观众 class watcher extends Thread{ TV tv; public watcher(TV tv){ this.tv = tv; } @Override public void run() { for (int i = 0; i < 20; i++) { tv.watch(); } } } //产品 --->电视节目 class TV{ //表演节目 String voice; //信号灯 boolean flag = false; //表演 public synchronized void preform(String voice){ //有节目时,演员休息 if (flag){ try { this.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } //没有节目时,表演,提醒观众观看 System.out.println("表演了"+voice); this.notifyAll(); this.voice = voice; this.flag = !this.flag; } //观看 public synchronized void watch(){ //没有节目时,观众休息 if (!flag){ try { this.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } //有节目时看节目 System.out.println("观看了"+voice); this.notifyAll(); this.flag = !this.flag; } }
浙公网安备 33010602011771号