手动上锁,自动释放锁。
锁的是对象有两种:1、类的实例,指向堆里的唯一的实例;2、类的字节码。
package com.sync;
import java.util.concurrent.TimeUnit;
public class ChangeOfLockObject {
Object o = new Object();
public void test(){
synchronized (o) {
while (true) {
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName());
}
}
}
public static void main(String[] args) {
ChangeOfLockObject demo = new ChangeOfLockObject();
new Thread(demo :: test, "t1").start();
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
Thread t2 = new Thread(demo :: test, "t2");
demo.o = new Object();
//t2能否执行?
t2.start();
}
}
Sync锁的是一个对象,但是如果实例变了,证明这个锁变了,证明以上程序用的不是同一把锁
public class constantObject{
String s1 = "hello";
String s2 = "hello";
public void test1(){
synchronized (s1) {
System.out.println("t1 start...");
try {
TimeUnit.SECONDS.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("t1 end...");
}
}
public void test2(){
synchronized (s2) {
System.out.println("t2 start...");
}
}
public static void main(String[] args) {
constantObject demo = new constantObject();
new Thread(demo :: test1,"test1").start();
new Thread(demo :: test2,"test2").start();
}
}
以上的test1和test2其实锁定的事同一个对象。使用new String()可以。
public class SynchronizationBlockCode {
int count = 0;
public synchronized void test1(){
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
count ++;
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public void test2(){
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (this) {
count ++;
}
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
业务逻辑中只有count++这句需要sync,这时不应该给整个方法上锁。采用细粒度的锁,可以使线程争用时间变短,从而提高效率
public class Demo{
public synchronized void test1(){
System.out.println(Thread.currentThread().getName() + " test1 start...");
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " test1 end...");
}
public void test2(){
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " test2");
}
public static void main(String[] args) {
Demo demo = new Demo();
new Thread(demo :: test1,"test1").start();
new Thread(demo :: test2,"test2").start();
}
}
可以同时调用
public class Demo {
String name;
double balance;
public synchronized void set(String name,double balance){
this.name = name;
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
this.balance = balance;
}
public /*synchronized*/ double getBalance(String name){
return this.balance;
}
public static void main(String[] args) {
Demo demo = new Demo();
new Thread(()->demo.set("aaa",100.0)).start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(demo.getBalance("aaa"));//
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(demo.getBalance("aaa"));
}
}
public class Demo {
int count = 0;
synchronized void test(){
System.out.println(Thread.currentThread().getName() + " start......");
while (true) {
count ++;
System.out.println(Thread.currentThread().getName() + " count = " + count);
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (count == 5) {
//碰到异常的情况,如果没有处理,会自动释放锁,所以T2可以执行。
int i = 1/0;
}
}
}
public static void main(String[] args) {
Demo demo11 = new Demo();
Runnable r = () -> demo11.test();
new Thread(r, "t1").start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(r, "t2").start();
}
}
可以执行
public class Demo {
boolean running = true;
public void test(){
System.out.println("test start...");
while (running){
}
System.out.println("test end...");
}
public static void main(String[] args) {
Demo demo = new Demo();
new Thread(demo :: test,"t1").start();
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
demo.running = false;
}
}
mian,t1线程都用到一个变量,java默认是T1线程中保留一份副本,这样如果main线程修改了该变量,
t1线程未必知道
使用volatile关键字,会让所有线程都会读到变量的修改值
在下面的代码中,running是存在于堆内存的t对象中
当线程t1开始运行的时候,会把running值从内存中读到t1线程的工作区,在运行过程中直接使用这个副本,
并不会每次都去读取堆内存,这样,当主线程修改running的值之后,t1线程感知不到,所以不会停止运行
public class Demo {
volatile int count = 0;
public void test(){
for (int i = 0; i < 10000; i++) {
count ++;
}
}
public static void main(String[] args) {
Demo demo = new Demo();
List<Thread> threads = new ArrayList();
for (int i = 0; i < 10; i++) {
threads.add(new Thread(demo::test, "thread-" + i));
}
threads.forEach((o)->o.start());
threads.forEach((o)->{
try {
o.join();
} catch (Exception e) {
e.printStackTrace();
}
});
System.out.println(demo.count);
}
}
比如说第一个线程加到100了,还没往上加,另外一个线程来了,把100拿过来执行方法,
然后第一个线程继续加到101,第二个线程也加到101,他两往回写都是101,线程不会管你加到哪儿了,
虽然说加了2但是实际上只加了1.
volatile并不能保证多个线程共同修改running变量时所带来的不一致问题,
public class Demo {
int count = 0;
//相比较上一个例子,synchronized既保证了原子性又保证了可见性
public synchronized void test(){
for (int i = 0; i < 10000; i++) {
count ++;
}
}
public static void main(String[] args) {
Demo demo = new Demo();
List<Thread> threads = new ArrayList<Thread>();
for (int i = 0; i < 10; i++) {
threads.add(new Thread(demo::test, "thread-" + i));
}
threads.forEach((o)->o.start());
threads.forEach((o)->{
try {
o.join();
} catch (Exception e) {
e.printStackTrace();
}
});
System.out.println(demo.count);
}
}
能够保证输出结果10000
public class Demo {
AtomicInteger count = new AtomicInteger(0);
public void test(){
for (int i = 0; i < 10000; i++) {
if(count.get() < 1000){
count.incrementAndGet();
}
}
}
public static void main(String[] args) {
Demo demo = new Demo();
List<Thread> threads = new ArrayList();
for (int i = 0; i < 10; i++) {
threads.add(new Thread(demo::test, "thread-" + i));
}
threads.forEach((o)->o.start());
threads.forEach((o)->{
try {
o.join();
} catch (Exception e) {
e.printStackTrace();
}
});
System.out.println(demo.count);
}
}
比如count加到999了,这时候一个线程拿到count判断,虽然.get方法保证原子性,但是他阻止不了其它线程也来判断,所以第一个线程还没加完,第二个线程也进来了,这时候两个线程都给count加了
public class ContainersCDL {
volatile List lists = new ArrayList();
public void add(Object o){
lists.add(0);
}
public int size(){
return lists.size();
}
public static void main(String[] args){
ContainersCDL c = new ContainersCDL();
CountDownLatch latch = new CountDownLatch(1);
new Thread(()->{
System.out.println("t2 start.....");
if(c.size()!=5){
try {
latch.await();
}catch (Exception e){
e.printStackTrace();
}
System.out.println("t2 end....");
}
}," t2").start();
new Thread(()->{
System.out.println("t1 start...");
for (int i =0;i<10;i++){
c.add(new Object());
System.out.println("add "+i);
if(c.size()==5){
latch.countDown();
}try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"t1").start();
}
}
CountDownLatch 使用await和countdown方法替代wait(释放锁)和notify(不释放锁)
CountDownLatch不涉及锁定,当count的值为零时当前线程继续运行。相当于是发令枪,运动员线程调用await等待,计数到0开始运行
当不涉及同步,只是涉及线程通信的时候,用synchronized加wait,notify就显得太重了
###11、面试题:写一个固定容量同步容器,拥有Put和get方法,以及getCount方法,能够支持两个生产者线程以及10个消费者线程的阻塞调用
wait 阻塞
notifyAll会唤醒所有的线程,无法精准唤醒某一个线程
signalAll会唤醒指定的那些线程
public class ConditionLock<T> {
private final LinkedList<T> lists = new LinkedList<>();
private final int MAX = 10;
private int count = 0;
private Lock lock = new ReentrantLock();
private Condition producer = lock.newCondition();
private Condition consumer = lock.newCondition();
public void put(T t){
try {
lock.lock();
while (lists.size()==MAX){
producer.await();
}
lists.add(t);
++count;
consumer.signalAll();
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
lock.unlock();
}
}
public T get(){
T t = null;
try {
lock.lock();
while (lists.size()==0){
consumer.await();
}
t = lists.removeFirst();
count--;
producer.signalAll();
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
lock.unlock();
}
return t;
}
public static void main(String[] args) {
ConditionLock<String> c = new ConditionLock<>();
for (int i = 0;i<100;i++){
new Thread(()->{
for (int j = 0;j<5;j++){
System.out.println("get: "+c.get());
}
},"c"+i).start();
}
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
for (int i =0;i<2;i++){
new Thread(()->{
for (int j =0;j<25;j++){
c.put(Thread.currentThread().getName()+" "+j);
}
},"p"+i).start();
}
}
}
只能在设置的线程中存在。线程的本地变量,与使用的线程绑定,其他线程不会get到。
线程并发高的情况下不要使用ThreadLocal
# synchronized
public class SaleOfTickets {
private static List<Integer> tickets = new LinkedList<>();
static {
for (int i=0;i<10000;i++){
tickets.add(i);
}
}
public static void main(String[] args) {
for (int i = 0;i<10;i++){
int finalI = i;
new Thread(()->{
while (true){
synchronized (tickets){
if (tickets.size()<=0){
break;
}
System.out.println(finalI +"窗口销售票编号:"+tickets.remove(0));
}
}
}).start();
}
}
}
这里使用synchronized使两个操作具备了原子性,不会出问题
public class ConcurrentLinkQueueTicket {
private static Queue<Integer> tickets = new ConcurrentLinkedDeque<>();
static {
for (int i=0;i<10000;i++){
tickets.add(i);
}
}
public static void main(String[] args) {
for (int i = 0;i<10;i++){
int finalI = i;
new Thread(()->{
while (true){
Integer poll = tickets.poll();
if (poll==null){
break;
}
System.out.println(finalI +"窗口销售票编号"+poll);
}
}).start();
}
}
}
在JDK1.5以后,java里面提供了很多的并发容器,这里我们用的是一个queue,队列。
所谓队列其实就是一个容器,就是站成一对,不管票还是人都在里面排成一堆,队列有几种,有先进先出的,
还有两端的队列,还有就是栈,先进后出,先加进去的后出来。
这里用了一个concurrentlinkedqueue,并发的链表队列。线程里面调用了一个poll方法,
意思是往外面拿一个数据,相当于在尾巴里面拿一个,如果没有拿到,他的返回值就是空,那么就中断线程。
这里面没有加锁,同样有判断,但是不会出问题。完成卖票功能这种效率是比较高的。queue里面是不能装空值。
这里虽然判断和操作是一起的,但是我们没有在判断里面有任何操作,大不了反过头来再拿一边,
poll底层实现是cas,这里我们就不用加锁了。
public class Demo {
public static void main(String[] args) {
// List lists = new ArrayList<>();
// List lists = new Vector<>();
List<String> lists = new CopyOnWriteArrayList<>();
Random r = new Random();
Thread[] threads = new Thread[100];
for (int i = 0; i < threads.length; i++) {
Runnable task = new Runnable() {
@Override
public void run() {
for (int j = 0; j < 1000; j++) {
lists.add("A" + r.nextInt(10000));
}
}
};
threads[i] = new Thread(task);
}
run(threads);
System.out.println(lists.size());
}
private static void run(Thread[] threads) {
long start = System.currentTimeMillis();
Arrays.asList(threads).forEach(t->t.start());
Arrays.asList(threads).forEach(t->{
try {
t.join();
} catch (Exception e) {
e.printStackTrace();
}
});
long end = System.currentTimeMillis();
System.out.println(end - start);
}
}
在往集合中添加数据的时候,先拷贝存储的数组,然后添加元素到拷贝好的数组中,
然后用现在的数组去替换成员变量的数组(就是get等读取操作读取的数组)。
这个机制和读写锁是一样的,但是比读写锁有改进的地方,那就是读取的时候可以写入的 ,
这样省去了读写之间的竞争,看了这个过程,你也发现了问题,同时写入的时候怎么办呢,当然果断还是加锁。读多写少可以用copyonwritelist
public class Demo {
public static void main(String[] args) {
ArrayList<String> arrayList = new ArrayList<>();
List<String> synchronizedList = Collections.synchronizedList(arrayList);
}
}
collections是java里面一个集合处理类,里面有给容器加锁的方法,通过调用api可以返回一个加了锁的容器
public class Demo {
public static void main(String[] args) {
Queue<String> strings = new ConcurrentLinkedQueue<>();
for (int i = 0; i < 10; i++) {
//offer,类似于add方法,add会出一些问题,比如容量限制,
//超出限制会抛异常,offer有返回值可以判断是否加成功了
strings.offer("a" + i);
}
System.out.println(strings);
System.out.println(strings.size());
System.out.println(strings.poll());//拿了就没了
System.out.println(strings.size());
System.out.println(strings.peek());//用一下不删
System.out.println(strings.size());
}
}
public class Demo {
public static void main(String[] args) {
// Map map = new ConcurrentHashMap<>();
Map<String, String> map = new ConcurrentSkipListMap<>();
// Map map = new Hashtable<>();
// Map map = new HashMap<>();
// Map map1 = Collections.synchronizedMap(map);
Random random = new Random();
Thread[] threads = new Thread[100];
CountDownLatch latch = new CountDownLatch(threads.length);
long start_time = System.currentTimeMillis();
for (int i = 0; i < threads.length; i++) {
threads[i] = new Thread(()->{
for(int j=0; j<10000;j++) {
map.put("a" + random.nextInt(100000), "a" + random.nextInt(100000));
// map1.put("a" + random.nextInt(100000), "a" + random.nextInt(100000));
}
latch.countDown();
});
}
Arrays.asList(threads).forEach(t->t.start());
try {
latch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
long end_time = System.currentTimeMillis();
System.out.println(end_time-start_time);
}
}
第一种用hashtable,hashtable所有方法都加了锁了,
第二种concurrenthashmap,大致能看出来他的效率要比hashtable要高一些,在多线程的情况下。
为什么呢,因为hashtable往里面加任何数据的时候都是要锁定整个对象,
而concurrenthashmap,是分成十六个段,每次插数据的时候,只会锁住一小段,1.8之后实现不同。
public class Demo {
private static BlockingQueue<String> strings = new LinkedBlockingQueue<>(10);
public static void main(String[] args) {
new Thread(()->{
for (int i = 0; i < 100; i++) {
try {
// 在阻塞式容器里面加了一个方法,put,也就是如果满了就会等待,对应的方法叫take,如果空了就会等待。
// 这种容器我们去用的时候自动就实现了阻塞式的生产者消费者。
strings.put("商品" + i);
} catch (Exception e) {
e.printStackTrace();
}
}
}, "producer").start();
for (int i = 0; i < 5; i++) {
new Thread(()->{
for(;;){
try {
// take,拿,如果空了也会阻塞
System.out.println(Thread.currentThread().getName() + " take " + strings.take()); //如果空了,就会等待
} catch (Exception e) {
e.printStackTrace();
}
}
},"consumer" + i).start();
}
}
}
public class Demo {
private static BlockingQueue<String> strings = new ArrayBlockingQueue<>(10);
public static void main(String[] args) throws InterruptedException {
for (int i = 0; i < 10; i++) {
strings.put("a" + i);
}
strings.add("aaaa");
// strings.put("aaaa");
// strings.offer("aaaa");
strings.offer("aaaa",1, TimeUnit.SECONDS);
System.out.println(strings);
}
}
有界队列,意思就是说这个队列能装的元素的个数是固定的,后面讲线程池的时候,里面装的其实是一个个任务。
这里只能装10个,如果超过了可能会出问题可能会阻塞,这里看你调用什么方法。
add会报异常
offer不会报异常,他只通过布尔类型的返回值来告诉你是加成功了还是没有加成功。
offer可以设置时间,如果这段时间加不进去就不加了也就是返回false
put方法是满了会阻塞住。
public class Demo {
private static BlockingQueue<MyTask> tasks = new DelayQueue<>();
static class MyTask implements Delayed{
long runningTime;
public MyTask(long rt) {
this.runningTime = rt;
}
@Override
public int compareTo(Delayed o) {
if (this.getDelay(TimeUnit.MILLISECONDS) < o.getDelay(TimeUnit.MICROSECONDS)) {
return -1;
}else if (this.getDelay(TimeUnit.MILLISECONDS) > o.getDelay(TimeUnit.MILLISECONDS)) {
return 1;
}else {
return 0;
}
}
@Override
public long getDelay(TimeUnit unit) {
return unit.convert(runningTime - System.currentTimeMillis(), TimeUnit.MILLISECONDS);
}
@Override
public String toString() {
return "" + runningTime;
}
public static void main(String[] args) throws InterruptedException {
long now = System.currentTimeMillis();
MyTask t1 = new MyTask(now+1000);
MyTask t2 = new MyTask(now+2000);
MyTask t3 = new MyTask(now+1500);
MyTask t4 = new MyTask(now+2500);
MyTask t5 = new MyTask(now+500);
tasks.put(t1);
tasks.put(t2);
tasks.put(t3);
tasks.put(t4);
tasks.put(t5);
System.out.println(tasks);
for (int i = 0; i < 5; i++) {
System.out.println(tasks.take());
}
}
}
}
容器里每一个元素都设置了一个时间,时间到了才能从中提取元素
public class Demo {
public static void main(String[] args) throws InterruptedException {
LinkedTransferQueue<String> strings = new LinkedTransferQueue<>();
new Thread(()->{
try {
System.out.println("t1"+strings.take());
} catch (Exception e) {
e.printStackTrace();
}
}).start();
new Thread(()->{
try {
System.out.println("t2"+strings.take());
} catch (Exception e) {
e.printStackTrace();
}
}).start();
TimeUnit.SECONDS.sleep(2);
strings.transfer("aaa");
// strings.put("aaa");
System.out.println(strings.size());
// new Thread(()->{
// try {
// System.out.println(strings.take());
// } catch (Exception e) {
// e.printStackTrace();
// }
// }).start();
}
}
和普通的queue的方法差不多,多了一个transfer方法。
如果你用这种队列的话,往往是消费者先启动,生产者生产一个东西的时候,他先是去找消费者,
如果有消费者就直接丢给消费者。
public class Demo {
public static void main(String[] args) throws InterruptedException {
BlockingQueue<String> strings = new SynchronousQueue<>();
new Thread(()->{
try {
System.out.println(strings.take());
} catch (Exception e) {
e.printStackTrace();
}
}).start();
// strings.add("aaa");
strings.put("aaa");
strings.put("aaa");
strings.put("aaa");
strings.put("aaa");
strings.put("aaa");
System.out.println(strings.size());
}
}
同步队列是容量为0,也就是来的东西必须给消费掉.
首先启动一个消费者,调用add方法,他报错了
只能调用put,意思就是阻塞等待消费者消费。put里面其实用的是transfer,任何东西必须消费,不能往容器里面扔。