javase:线程同步
互斥锁
互斥锁介绍
①互斥锁是一个互斥的同步对象,意味着同一时间间有且仅有一个线程可以获取它
②互斥锁可适用于一个共享资源,每次只能被一个线程访问的情况
互斥锁特性
互斥锁存在于每个对象中,通过利用互斥锁(特殊的标记)实现某一个时间上只能有一个线程对这个对象进行操作
临界资源
多个线程共享同一个资源,那么这个资源就叫临界资源
synchronize块
synchronize关键字
①通过synchronize关键字,间接的使用互斥锁
②使用synchronize关键字修饰的方法。表示执行该方法是,当前对象只能被一个线程使用。这个线程使用完,才能被其他线程使用
synchronize块
①synchronize可以修饰一片代码段
语法:synchronize(object){//代码} Object就是对象锁,object中可以是产生的其他对象,也可以是this,this表示临界资源
②synchronize可以修饰方法
等价于:synchronize(this){//代码块}
③不同点:synchronize修饰方法的作用范围比synchronize(this){}范围大
生产者/消费者
生产者消费者模型
①provider生产者线程负责生产数据
②consumer 消费者线程负责消费生成的数据
多线程实现生产者和消费者案例
Stack.java
package ThreadDemo.ProvideAndConsumer;
import java.util.Stack;
public class StackTest {
public static void main(String[] args) {
//1.创建一个栈
Stack<String> stack = new Stack<>();
//2.创建两个线程
ProvideDemo p1 = new ProvideDemo(stack);
ProvideDemo p2 = new ProvideDemo(stack);
ProvideDemo p3 = new ProvideDemo(stack);
ConsumerDemo c1 = new ConsumerDemo(stack);
ConsumerDemo c2 = new ConsumerDemo(stack);
ConsumerDemo c3 = new ConsumerDemo(stack);
//3.启动线程
p1.start();
p2.start();
p3.start();
c1.start();
c2.start();
c3.start();
}
}
ProvideDemo.java
package ThreadDemo.ProvideAndConsumer;
import ThreadDemo.ThreadDemo;
import java.util.Stack;
public class ProvideDemo extends Thread{
private Stack<String> stack;
private int start = 1;
public ProvideDemo(Stack stack){
this.stack = stack;
}
@Override
public void run(){
while(true){
synchronized (stack){
stack.push(start+"");
System.out.println("入栈内容为:"+start);
System.out.println("栈中数据为:"+stack);
start++;
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
ConsumerDemo.java
package ThreadDemo.ProvideAndConsumer;
import java.util.Stack;
public class ConsumerDemo extends Thread{
private Stack<String> stack;
public ConsumerDemo(Stack stack){
this.stack = stack;
}
@Override
public void run() {
while(true){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (stack){
if(!stack.isEmpty()){
String data = stack.pop();
System.out.println("出栈内容为:"+data);
System.out.println("栈中数据为:"+stack);
}
}
}
}
}
死锁及解决方案
死锁概念
是指两个或两个以上的进程(线程)在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外部处理作用,它们都将无限等待下去
死锁原因
①系统资源不足②进程(线程)推进的顺序不恰当③资源分配不当
死锁形成的条件
①互斥条件:所谓互斥就是进程在某一时间独占资源
②请求与保持联系:一个进程因请求资源而阻塞时,对已获得的资源保持不放
③不剥夺条件:进程已获得资源,在未使用完之前,不能强行剥夺
④循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系
死锁形成的场景
①忘记释放锁②单线程重复申请锁③多线程多锁申请④多线程多锁生气
避免死锁
①加锁顺序(线程按照一定的顺序加锁)
②加锁时限(线程尝试获取锁的时候加上一定的时限,超过时限则放弃对该锁的请求,并释放自己占用的锁)
③死锁检测(每当一个线程获得了锁,会在线程和锁相关的数据结构中(map,graph等)将其几下。除此之外,每当有线程请求锁,也需要记录在这个数据结构之中)
线程休眠与唤醒
Object类的API
①wait();当前线程要释放锁定资源,但要进入阻塞状态。使用wait()方法要释放对象锁
②notify()是要解除阻塞进入对象池中等待获取对象锁
③notifyAll()解除阻塞池中所有线程。让其进入到阻塞对象池中,去争夺对象锁
案例:利用线程交叉输出奇数和偶数
Number.java
package ThreadDemo.NumberDemo;
public class Number {
public int getNo() {
return no;
}
public void setNo(int no) {
this.no = no;
}
private int no ;
public Number(int no){
super();
this.no = no;
}
}
NumberDemo.java
package ThreadDemo.NumberDemo;
public class NumberDemo {
public static void main(String[] args) {
//1.定义一个数字(临界资源)
Number no = new Number(1);
//2.创建线程资源
NumberThread nt = new NumberThread(no);
//3.创建线程
Thread t1 = new Thread(nt);
Thread t2 = new Thread(nt);
//
t1.start();
t2.start();
}
}
class NumberThread implements Runnable{
private Number no;
public NumberThread(Number no){
this.no = no;
}
@Override
public void run() {
while(true){
synchronized (no){
int num = no.getNo();
if(num % 2 == 0 ){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
even();
num++;
no.setNo(num);
no.notifyAll();
try {
no.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
continue;
}
if(num % 2 == 1){
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
odd();
num++;
no.setNo(num);
no.notifyAll();
try {
no.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
continue;
}
}
}
}
public void even(){
System.out.println(no.getNo());
}
public void odd(){
System.out.println(no.getNo());
}
}