最近开始学习并发编程,每天抽空学一点,晚上坚持做一下笔记。
180918-------------------------------------------------------------------------------------------------------------------------
一、什么是线程安全
多个线程同时访问一个类(对象或方法)时,这个类始终都能展现出正确的行为,那么这个类就是安全的。
二、多个线程多个锁
多个线程,每个线程都可以拿到自己指定的锁,分别获得锁之后,执行synchronized方法体的内容。
三、synchronized关键字
1、关键字synchronized取得的锁都是对象锁,而不是把一段代码(方法)当做锁,所以先执行带有synchronized关键字的代码段(方法)的线程就持有该代码段(方法)所属对象的锁(Lock),如果存在两个对象,则线程持就有两个不同的锁,他们互不影响,可以并行运行。
代码示例:
/*no static from:txd2016_5_11 180918*/
public synchronized void printNumber_void(String tag) {
System.err.println("这里是:printNumber_void "+tag);
if("a".equals(tag)) {
number = 100;
System.out.println("tag : a,set number over");
}else {
number = 200;
System.out.println("tag is not a:set number over");
}
try {
Thread.sleep(3*1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("执行结果:"+tag+", number:"+number);
}
2、静态方法上存在synchronized关键字,则表示锁定该对象对应的.Class类,是为类一级的锁(独占锁),导致其串行运行。
代码示例:
/* static from:txd2016_5_11 180918*/
public static synchronized void printNumber_static(String tag) {
System.err.println("这里是:printNumber_static "+tag);
if("a".equals(tag)) {
number_static = 100;
System.out.println("tag : a,set number over");
}else {
number_static = 200;
System.out.println("tag is not a:set number over");
}
try {
Thread.sleep(3*1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("执行结果:"+tag+", number:"+number_static);
}
两段代码执行结果:
System.out.println("两个不同的对象访问static锁");
t1.start();
t2.start();
System.err.println("执行结果:t1与t2按顺序执行,即先到者先将类锁住了,"
+ "后来者只有等前面的执行结束之后才能执行");
System.out.println("两个不同的对象访问非static锁");
t3.start();
t4.start();
System.err.println("执行结果:t3与t4并发执行,之间互不影响,所操作的都是自己的对象内内容");
-------------------------------------------------------------------------------------------------- 180918 end
源码?目前还不到那个层次,至少目前状态还是不存在的,至于以后,那就是一定得好好抠一下。
180919 ---------------------------------------关于锁的几个问题-----------------------------------------
1、锁讲解:
测试代码:
private int count = 3;
//synchronized 加锁
//synchronized
public synchronized void run() {
count -- ;
float result = 100/count;
try {
Thread.sleep(1*1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(this.currentThread().getName()+" count = "+count+" \t result = "+result);
}
main执行测试:
public static void main(String[] args) {
MyThread01 myThread = new MyThread01();
Thread t1 = new Thread(myThread,"t1");
Thread t2 = new Thread(myThread,"t2");
Thread t3 = new Thread(myThread,"t3");
Thread t4 = new Thread(myThread,"t4");
Thread t5 = new Thread(myThread,"t5");
Thread[] threads = {t1,t2,t3,t4,t5};
for(Thread t:threads) {
t.start();
}
}
执行结果分析:
t1到t2不定顺序串行执行,其中某一个线程会报除零错误,其余线程都正常执行,count数也由3减少为-2;
2、同步与异步
同步(synchronized):类似于MySQL的主从同步(主服务器:读写操作,从服务器:readOnly)N个线程可以放心大胆地并发去从服务器上进行DML操作,N个线程前往主服务器进行写操作,如insert,则先到达的线程会先将表锁住,等待insert执行完毕后下一个线程才有可能被允许进来进行insert操作(主服务器与从服务器将会进行数据同步,线程在主服务执行完insert操作后将产生日志文件,主服务器将此日志文件传给从服务器,此处涉及到同步刷盘/异步刷盘);区分MySQL的分库分表(后者主要是为了减轻数据库压力)。
同步的概念就是共享,如果不是共享的资源就没有必要进行同步。同步的目的就是为了线程安全,而对于线程安全来说,则需要满足两个特性:原子性(不可拆分?)、可见性。
异步(asynchronized):异步的概念就是*独*-*立*,相互之间不收任何制约。
代码示例(原子性):MyObject类
public synchronized void method01() {
System.err.println(Thread.currentThread().getName()+"我是被锁住的方法");
try {
Thread.sleep(5*1000); //睡4秒钟
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public void method2() {
System.err.println(Thread.currentThread().getName()+"我还没有被锁住");
try {
Thread.sleep(1*1000); //睡1秒
} catch (InterruptedException e) {
e.printStackTrace();
}
}
main测试:
final MyObject myObj = new MyObject();
Thread thread1 = new Thread(new Runnable() {
@Override
public void run() {
myObj.method01();
}
},"thread1");
Thread thread2 = new Thread(new Runnable() {
@Override
public void run() {
myObj.method2();
}
},"thread2");
Thread thread3 = new Thread(new Runnable() {
@Override
public void run() {
myObj.method1();
}
},"thread3");
thread1.start();
thread2.start();
thread3.start();
执行结果:。。。
thread1先获取到method01的锁,后边thread3接着去获取method01则需要等待thread1释放该资源;thread2由于获取的是thread02且该方法没有加锁,自然就能够迅速被thread2获取并执行了。
-------------------------------------------------------------------------------------------------- 180919 end
180920 -------------------------------------关于脏读的几个问题----------------------------------------
脏读:
示例代码:
数据资源(userName、password)及其初始化值:
private String userName="txd";
private String password="123";
操作(get/set),set方法加了锁,get方法则不加锁:
public synchronized void setValue(String username,String password) {
this.userName = username;
try {
Thread.sleep(2*1000);
}catch(InterruptedException e) {
System.err.println(Thread.currentThread().getName()+"线程此时被打断了");
}
this.password = password;
System.err.println("setVlaue的最终执行结果为:username="+username+",
password="+password);
}
public void getValue() {
System.err.println("getValue的最终执行结果为:username="+this.userName+",
password="+this.password);
}
main测试:
public static void main(String[] args) {
final DirtyRead dr = new DirtyRead();
Thread t1 = new Thread(
new Runnable() {
@Override
public void run() {
dr.setValue("晓冬", "凛冬将至");
}
}
);
Thread t2 = new Thread(
new Runnable() {
@Override
public void run() {
dr.getValue();
}
}
);
t1.start();
t2.start();
}
运行效果:
getValue的最终执行结果为:username=晓冬, password=123
setVlaue的最终执行结果为:username=晓冬, password=凛冬将至
显然:理想状态下的键值对信息应该是:“txd”-"123"、"晓冬"-"凛冬将至";
结果显示:发生脏读现象。
将set/get方法都加锁,测试效果:
//先get再set
getValue的最终执行结果为:username=txd, password=123
setVlaue的最终执行结果为:username=晓冬, password=凛冬将至
//先set再get
setVlaue的最终执行结果为:username=晓冬, password=凛冬将至
getValue的最终执行结果为:username=晓冬, password=凛冬将至
解决脏读方案:根据业务场景不同而不同,比如同时都加锁、写互斥/读共享锁...加锁时考虑业务的整体性,保证业务(Service)的 原子性,也从侧面保证了业务的一致性。
备注:数据库的ACID:原子性、一致性、隔离性、持久性;
锁重入:synchronized关键字拥有锁重入的功能,即在使用synchronized时,当一个线程得到一个对象锁后,再次请求此对象时可以再次得到该对象的锁,作用?还没意识到。。。。
-------------------------------------------------------------------------------------------------- 180920 end
synchronized关键字使用时的注意事项:
1、为了提高执行速度,锁的粒度应该尽量小。
2、不要为字符串常量加锁,会出现死循环。
3、当使用一个对象进行加锁的时候,若对象本身发生了改变,则其持有的锁也就不同了。这里的对象本身的改变不包括对象的属性等的改变。
4、注意避免死锁。