写个死锁
思路:死锁要两个线程互相占用对方的资源,所以需要定义两个对象作为共享资源,然后两个类分别实现Runable接口,然后重写run的时候嵌套用两次同步块。
- 线程1和线程2均需要获取对象A和B,线程1先获取对象A,再获取对象B;
- 线程2先获取对象B,再获取对象A。
- 双方都在等地对方释放已拥有的对象,且不放弃自己已拥有的对象。
public class DeadLock {
public static String objA = "objA";//资源1
public static Integer objB = 1;//资源2
public static void main(String[] arg) {
Thread1 thread1=new Thread1();
Thread th1=new Thread(thread1);
Thread2 thread2=new Thread2();
Thread th2=new Thread(thread2);
th1.start();
th2.start();
}
}
class Thread1 implements Runnable {
@Override
public void run() {
try {
System.out.println("Thread1 is running ...");
synchronized (DeadLock.objA) {// Thread1先获取objA
System.out.println("Thread1 got the DeadLock.objA");
Thread.sleep(3000); // 等待Thread2获取资源objB
synchronized (DeadLock.objB){// Thread1再获取objB
System.out.println("Thread1 got the DeadLock.objB");
}
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
class Thread2 implements Runnable{
@Override
public void run() {
try {
System.out.println("Thread2 is running ...");
synchronized (DeadLock.objB){// Thread2先获取objB
System.out.println("Thread2 got the DeadLock.objB");
Thread.sleep(3000);// 等待Thread1获取资源objA
synchronized (DeadLock.objA){// Thread2再获取objA
System.out.println("Thread2 got the DeadLock.objA");
}
}
}catch (InterruptedException e){
e.printStackTrace();
}
}
}
结果:都执行不到第二个synchronized块内的代码。
Thread1 is running …
Thread1 got the DeadLock.objA
Thread2 is running …
Thread2 got the DeadLock.objB
三个线程同时启动,保证先后顺序:t1>t2>t3
利用join,在一个线程中加入另一个线程。
public class JoinTest2 {
public static void main(String[] args) {
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("t1");
}
});
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
try {
// 引用t1线程,等待t1线程执行完
t1.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("t2");
}
});
Thread t3 = new Thread(new Runnable() {
@Override
public void run() {
try {
// 引用t2线程,等待t2线程执行完
t2.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("t3");
}
});
t2.start();//这里三个线程的启动顺序可以任意
t3.start();
t1.start();
}
}
两个线程交替打印0和1、交替1~100
写法0:只用一个线程类+synchronized
class MyPrint implements Runnable{
int i = 1;
@Override
public void run() {
while (true){
synchronized (this){
this.notify();//在这里唤醒的目的是为保证拿到锁的线程只有一个 不
// 会立即释放锁 退出代码块才会释放锁
if (i <= 100 ){
System.out.println(Thread.currentThread().getName()+" "+i++);
}else {
break;
}
try {
this.wait();//打印过数据的线程等待 必须等到没打印过数字的拿到锁了才能唤醒
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
public class JiaotiPrint {
public static void main(String[] args) {
MyPrint print = new MyPrint();
Thread thread1 = new Thread(print);
Thread thread2 = new Thread(print);
// Thread thread3 = new Thread(print);
thread1.start();
thread2.start();
// thread3.start();
}
}
写法一:wait和notify方法配合
public class WaitNotifyToPrint {
static volatile int i = 0;
public static void main(String[] args) {
final Object object = new Object();
new Thread(() -> {
synchronized (object){
while (true){
if(i <= 100)
System.out.println(Thread.currentThread().getName() + ": 0 " + i++);
try{
object.notify();
Thread.sleep(500);
object.wait();
}catch (InterruptedException e){
e.printStackTrace();
}
}
}
},"t1").start();
new Thread(() -> {
synchronized (object){
while (true){
if(i <= 100)
System.out.println(Thread.currentThread().getName() + ": 1 " + i++);
try{
object.notify();
Thread.sleep(500);
object.wait();
}catch (InterruptedException e){
e.printStackTrace();
}
}
}
},"t2").start();
}
}
写法二:用LockSupport类的park和unpack方法配合
这里没有用匿名类是因为需要有变量名,作为参数传给unpark方法。
public class LockSupportToPrint {
private static Thread t1;
private static Thread t2;
private static volatile int i = 0;
public static void main(String[] args) {
t1 = new Thread(() -> {
while(true){
if(i <= 100)
System.out.println(Thread.currentThread().getName() + " 0 " + i++);
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
LockSupport.unpark(t2);//释放t2线程 设置锁标志位
LockSupport.park();//阻塞当前线程
}
},"t1");
t2 = new Thread(() -> {
while(true){
LockSupport.park();//阻塞当前线程
if(i <= 100)
System.out.println(Thread.currentThread().getName() + " 1 " + i++);
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
LockSupport.unpark(t1);//释放t1线程
}
},"t2");
t1.start();
t2.start();
}
}
写法三:ReentrantLock和condition(await和signal方法)
public class LockConditionToPrint {
public static void main(String[] args) {
char[] a1 = "1234567".toCharArray();
char[] a2 = "ABCDEFG".toCharArray();
Lock lock = new ReentrantLock();//锁
Condition t1 = lock.newCondition();//t1队列
Condition t2 = lock.newCondition();//t2队列
new Thread(() ->{
try{
lock.lock();
for(char c : a1){
System.out.print(c);
t2.signal();//唤醒t2队列中等待的线程
t1.await();//进入t1队列自旋等待
}
t1.signal();//避免有线程未被唤醒
t2.signal();//避免有线程未被唤醒
}catch (Exception e){
e.printStackTrace();
}finally {
lock.unlock();
}
},"t1").start();
new Thread(() ->{
try{
lock.lock();
for(char c : a2){
System.out.print(c);
t1.signal();//唤醒t1队列中等待的线程
t2.await();//进入t2队列自旋等待
}
t1.signal();//避免有线程未被唤醒
t2.signal();//避免有线程未被唤醒
}catch (Exception e){
e.printStackTrace();
}finally {
lock.unlock();
}
},"t2").start();
}
}
写法四:基于cas机制实现线程交替打印()
public class CasToPrint {
enum ReadyToRun{
T1,T2
}
private static volatile ReadyToRun readyToRun = ReadyToRun.T1;
public static void main(String[] args) {
char[] a1 = "1234567".toCharArray();
char[] a2 = "ABCDEFG".toCharArray();
new Thread(() ->{
for(char c : a1){
while (readyToRun != ReadyToRun.T1){}//cas自旋
System.out.print(c);
readyToRun = ReadyToRun.T2;//线程可见性
}
},"t1").start();
new Thread(() ->{
for(char c : a2){
while (readyToRun != ReadyToRun.T2){}//cas自旋
System.out.print(c);
readyToRun = ReadyToRun.T1;//线程可见性
}
},"t2").start();
}
}
生产者与消费者问题
这个问题是来模拟操作系统中多个线程之间的同步和互斥问题,是多线程同步问题的经典案例。该问题描述了共享固定大小缓冲区的两个线程——即所谓的“生产者”和“消费者”——在实际运行时会发生的问题。生产者的主要作用是生成一定量的数据放到缓冲区中,然后重复此过程。与此同时,消费者也在缓冲区消耗这些数据。该问题的关键就是要保证生产者不会在缓冲区满时加入数据,消费者也不会在缓冲区中空时消耗数据。
要解决该问题,就必须让生产者在缓冲区满时休眠(要么干脆就放弃数据),等到下次消费者消耗缓冲区中的数据的时候,生产者才能被唤醒,开始往缓冲区添加数据。同样,也可以让消费者在缓冲区空时进入休眠,等到生产者往缓冲区添加数据之后,再唤醒消费者。通常采用进程间通信的方法解决该问题。如果解决方法不够完善,则容易出现死锁的情况。出现死锁时,两个线程都会陷入休眠,等待对方唤醒自己。该问题也能被推广到多个生产者和消费者的情形。
package com.luowei.Algorithm;
public class ProducerComsumer {
public static void main(String[] args) {
Cherk cherk = new Cherk();
new Thread(new Producer(cherk),"生产者A").start();
new Thread(new Consumer(cherk),"消费者A").start();
}
}
class Cherk{//工具类,生产与消费的方法
private int product = 0; // 商品库存
public synchronized void product() {
if (product==10){// 10个商品为最大库存量,为此生产出一个商品
System.out.println("库存已满");
try {
this.wait();// 当库存满的时候,线程调用wait方法,会释放锁
} catch (InterruptedException e) {
e.printStackTrace();
}
}
product++;
System.out.println(Thread.currentThread().getName()+"生产了一个商品,当前库存为:"+product);
System.out.println("仓库有货,数量为:"+product);
this.notifyAll();// 唤醒其他等待的线程
}
// 消费方法,每次消费商品数量减一
public synchronized void consumer() {
if (product<=0){
System.out.println("没货了");
try {
this.wait();// 当缺货了,就不让消费了,等待生产者生产商品
} catch (InterruptedException e) {
e.printStackTrace();
}
}
product--;
System.out.println(Thread.currentThread().getName()+"消费了一个商品");
// 唤醒等待的线程
this.notifyAll();
}
}
class Producer implements Runnable{
private Cherk cherk;// 有一个Cherk类,供线程调用方法
public Producer(Cherk cherk) {
this.cherk = cherk;
}
public void run() {// 假设生产20次
for(int i = 0; i<20; i++){
cherk.product();
}
}
}
class Consumer implements Runnable{
private Cherk cherk;// 有一个Cherk类,供线程调用方法
public Consumer(Cherk cherk) {
this.cherk = cherk;
}
public void run() {
for(int i = 0; i<20; i++){
cherk.consumer();
}
}
}
哲学家就餐问题
多线程并发处理共享资源问题的模拟。
添加一个服务生,只有经过服务生允许后哲学家才能拿叉子,由服务生负责避免死锁。哲学家必须确定左右两边的叉子都可用后,才能同时拿起左右两边的叉子。给叉子编号,每位哲学家每次只能拿起编号较小的叉子,最后一位哲学家无法找到编号小的叉子,则不能拿起叉子。多余的叉子将由其他哲学家拿起。这种方式虽然避免了死锁,但是资源利用率不高。设计思想:
- 一个哲学家是一个线程,它不停地进行思考、吃饭。
- 为了模拟思考和吃饭的过程,该线程需要休眠一定时间。
- 共有5把叉子,每把叉子的状态为可用或不可用。
- 吃饭时,要求哲学家左右两边的叉子都是可用的,才能同时拿起叉子(将叉子的状态设置为不可用)。
- 哲学家放下叉子时,需要将叉子状态设置为可用,并通知在等待的其他哲学家。
public class PhilosopherProblem {
public static void main(String[] arg){
Fork fork=new Fork();
Philosopher philosopher0=new Philosopher("0",fork);
Philosopher philosopher1=new Philosopher("1",fork);
Philosopher philosopher2=new Philosopher("2",fork);
Philosopher philosopher3=new Philosopher("3",fork);
Philosopher philosopher4=new Philosopher("4",fork);
philosopher0.start();
philosopher1.start();
philosopher2.start();
philosopher3.start();
philosopher4.start();
}
}
class Philosopher extends Thread {
private String name;
private Fork fork;
public Philosopher(String name, Fork fork) {
super(name);// 更新自身线程名,同时需要设置自己的name
this.name=name;
this.fork = fork;
}
@Override
public void run() {
while (true) {
think();
fork.takeFork();
eat();
fork.putFork();
}
}
public void think() {
System.out.println("Philosopher_" + name + ": I'm thinking...");
try {
Thread.sleep(1000);// 模拟思考
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public void eat() {
System.out.println("Philosopher" + name + ": I'm eating...");
try {
Thread.sleep(1000);// 模拟吃饭
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
class Fork {
// 初始时,五支叉子都未被使用
private boolean[] fork = new boolean[5];
// 只有当左右两边的叉子都同时可用时,才能拿起叉子
public synchronized void takeFork() {
String name = Thread.currentThread().getName();
int i = Integer.valueOf(name);
while (fork[i] || fork[(i + 1) % 5]) {
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 同时拿起叉子,更新叉子状态
fork[i] = true;
fork[(i + 1) % 5] = true;
}
// 同时释放左右手的叉子并通知等待的哲学家
public synchronized void putFork() {
String name = Thread.currentThread().getName();
int i = Integer.valueOf(name);
// 更新叉子的状态
fork[i] = false;
fork[(i + 1) % 5] = false;
notifyAll(); // 通知在等待的哲学家
}
}
银行家算法
银行家算法是经典的避免死锁的算法。
要掌握银行家算法中的四个数据结构,算法流程,安全性算法原理。
避免死锁-----银行家算法详解
然后会写个简单的模拟。
Java模拟实现银行家算法
其他问题
实现一个容器,提供两个方法,add,size。写两个线程,线程1添加10个元素到容器中, 线程2实现监控元素的个数,当个数到5个时,线程2给出提示并结束。