synchronized的使用
解法一:wait/notify和synchronized的组合
import java.util.LinkedList; import java.util.List; import java.util.concurrent.Semaphore; /** * 实现一个容器,提供add,size方法 * 两个线程,线程一添加十个元素到容器中,线程二监控元素的个数,当个数到5时,线程二给出提示并结束 * * @author zab * @date 2019-10-20 21:54 */ public class ThreadCommunicationTest1 { private volatile MyContainer1 myContainer1 = new MyContainer1(); public void f1() { synchronized (this) { for (int i = 1; i <= 10; i++) { myContainer1.add("test"); System.out.println("add" + i); if(i == 5){ this.notify();//唤醒其他等待的锁 try { this.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } } } } public void f2() { synchronized (this) { try { this.wait(); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("size等于5啦!!!"); this.notify(); } } public static void main(String[] args) { ThreadCommunicationTest1 t = new ThreadCommunicationTest1(); new Thread(t::f2).start(); new Thread(t::f1).start(); } } class MyContainer1 { List<String> list = new LinkedList<>(); public void add(String s) { list.add(s); } public int size() { return list.size(); } }
多次运行结果如下:
wait/notify的解法比较常规,大概逻辑是,f2方法启动的线程先运行,由于f2方法一来就加锁等待,释放锁,f1方法的线程获得锁,循环输出1、2、3、4、5,当输出5时,叫醒f2,同时自己wait,释放锁,f2得以执行,输出"size等于5啦!!!",输出完毕过后叫醒正在wait的f1,f1得以执行下面的输出。
方法二:lock、condition组合
import java.util.LinkedList; import java.util.List; import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; /** * 实现一个容器,提供add,size方法 * 两个线程,线程一添加十个元素到容器中,线程二监控元素的个数,当个数到5时,线程二给出提示并结束 * * @author zab * @date 2019-10-20 21:54 */ public class ThreadCommunicationTest2 { private volatile MyContainer2 myContainer2 = new MyContainer2(); private Lock lock = new ReentrantLock(); private Condition c1 = lock.newCondition(); private Condition c2 = lock.newCondition(); public void f1() { lock.lock(); for (int i = 1; i <= 10; i++) { System.out.println("add" + i); if (i == 5) { c2.signal(); try { c1.await(); } catch (InterruptedException e) { e.printStackTrace(); } } } lock.unlock(); } public void f2() { lock.lock(); try { c2.await(); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("size等于5啦!!!"); c1.signal(); lock.unlock(); } public static void main(String[] args) { ThreadCommunicationTest2 t = new ThreadCommunicationTest2(); new Thread(t::f2).start(); new Thread(t::f1).start(); } } class MyContainer2 { List<String> list = new LinkedList<>(); public void add(String s) { list.add(s); } public int size() { return list.size(); } }
synchronize和Lock锁的区别
为什么java已经通过synchronized关键字实现同步访问了,还需要提供Lock?
synchronized的缺陷
前面博客有提到过释放对象的锁有两种情况:
程序执行完同步代码块会释放代码块。
程序在执行同步代码块是出现异常,JVM会自动释放锁去处理异常。
如果获取锁的线程需要等待I/O或者调用了sleep()方法被阻塞了,但仍持有锁,其他线程只能干巴巴的等着,这样就会很影响程序效率。
因此就需要一种机制,可以不让等待的线程已知等待下去,比如值等待一段时间或响应中断,Lock锁就可以办到。
再举个例子:当有多个线程读写文件时,读操作和写操作会发生冲突现象,写操作和写操作会发生冲突现象,但是读操作和读操作不会发生冲突现象。
但是采用synchronized关键字来实现同步的话,就会导致一个问题:如果多个线程都只是进行读操作,所以当一个线程在进行读操作时,其他线程只能等待无法进行读操作。
因此就需要一种机制来使得多个线程都只是进行读操作时,线程之间不会发生冲突,通过Lock就可以办到。
另外,Lock可以知道线程有没有得到锁,而synchronized不能。
总结区别
总结来说,Lock与synchronized有以下区别:
Lock是一个接口,而synchronized是关键字。
synchronized会自动释放锁,而Lock必须手动释放锁。
Lock可以让等待锁的线程响应中断,而synchronized不会,线程会一直等待下去。
通过Lock可以知道线程有没有拿到锁,而synchronized不能。
Lock能提高多个线程读操作的效率。
synchronized能锁住类、方法和代码块,而Lock是块范围内的
Java多线程中Lock的使用
Jdk1.5以后,在java.util.concurrent.locks包下,有一组实现线程同步的接口和类,说到线程的同步,可能大家都会想到synchronized关键字,
这是java内置的关键字,用来处理线程同步的,但这个关键字有很多的缺陷,使用起来也不是很方便和直观,所以就出现了Lock,下面,我们
就来对比着讲解Lock。
通常我们在使用synchronized关键字的时候会遇到下面这些问题:
(1)不可控性,无法做到随心的加锁和释放锁。
(2)效率比较低下,比如我们现在并发的读两个文件,读与读之间是互不影响的,但如果给这个读的对象使用synchronized来实现同步的话,
那么只要有一个线程进入了,那么其他的线程都要等待。
(3)无法知道线程是否获取到了锁。
而上面synchronized的这些问题,Lock都可以很好的解决,并且jdk1.5以后,还提供了各种锁,例如读写锁,但有一点需要注意,使用synchronized
关键时,无须手动释放锁,但使用Lock必须手动释放锁。下面我们就来学习一下Lock锁。
Lock是一个上层的接口,其原型如下,总共提供了6个方法:
public interface Lock { // 用来获取锁,如果锁已经被其他线程获取,则一直等待,直到获取到锁 void lock(); // 该方法获取锁时,可以响应中断,比如现在有两个线程,一个已经获取到了锁,另一个线程调用这个方法正在等待锁,但是此刻又不想让这个线程一直在这死等,可以通过 调用线程的Thread.interrupted()方法,来中断线程的等待过程 void lockInterruptibly() throws InterruptedException; // tryLock方法会返回bool值,该方法会尝试着获取锁,如果获取到锁,就返回true,如果没有获取到锁,就返回false,但是该方法会立刻返回,而不会一直等待 boolean tryLock(); // 这个方法和上面的tryLock差不多是一样的,只是会尝试指定的时间,如果在指定的时间内拿到了锁,则会返回true,如果在指定的时间内没有拿到锁,则会返回false boolean tryLock(long time, TimeUnit unit) throws InterruptedException; // 释放锁 void unlock(); // 实现线程通信,相当于wait和notify,后面会单独讲解 Condition newCondition(); }
那么这几个方法该如何使用了?前面我们说到,使用Lock是需要手动释放锁的,但是如果程序中抛出了异常,那么就无法做到释放锁,有可能引起死锁,
所以我们在使用Lock的时候,有一种固定的格式,如下:
Lock l = ...;
l.lock();
try {
// access the resource protected by this lock
} finally {// 必须使用try,最后在finally里面释放锁
l.unlock();
}
下面我们来看一个简单的例子,代码如下:
/** * 描述:Lock使用 */ public class LockDemo { // new一个锁对象,注意此处必须声明成类对象,保持只有一把锁,ReentrantLock是Lock的唯一实现类 Lock lock = new ReentrantLock(); public void readFile(String fileMessage){ lock.lock();// 上锁 try{ System.out.println(Thread.currentThread().getName()+"得到了锁,正在读取文件……"); for(int i=0; i<fileMessage.length(); i++){ System.out.print(fileMessage.charAt(i)); } System.out.println(); System.out.println("文件读取完毕!"); }finally{ System.out.println(Thread.currentThread().getName()+"释放了锁!"); lock.unlock(); } } public void demo(final String fileMessage){ // 创建若干个线程 ExecutorService service = Executors.newCachedThreadPool(); // 提交20个任务 for(int i=0; i<20; i++){ service.execute(new Runnable() { @Override public void run() { readFile(fileMessage); try { Thread.sleep(20); } catch (InterruptedException e) { e.printStackTrace(); } } }); } // 释放线程池中的线程 service.shutdown(); } }