JAVA多线程(二)同步

引入:

多线程提高了资源利用效率,但同时它也带来了线程安全的问题。比如在定电影票时,两个人都同时要定5排5座,或者是两个人同时给一张银行卡冲不同的钱,最后银行卡的钱是增加谁冲的呢 (其实就是说当两个线程同时去访问或改变一个资源时,线程是不安全的)?注意上面说的是同时,当然现实中不会出现那样的同时,因为同步方法已解决上面的问题了。

正解同步:

那什么是同步呢?这里千万不要误解!!!同步是协同步调,即在同一时刻,只能有一个线程访问临界资源,也就是同步互斥访问。怎么做到同步呢?通常来说,是在访问临界资源的代码前面加上一个锁,当访问完临界资源后释放锁,让其他线程继续访问。

实现:

一:  synchronizd关键字

1.同步代码块
  语法:
  synchronizd(同步锁)
{
  需要同步操作的代码 //其实这段代码就是临界区
 }
 在任何时候,最多允许一个线程拥有同步锁,谁拿到锁就进入代码块
一般把当前并发访问的共同资源作为同步监听对象.


// 同步代码块
public class SynchronizdDemo {
	public static void main(String[] args) {
		Apple1 a = new Apple1();
		new Thread(a, "小A").start();
		new Thread(a, "小B").start();
		new Thread(a, "小C").start();
	}
}


class Apple1 implements Runnable {
	private int num = 50;

	public void run() {
		
			for (int i = 0; i < 50; i++) {
				synchronized (this) {//this表示Apple1的对象,该对象属于多线程共享的资源
				if (num > 0) {
					// 模拟网络延迟
					try {
						Thread.sleep(10);
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
					System.out.println(Thread.currentThread().getName() + "吃了编号为" + num + "的苹果");
					num--;
				}
			}
		}
	}

}

2.同步方法
synchronizd 修饰的方法
synchronizd public void doWork()
{
方法体
}
对于非static方法,同步锁对象是this
对于static方法,使用当前方法所在类的字节码对象(Apple.class)

//使用同步方法
public class SynchronizdMethodDemo {
	public static void main(String[] args) {
		Apple2 a = new Apple2();
		new Thread(a, "小A").start();
		new Thread(a, "小B").start();
		new Thread(a, "小C").start();
	}
}
class Apple2 implements Runnable {
	private int num = 50;

	public void run() {

		for (int i = 0; i < 50; i++) {
           eat();
		}
	}
	synchronized private void eat() {
		if (num > 0) {
			// 模拟网络延迟

			try {
				Thread.sleep(10);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			System.out.println(Thread.currentThread().getName() + "吃了编号为" + num + "的苹果");
			num--;
		}
	}
}

3.synchronizd (锁对象){
存在线程安全单位代码
}
锁对象:锁对象可以是任意对象,多个线程使用的必须是同一把锁,也就是说必须是同一个对象。锁对象必须是唯一的

锁对象被释放:同步代码块执行完毕/线程进入等待状态时/线程停止时

 

注意事项:

1.synchronized(锁住的对象), synchronized(this)锁住的只是对象本身,同一个类的不同对象调用的synchronized方法并不会被锁住,而synchronized(className.class)实现了全局锁的功能,所有这个类的对象调用这个方法都受到锁的影响,此外()中还可以添加一个具体的对象,实现给具体对象加锁。

2.不要用synchronizd修饰run方法,修饰之后,某一个线程就执行完了所有的功能,好比多个线程出现串行。
解决:把需要同步操作的代码定义在一个新的方法中,用synchronizd修饰该方法,再在run方法中调用此方法。

3.还有你有没有考虑过如果要把String类或基本类型作为对象监视器可以吗? 当然是不可以的,因为使用基本类型(JVM为它们维护了一个常量池)时会自动装箱得到新的值,即变量指向了新的对象,导致对象监视器前后不一致。

解决:设为常量,用 final 修饰,保证一直都是同一个对象监视器。

synchronized下的等待/通知机制的经典使用:生产消费者模型

//生产者与消费者互斥使用仓库
    public static List<String>  warehouse = new LinkedList<>();
public static void main(String[] args) {
        //生产者线程
        Thread thread_1 = new Thread(){
             @Override
             public void run() {
                //对象监视器为warehouse,必须先获取这个对象监视器
                synchronized ( warehouse) {
                    int i = 0;
                    //生产10个商品
                    while(warehouse.size()<=10){
                        ++i;
                    //生产商品,添加进仓库
                     warehouse.add("生产了商品goods"+i);
                     //当商品数量足够时,便唤醒消费者线程
                     if(warehouse.size()>=10){
                         warehouse.notify();
                         //生产任务完成,跳出循环,结束运行,从而可以释放锁
                         break;
                    } 
                }
                }
            }
         };

         //消费者线程
        Thread thread_2 = new Thread(){
            @Override
            public void run() {
                synchronized (warehouse) {
                    try {//如果仓库的商品数量不能满足消费者
                        if(warehouse.size()<10){
                            //消费者进入等待队列,等待被唤醒
                            warehouse.wait();
                        }
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }

                   //消费商品
                    for(String goods:warehouse){
                        System.out.println("消费者消费了商品:"+goods);
                    }
                }
            }
         };
         //
         thread_2.start();
         thread_2.setPriority(Thread.MAX_PRIORITY);
         thread_1.start();
    }

二、显式锁 Lock

Lock相比与synchronized的优点:

Case 1 :
在使用synchronized关键字的情形下,假如占有锁的线程由于要等待IO或者其他原因(比如调用sleep方法)被阻塞了,但是又没有释放锁,那么其他线程就只能一直等待,别无他法。这会极大影响程序执行效率。因此,就需要有一种机制可以不让等待的线程一直无期限地等待下去(比如只等待一定的时间 (解决方案:tryLock(long time, TimeUnit unit)) 或者 能够响应中断 (解决方案:lockInterruptibly())),这种情况可以通过 Lock 解决。


Case 2 :
我们知道,当多个线程读写文件时,读操作和写操作会发生冲突现象,写操作和写操作也会发生冲突现象,但是读操作和读操作不会发生冲突现象。但是如果采用synchronized关键字实现同步的话,就会导致一个问题,即当多个线程都只是进行读操作时,也只有一个线程在可以进行读操作,其他线程只能等待锁的释放而无法进行读操作。因此,需要一种机制来使得当多个线程都只是进行读操作时,线程之间不会发生冲突。同样地,Lock也可以解决这种情况 (解决方案:ReentrantReadWriteLock) 


Case 3 :
我们可以通过Lock得知线程有没有成功获取到锁 (解决方案:ReentrantLock) ,但这个是synchronized无法办到的。

因此也可想而知,Lock方式是需要手动获取锁和释放锁。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值