转载(https://www.cnblogs.com/soundcode/p/6295910.html)加上了自己的补充和理解
java允许多线程并发控制,当多个线程同时操作一个可共享的资源变量时(如数据的增删改查), 将会导致数据不准确,相互之间产生冲突,因此加入同步锁以避免在该线程没有完成操作之前,被其他线程的调用, 从而保证了该变量的唯一性和准确性。
public synchronized void aMethod() {
// do something
}
这也就是同步方法,那这时synchronized锁定的是哪个对象呢?它锁定的是调用这个同步方法对象。也就是说,当一个对象P1在不同的线程中执行这个同步方法时,它们之间会形成互斥,达到同步的效果。但是这个对象所属的Class所产生的另一对象P2却可以任意调用这个被加了synchronized关键字的方法。
上边的示例代码等同于如下代码:
public void methodAAA()
{
synchronized (this) // (1)
{
//…..
}
}
(1)处的this指的是什么呢?它指的就是调用这个方法的对象,如P1。可见同步方法实质是将synchronized作用于object reference。――那个拿到了P1对象锁的线程,才可以调用P1的同步方法,而对P2而言,P1这个锁与它毫不相干,程序也可能在这种情形下摆脱同步机制的控制,造成数据混乱:(
问题:这里提到的都是对象锁,那么我调用这个对象的非synchronized方法的时候,是否是同步的呢?
解释:不是同步的,此时别的对象依旧可以访问这个对象的非synchronized方法,例子见对象锁的理解中的脏读部分,thread睡眠的时候,并未释放锁,然而main线程依旧可以访问getvalue
继续问题:我认为同是直接对方法加synchronized,所以他们的监听器应该是同一个对象,所以说那时候的getValue不可访问,相当于就是方法里的代码块synchronized(object),为同一个Object,也就是说能否同步,是要看获得的锁是否是同一个锁
简单说就是:如果两个线程使用了同一个“对象监视器”,运行结果同步,否则不同步.
静态同步synchronized方法与synchronized(class)代码块:
静态同步synchronized方法与synchronized(class)代码块持有的锁一样,都是Class锁,Class锁对对象的所有实例起作用。synchronized关键字加到非static静态方法上持有的是对象锁。
线程A,B和线程C持有的锁不一样,所以A和B运行同步,但是和C运行不同步。
2 同步代码块
public void method3(SomeObject so)
{
synchronized(so)
{
//…..
}
}
这时,锁就是so这个对象,谁拿到这个锁谁就可以运行它所控制的那段代码。当有一个明确的对象作为锁时,就可以这样写程序,但当没有明确的对象作为锁,只是想让一段代码同步时,可以创建一个特殊的instance变量(它得是一个对象)来充当锁。
如果一个对象既有同步方法,又有同步块,那么当其中任意一个同步方法或者同步块被某个线程执行时,这个对象就被锁定了,其他线程无法在此时访问这个对象的同步方法,也不能执行同步块。
synchronized 关键字用于保护共享数据
例子:
public class ThreadTest implements Runnable{
public synchronized void run(){
for(int i=0;i<10;i++) {
System.out.print(" " + i);
}
}
public static void main(String[] args) {
Runnable r1 = new ThreadTest(); //也可写成ThreadTest r1 = new ThreadTest();
Runnable r2 = new ThreadTest();
Thread t1 = new Thread(r1);
Thread t2 = new Thread(r2);
t1.start();
t2.start();
}}
在这个程序中,run()虽然被加上了synchronized 关键字,但保护的不是共享数据。因为这个程序中的t1,t2 是两个对象(r1,r2)的线程。而不同的对象的数据是不同的,r1,r2 有各自的run()方法,所以输出结果无法预知。
synchronized的目的是使同一个对象的多个线程,在某个时刻只有其中的一个线程可以访问这个对象的synchronized 数据。每个对象都有一个“锁标志”,当这个对象的一个线程访问这个对象的某个synchronized 数据时,这个对象的所有被synchronized 修饰的数据将被上锁(因为“锁标志”被当前线程拿走了),只有当前线程访问完它要访问的synchronized 数据时,当前线程才会释放“锁标志”,这样同一个对象的其它线程才有机会访问synchronized 数据。
此处想看锁标志的源码:简单的看,方法里有一个标志位,来表示对象锁是否被线程访问?
源码剖析synchronized
再看例子:
public class ThreadTest implements Runnable{
public void run(){
synchronized(this){
for(int i=0;i<10;i++){
System.out.print(" " + i);
}
}
}
public static void main(String[] args){
Runnable r = new ThreadTest();
Thread t1 = new Thread(r);
Thread t2 = new Thread(r);
t1.start();
t2.start();
}
}
结果应该是:0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9
而代码:
public class ThreadTest implements Runnable{
public void run(){
for(int k=0;k<5;k++){
System.out.println(Thread.currentThread().getName()+ " : for loop : " + k);
}
synchronized(this){
for(int k=0;k<5;k++) {
System.out.println(Thread.currentThread().getName()+ " : synchronized for loop : " + k);
}} }
public static void main(String[] args){
Runnable r = new ThreadTest();
Thread t1 = new Thread(r,"t1_name");
Thread t2 = new Thread(r,"t2_name");
t1.start();
t2.start();
} }
运行结果:
t1_name : for loop : 0
t1_name : for loop : 1
t1_name : for loop : 2
t2_name : for loop : 0
t1_name : for loop : 3
t2_name : for loop : 1
t1_name : for loop : 4
t2_name : for loop : 2
t1_name : synchronized for loop : 0
t2_name : for loop : 3
t1_name : synchronized for loop : 1
t2_name : for loop : 4
t1_name : synchronized for loop : 2
t1_name : synchronized for loop : 3
t1_name : synchronized for loop : 4
t2_name : synchronized for loop : 0
t2_name : synchronized for loop : 1
t2_name : synchronized for loop : 2
t2_name : synchronized for loop : 3
t2_name : synchronized for loop : 4
第一个for 循环没有受synchronized 保护。
问题出现:在同步代码块的循环体重,输出的时候是调用静态方法currentthread,会对结果产生影响,不调用的时候,结果是两个线程完成一遍循环,而调用的话,则是分别前后完成一遍循环,有点想不清楚了???
3 wait与notify
配合此图学习,wait后进入等待队列,notify唤醒后进入了锁池,此时谁获得cpu会根据线程优先级(相同则随机决定)进入running状态
a. wait()、notify/notifyAll() 方法是Object的本地final方法,无法被重写。
b. wait()使当前线程阻塞,前提是 必须先获得锁,一般配合synchronized 关键字使用,即,一般在synchronized 同步代码块里使用 wait()、notify/notifyAll() 方法。
c. 由于 wait()、notify/notifyAll() 在synchronized 代码块执行,说明当前线程一定是获取了锁的。wait是指在一个已经进入了同步锁的线程内,让自己暂时让出同步锁,以便其他正在等待此锁的线程可以得到同步锁并运行,只有其他线程调用了notify方法(notify并不释放锁,只是告诉调用过wait方法的线程可以去参与获得锁的竞争了,但不是马上得到锁,因为锁还在别人手里,别人还没释放),调用wait方法的一个或多个线程就会解除wait状态,重新参与竞争对象锁,程序如果可以再次得到锁,就可以继续向下运行。。也就是在你notifyall()了之后,之前在wait()的线程都被唤醒了,但是锁有可能没被释放(一般就是当前线程拥有锁,因为notify一般在synchronized代码块中),锁的释放要看代码块的具体执行情况。所以在编程中,尽量在使用了notify/notifyAll() 后立即退出临界区,以唤醒其他线程
看例子:
public class CyclicBarrierTest {
public static void main(String[] args) throws Exception {
final Sum sum=new Sum();
new Thread(new Runnable() {
@Override
public void run() {
try {
synchronized (sum) {
System.out.println("thread3 get lock");
sum.sum();
sum.notifyAll(); //此时唤醒没有作用,没有线程等待
Thread.sleep(2000);
System.out.println("thread3 really release lock");
}
} catch (Exception e) {
e.printStackTrace();
}
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
try {
synchronized (sum) {
System.out.println("thread1 get lock");
sum.wait();//主动释放掉sum对象锁
System.out.println(sum.total);
System.out.println("thread1 release lock");
}
} catch (Exception e) {
e.printStackTrace();
}
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
try {
synchronized (sum) {
System.out.println("thread2 get lock");
sum.wait(); //释放sum的对象锁,等待其他对象唤醒(其他对象释放sum锁)
System.out.println(sum.total);
System.out.println("thread2 release lock");
}
} catch (Exception e) {
e.printStackTrace();
}
}
}).start();
}
}
class Sum{
public Integer total=0;
public void sum() throws Exception{
total=100;
Thread.sleep(5000);
}
}
输出结果:
thread3 get lock
thread3 really release lock
thread2 get lock
thread1 get lock
//程序后面一直阻塞
代码解释:线程3得到锁sum,此时唤醒没有用处,因为没有在wait的线程,然后线程3释放锁sum。
线程1得到锁sum,sum.wait(),线程阻塞,锁被释放。
线程2得到锁sum,sum.wait(),线程阻塞,锁被释放。
无人唤醒线程
更改顺序,见代码:
public class CyclicBarrierTest {
public static void main(String[] args) throws Exception {
final Sum sum=new Sum();
new Thread(new Runnable() {
@Override
public void run() {
try {
synchronized (sum) {
System.out.println("thread1 get lock");
sum.wait();//主动释放sum对象锁,等待唤醒
System.out.println(sum.total);
System.out.println("thread1 release lock");
}
} catch (Exception e) {
e.printStackTrace();
}
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
try {
synchronized (sum) {
System.out.println("thread2 get lock");
sum.wait(); //主动释放sum对象锁,等待唤醒
System.out.println(sum.total);
System.out.println("thread2 release lock");
}
} catch (Exception e) {
e.printStackTrace();
}
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
try {
synchronized (sum) {
System.out.println("thread3 get lock");
sum.sum();
sum.notifyAll();//唤醒其他等待线程(线程1,2)
Thread.sleep(2000);
System.out.println("thread3 really release lock");
}
} catch (Exception e) {
e.printStackTrace();
}
}
}).start();
}
}
class Sum{
public Integer total=0;
public void sum() throws Exception{
total=100;
Thread.sleep(5000);
}
}
输出:
thread1 get lock
thread2 get lock
thread3 get lock
thread3 really release lock
100
thread2 release lock
100
thread1 release lock
代码解释:
线程1得到锁sum,释放锁,线程1进入阻塞,等待唤醒。
线程2得到锁sum,释放锁,线程2进入阻塞,等待唤醒。
线程3得到锁sum,锁sum唤醒线程1,2(此时还未释放),线程3释放锁,线程1,2竞争锁(由CPU分配)
线程1先被唤醒执行sum.total,然后释放锁,然后线程2被唤醒,获得锁…….
例子来源:(https://blog.csdn.net/azhegps/article/details/63031562)
d. wait() 需要被try catch包围,中断也可以使wait等待的线程唤醒。
e. notify 和wait 的顺序不能错,如果A线程先执行notify方法,B线程在执行wait方法,那么B线程是无法被唤醒的。
f. notify 和 notifyAll的区别:notify方法只唤醒一个等待(对象的)线程并使该线程开始执行。所以如果有多个线程等待一个对象,这个方法只会唤醒其中一个线程,选择哪个线程取决于操作系统对多线程管理的实现。notifyAll 会唤醒所有等待(对象的)线程,尽管哪一个线程将会第一个处理取决于操作系统的实现。如果当前情况下有多个线程需要被唤醒,推荐使用notifyAll 方法。比如在生产者-消费者里面的使用,每次都需要唤醒所有的消费者或是生产者,以判断程序是否可以继续往下执行。
g. 在多线程中要测试某个条件的变化,使用if 还是while?
要注意,notify唤醒沉睡的线程后,线程会接着上次的执行继续往下执行。所以在进行条件判断时候,可以先把 wait 语句忽略不计来进行考虑,显然,要确保程序一定要执行,并且要保证程序直到满足一定的条件再执行,要使用while来执行,以确保条件满足和一定执行。如下代码:
1 public class K {
2 //状态锁
3 private Object lock;
4 //条件变量
5 private int now,need;
6 public void produce(int num){
7 //同步
8 synchronized (lock){
9 //当前有的不满足需要,进行等待
10 while(now < need){
11 try {
12 //等待阻塞
13 wait();
14 } catch (InterruptedException e) {
15 e.printStackTrace();
16 }
17 System.out.println("我被唤醒了!");
18 }
19 // 做其他的事情
20 }
21 }
22 }
23
此代码为一模板,具体实现看下面的例子
生产者消费者的问题,来源于(https://www.cnblogs.com/moongeek/p/7631447.html)
基本思想:假设有一个公共的容量有限的池子,有两种人,一种是生产者,另一种是消费者。需要满足如下条件:
1、生产者产生资源往池子里添加,前提是池子没有满,如果池子满了,则生产者暂停生产,直到自己的生成能放下池子。
2、消费者消耗池子里的资源,前提是池子的资源不为空,否则消费者暂停消耗,进入等待直到池子里有资源数满足自己的需求。
仓库类:
1 import java.util.LinkedList;
2
3 /**
4 * 生产者和消费者的问题
5 * wait、notify/notifyAll() 实现
6 */
7 public class Storage1 implements AbstractStorage {
8 //仓库最大容量
9 private final int MAX_SIZE = 100;
10 //仓库存储的载体
11 private LinkedList list = new LinkedList();
12
13 //生产产品
14 public void produce(int num){
15 //同步
16 synchronized (list){
17 //仓库剩余的容量不足以存放即将要生产的数量,暂停生产
18 while(list.size()+num > MAX_SIZE){
19 System.out.println("【要生产的产品数量】:" + num + "\t【库存量】:"
20 + list.size() + "\t暂时不能执行生产任务!");
21
22 try {
23 //条件不满足,生产阻塞
24 list.wait();
25 } catch (InterruptedException e) {
26 e.printStackTrace();
27 }
28 }
29
30 for(int i=0;i31 list.add(new Object());
32 }
33
34 System.out.println("【已经生产产品数】:" + num + "\t【现仓储量为】:" + list.size());
35
36 list.notifyAll();
37 }
38 }
39
40 //消费产品
41 public void consume(int num){
42 synchronized (list){
43
44 //不满足消费条件
45 while(num > list.size()){
46 System.out.println("【要消费的产品数量】:" + num + "\t【库存量】:"
47 + list.size() + "\t暂时不能执行生产任务!");
48
49 try {
50 list.wait();
51 } catch (InterruptedException e) {
52 e.printStackTrace();
53 }
54 }
55
56 //消费条件满足,开始消费
57 for(int i=0;i58 list.remove();
59 }
60
61 System.out.println("【已经消费产品数】:" + num + "\t【现仓储量为】:" + list.size());
62
63 list.notifyAll();
64 }
65 }
66 }
代码解释:
生产的时候,正常生产(容量足够),不进入while循环,只是一个List.add()的调用
,当容量不够的时候,进入while循环,线程阻塞,释放锁list,线程等待被唤醒(其实
就是等待消费者来消费直到仓库容量足够),此时消费者进来(获得锁list),正常消费
的情况下,即调用list.remove,完成后,唤醒锁,且synchronized代码块执行完毕,
释放锁List,此时继续从生产线程wait()后开始,继续判断是否足够生产,如果足够,
进行正常生产,然后唤醒消费的线程(如果此时消费线程处于等待,即不够消费的情况),
释放锁。
4 使用特殊域变量(volatile)实现线程同步
a.volatile关键字为域变量的访问提供了一种免锁机制
b.使用volatile修饰域相当于告诉虚拟机该域可能会被其他线程更新
c.因此每次使用该域就要重新计算,而不是使用寄存器中的值
d.volatile不会提供任何原子操作,它也不能用来修饰final类型的变量
原子操作?
class Bank {
//需要同步的变量加上volatile
private volatile int account = 100;
public int getAccount() {
return account;
}
//这里不再需要synchronized
public void save(int money) {
account += money;
}
}
5 使用重入锁实现线程同步
在JavaSE5.0中新增了一个java.util.concurrent包来支持同步。
ReentrantLock类是可重入、互斥、实现了Lock接口的锁,它与使用synchronized方法和快具有相同的基本行为和语义,并且扩展了其能力。
ReenreantLock类的常用方法有:
ReentrantLock() : 创建一个ReentrantLock实例
lock() : 获得锁
unlock() : 释放锁
例子:
class Bank {
private int account = 100;
//需要声明这个锁
private Lock lock = new ReentrantLock();
public int getAccount() {
return account;
}
//这里不再需要synchronized
public void save(int money) {
lock.lock();
try{
account += money;
}finally{
lock.unlock();
}
}
}
注意:
a.如果synchronized关键字能满足用户的需求,就用synchronized,因为它能简化代码
b.如果需要更高级的功能,就用ReentrantLock类,此时要注意及时释放锁,否则会出现死锁,通常在finally代码释放锁
6 使用局部变量实现线程同步
如果使用ThreadLocal管理变量,则每一个使用该变量的线程都获得该变量的副本,副本之间相互独立,这样每一个线程都可以随意修改自己的变量副本,而不会对其他线程产生影响。
ThreadLocal 类的常用方法
详解见(https://blog.csdn.net/u013735511/article/details/70416597)
ThreadLocal() : 创建一个线程本地变量
get() : 返回此线程局部变量的当前线程副本中的值
initialValue() : 返回此线程局部变量的当前线程的"初始值"
set(T value) : 将此线程局部变量的当前线程副本中的值设置为value
注意:
a.ThreadLocal与同步机制都是为了解决多线程中相同变量的访问冲突问题。
b.前者采用以”空间换时间”的方法,后者采用以”时间换空间”的方式
需要一个应用来理解
7 使用阻塞队列实现线程同步
关于队列,阻塞队列的问题!此处需要研究
8 使用原子变量实现线程同步
原子队列不慎理解!?