线程的同步问题
当我们使用多线程对同一块数据进行操作的时候,会出现线程的同步,导致数据的错误,所以我们要对多线程的公共数据块上锁。
同步机制synchronized:synchronized关键字用于修饰方法或者单独的synchronized代码块,当一个线程想执行synchronized中的内容时,必须先获取到对象锁,当对象锁没有线程占用时,进入synchronized方法会自动获取到对象锁,执行完毕后会自动释放锁,如果对象锁被A线程占用,B线程想执行synchronized的代码只能等待A个线程执行完毕后, 释放对象锁,B线程才能获取到对象锁进入方法执行。一个线程获得对象A的锁,也可以获得对象B的锁,两个不同类的对象锁没有关联。如果把锁放在每个线程中 则每个线程执行方法时,都获取的是自己的锁,都可以获得许可,所以依旧会产生线程同步。
生产者消费者问题(Producer-consumer problem),也称有限缓冲问题(Bounded-buffer problem),是一个多线程同步问题的经典案例。生产者生成一定量的数据放到缓冲区中,然后重复此过程;与此同时,消费者也在缓冲区消耗这些数据。生产者和消费者之间必须保持同步,要保证生产者不会在缓冲区满时放入数据,消费者也不会在缓冲区空时消耗数据。不够完善的解决方法容易出现死锁的情况,此时进程都在等待唤醒。所以可用wait()和notifyAll()方法来等待、唤醒线程。
先举一个锁放在线程中错误的例子:
一个线程获得对象A的锁,也可以获得对象B的锁,两个不同类的对象锁没有关联。如果把锁放在每个线程中 则每个线程执行方法时,都获取的是自己的锁,都可以获得许可,所以依旧会产生线程同步。
Product:
public class Product {
private String name;
private int num;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getNum() {
return num;
}
public void setNum(int num) {
this.num = num;
}
}
CThread(ConsumerThread):
public class CThread extends Thread{
Product pro=new Product();
public CThread(Product pro) {
this.pro=pro;
}
public void run() {
//消费者消费
while(true) {
synchronized(pro) {
if(pro.getNum()<=0) {
System.out.println(this.currentThread().getName()+"消费者访问,已经消费完了");
try {
pro.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
try {
Thread.sleep(500);
} catch (InterruptedException e) {
// TODO 自动生成的 catch 块
e.printStackTrace();
}
pro.notifyAll();
System.out.println(this.currentThread().getName()+"消费者消费1个");
pro.setNum(pro.getNum()-1);
System.out.println("当前数量为:"+pro.getNum());
}
}
}
}
PThread(ProduceThread):
public class PThread extends Thread{
Product pro=new Product();
public PThread(Product pro) {
this.pro=pro;
}
public void run() {
//生产者生产
synchronized(pro) {
while(true) {
synchronized(pro) {
if(pro.getNum()>=5) {
System.out.println(this.currentThread().getName()+"生产者访问,已经满了");
try {
pro.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO 自动生成的 catch 块
e.printStackTrace();
}
pro.notifyAll();
System.out.println(this.currentThread().getName()+"生产者生产1个");
pro.setNum(pro.getNum()+1);
System.out.println("当前数量为:"+pro.getNum());
}
}
}
}
}
Test:
public class test {
public static void main(String args[]) {
Product pro=new Product();
pro.setNum(4);
PThread p=new PThread(pro);
CThread c=new CThread(pro);
Thread p1=new Thread(p,"p1");
Thread p2=new Thread(p,"p2");
Thread p3=new Thread(p,"p3");
Thread c1=new Thread(c,"c1");
Thread c2=new Thread(c,"c2");
p1.start();
p2.start();
p3.start();
c1.start();
c2.start();
}
}
以上代码虽然是有锁了,但是锁在了线程中,测试时,每个线程都会实例化一个自己的锁,所以每次申请的都是自己的锁,没有其他的线程争夺,所以依旧是会出现线程同步问题。
正确代码:
public class Consumer extends Thread{
private Product pro;
public Consumer(Product pro) {
this.pro=pro;
}
public void run() {
while(true) {
try {
Thread.sleep(3000);
pro.consumer();
} catch (InterruptedException e) {
// TODO 自动生成的 catch 块
e.printStackTrace();
}
}
}
}
public class Producer extends Thread{
private Product pro;
public Producer(Product pro) {
this.pro=pro;
}
public void run() {
while(true) {
try {
Thread.sleep(1000);
pro.produce();
} catch (InterruptedException e) {
// TODO 自动生成的 catch 块
e.printStackTrace();
}
}
}
}
public class Product {
private LinkedList data=new LinkedList();
public void produce() throws InterruptedException{
synchronized(data) {
if(data.size()==5) {
System.out.println("已经满了");
data.wait();
}
data.add(new Object());
System.out.println(Thread.currentThread().getName()+"生产一个,现在有"+data.size()+"个");
data.notifyAll();
}
}
public void consumer() throws InterruptedException{
synchronized(data) {
if(data.size()==0) {
System.out.println("已经没有了");
data.wait();
}
data.remove();
System.out.println(Thread.currentThread().getName()+"消费一个现在还有"+data.size()+"个");
data.notifyAll();
}
}
}
public class Test {
public static void main(String args[]) {
Product pro=new Product();
Producer p=new Producer(pro);
Consumer c=new Consumer(pro);
Producer p1=new Producer(pro);
Consumer c1=new Consumer(pro);
p.start();
p1.start();
c.start();
c1.start();
}
}
这样每个线程访问的就是同一个资源了,当多个线程同时访问时,会只有一个线程进去,然后其他线程进行等待。
如果各位有兴趣的话,可以去看看这个帖子,这为作者还提供了了其他的方法
Java多种方式解决生产者消费者问题(十分详细)------Xiyou_limeng