马士兵并发编程学习笔记


(一)
public class Demo01 {

private int count = 10;
private Object object = new Object();
@Test
public void test(){
synchronized (object) { //任何线程要执行下面的代码,必须先拿到object对象的锁
count --;
System.out.println(Thread.currentThread().getName() + " count = " + count);
}
}

public static void main(String[] args) {
Demo01 demo01=new Demo01();

new Thread(()->demo01.test()).start();
new Thread(()->demo01.test()).start();

}
1.synchronized关键字锁定的是对象不是代码块,demo中锁的是object对象的实例(堆内存中)
2.锁定的对象有两种情况:①类的实例 ②类的字节码(.class)
3.关于线程安全:加synchronized关键字之后不一定能实现线程安全,具体还要看
锁定的对象是否唯一。
(二)
public class Demo02 {

private int count = 10;
@Test
public void test(){
synchronized (this) { //任何线程要执行下面的代码,必须先拿到Demo02对象实例的锁
count --;
System.out.println(Thread.currentThread().getName() + " count = " + count);
}
}
}
1.synchronized(this)锁定的是当前类的实例,demo中锁定的是Demo02类的实例
2.此demo中如果Demo02类是单例的话可以保证在多线程访问时是线程安全的,
如果存在有多个Demo02的实例的话在多线程中不能保证线程安全,因为方法中的锁不唯一了。(堆内存中的地址不一样)
(三)
public class Demo03 {

private int count = 10;
public synchronized void test(){//等同于synchronized(this),锁定的是Demo03对象的实例
count --;
System.out.println(Thread.currentThread().getName() + " count =" + count);
}
}
1.synchronized关键字修饰普通方法等同于synchronized(this)
(四)
public class Demo04 {

private static int count = 10;
public synchronized static void test1(){ //这里等同于synchronized(Demo04.class)
count --;
System.out.println(Thread.currentThread().getName() + " count = " + count);
}
public static void test2(){ //考虑一下这里写synchronize(this)是否可以
synchronized (Demo04.class) {
count --;
}
}
}
1.synchronize关键字修饰静态方法锁定的是类的.class文件
2.静态方法中synchronize锁定代码块,锁定的对象不能是类的实例,只能是类的.class文件。
原理如同在静态方法中不能直接调用非静态方法
3.类的.class文件是唯一的,所以说synchronize修饰静态方法或者锁定的对象是类的.class文件的时候
在多线程中是可以实现线程安全的
(五)
public class Demo05 implements Runnable{

private int count = 10;
@Override
public /* synchronized*/ void run(){
count --;
System.out.println(Thread.currentThread().getName() + " count = " + count);
}
public static void main(String[] args) {
Demo05 demo05 = new Demo05();
for (int i = 0; i < 5; i++) {
new Thread(demo05,"THREAD" + i).start();
}
}
}
1.run()方法没加synchronized关键字时,多个线程同时访问count,线程是不安全的
2.run()方法加上synchronized关键字后,锁定的是Demo05对象的实例,因为只创建了
一个Demo05的实例,多个线程访问时都要拿到Demo05的锁标记才能执行,在多个线程同时访问时也是线程安全的
(六)
public class Demo06 implements Runnable{

private int count = 10;

@Override
public synchronized void run() {
count --;
System.out.println(Thread.currentThread().getName() + " count = " + count);
}

public static void main(String[] args) {
for (int i = 0; i < 5; i++) {
Demo06 demo06 = new Demo06();//注意这里
new Thread(demo06,"THREAD" + i).start();
}
}
}
1.执行可以知道,demo中虽然加上了synchronized关键字来修饰方法,但是线程是不安全的。为什么呢??
分析一下:synchronized修饰的是普通方法,锁定的是Demo06实例,从Main方法中可以看到,在for循环中
创建了多个Demo06的实例,也就是说每个线程对应都拿到各自的锁标记,可以同时执行。
例子:
多人同时上厕所,厕所门只有一把锁的时候是一个人上完之后把钥匙(锁标记)给到下一个人才可以开门上厕所
如果厕所门的锁有多个钥匙的情况下,就是每个人都有锁的钥匙了,大家可以一起去打开门来上厕所。(归根结底还是堆内存上的地址)
demo中就如同厕所门的锁有多把钥匙(锁标记),不能实现线程安全
(七)
public class Demo07 {

public synchronized void test1(){
System.out.println(Thread.currentThread().getName() + " test1 start..........");
try {
Thread.sleep(10 * 1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " test1 end........");
}
public void test2(){
try {
Thread.sleep(5 * 1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "test2 execute......");
}
public static void main(String[] args) {
Demo07 demo07 = new Demo07();
new Thread(demo07 :: test1,"t1").start(); //JDK1.8新特性
new Thread(demo07 :: test2,"t2").start(); //JDK1.8新特性
}
}
运行结果:
t1 test1 start..........
t2test2 execute......
t1 test1 end........
1.同步方法和非同步方法是可以同时调用的
(八)
package thread.demo_008;

import java.util.concurrent.TimeUnit;

/**
* 对业务写方法加锁
* 对业务读方法不加锁
* 容易产生脏读问题
* @author Jcon
*
*/
public class Demo08 {

String name;
double balance;
public synchronized void set(String name, double balance){
this.name = name;
try {
Thread.sleep(2 * 1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
this.balance = balance;
}
public synchronized double getBalance(String name){
return this.balance;
}

public static void main(String[] args) {
Demo08 demo08 = new Demo08();
new Thread(()->demo08.set("zhangsan",100.0)).start(); //JDK1.8新特性
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(demo08.getBalance("zhangsan"));
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(demo08.getBalance("zhangsan"));
}
}

2.对业务写方法加锁,同时也要对业务读方法加锁,否则容易产生脏读问题
(九)
/**
* 一个同步方法可以调用另一个同步方法,一个线程已经拥有某个对象的锁,再次申请的时候
* 仍然会得到该对象的锁
* 也就是说synchronized获得的锁是可重入的
* @author Jcon
*
*/
public class Demo09 {

synchronized void test1(){
System.out.println("test1 start.........");
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
test2();
}
synchronized void test2(){
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("test2 start.......");
}
public static void main(String[] args) {
Demo09 demo09 = new Demo09();
demo09.test1();
}
}
1.一个同步方法可以调用另一个同步方法,一个线程已经拥有某个对象的锁,
再次申请的时候仍然会得到该对象的锁
也就是说synchronized获得的锁是可重入的
(十)
package thread.demo_010;

import java.util.concurrent.TimeUnit;

/**
* 一个同步方法可以调用另外一个同步方法,一个线程已经拥有某个对象的锁,
* 再次申请的时候仍然会得到该对象的锁,也就是说synchronize获得的锁是可重入的
* 这里是继承中有可能发生的情形,子类调用父类的同步方法
* @author Jcon
*
*/
public class Demo10 {

synchronized void test(){
System.out.println("test start........");
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("test end........");
}
public static void main(String[] args) {
new Demo100().test();
}
}

class Demo100 extends Demo10{
@Override
synchronized void test() {
System.out.println("child test start.......");
super.test();
System.out.println("child test end.......");
}
}
2.一个同步方法可以调用另一个同步方法,一个线程已经拥有某个对象的锁,
再次申请的时候仍然会得到该对象的锁
也就是说synchronized获得的锁是可重入的(这里是继承中有可能发生的情形,子类调用父类的同步方法

(十一)
/**
* 程序在执行过程中,如果出现异常,默认情况锁会被释放
* 所以,在并发处理的过程中,有异常要多加小心,不然可能会发生不一致的情况
* 比如,在一个web app处理过程中,多个servlet线程共同访问通一个资源,这是如果异常处理不合适
* 在第一个线程中抛出异常,其他线程就会进入同步代码去,有可能访问到异常产生是的数据
* 因此要非常小心的处理同步业务逻辑中的异常
* @author Jcon
*
*/
public class Demo11 {

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) {
int i = 1/0; //此处抛出异常,锁将被释放,要想不被释放,可以在这里进行catch处理,然后让循环继续
}
}
}
public static void main(String[] args) {
Demo11 demo11 = new Demo11();
Runnable r = new Runnable() {
@Override
public void run() {
demo11.test();
}
};
new Thread(r, "t1").start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(r, "t2").start();
}
}
1.程序在执行过程中,如果出现异常,默认情况锁会被释放
(十二)
public class Demo12 {

volatile boolean running = true;
public void test(){
System.out.println("test start.......");
while (running) {
/*try {
TimeUnit.SECONDS.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}*/
}
System.out.println("test end........");
}
public static void main(String[] args) {
Demo12 demo12 = new Demo12();
new Thread(demo12 :: test, "t1").start(); //JDK1.8新特性
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
demo12.running = false;
}
}
* volatile 关键字,使一个变量在多个线程间可见
* A B线程都用到一个变量,java默认是A线程中保留一份copy,这样如果B线程修改了该变量,则A线程未必知道
* 使用volatile关键字,会让所有线程都会读到变量的修改值
*
* 在下面的代码中,running是存在于堆内存的t对象中
* 当线程t1开始运行的时候,会把running值从内存中读到t1线程的工作区,在运行过程中直接使用这个copy,并不会每次都去
* 读取堆内存,这样,当主线程修改running的值之后,t1线程感知不到,所以不会停止运行
*
* 使用volatile,将会强制所有线程都去堆内存中读取running的值
*
* 可以阅读这篇文章进行更深入的理解
*

(十三)
**
* volatile并不能保证多个线程共同修改running变量时所带来的不一致问题,
* 也就是说volatile不能替代synchronize
* 运行下面的程序,并分析结果
* @author Jcon
*
*/
public class Demo13 {

volatile int count = 0;
public void test(){
for (int i = 0; i < 10000; i++) {
count ++;
}
}
public static void main(String[] args) {
Demo13 demo13 = new Demo13();
List<Thread> threads = new ArrayList<Thread>();
for (int i = 0; i < 10; i++) {
threads.add(new Thread(demo13::test, "thread-" + i));
}
threads.forEach((o)->o.start()); //JDK1.8新特性
threads.forEach((o)->{ //JDK1.8新特性
try {
o.join(); //等线程执行完毕之后才执行主线程main
} catch (Exception e) {
e.printStackTrace();
}
});
System.out.println(demo13.count);
}
}
* volatile并不能保证多个线程共同修改running变量时所带来的不一致问题,也就是说volatile不能替代synchronized
(十四)
public class Demo14 {

int count = 0;
public synchronized void test(){
for (int i = 0; i < 10000; i++) {
count ++;
}
}
public static void main(String[] args) {
Demo14 demo14 = new Demo14();
List<Thread> threads = new ArrayList<Thread>();
for (int i = 0; i < 10; i++) {
threads.add(new Thread(demo14::test, "thread-" + i));
}
threads.forEach((o)->o.start()); //JDK1.8新特性
threads.forEach((o)->{ //JDK1.8新特性
try {
o.join(); //等线程执行完毕之后才执行主线程main
} catch (Exception e) {
e.printStackTrace();
}
});
System.out.println(demo14.count);
}
}

* 对比上一个程序,可以用synchronized解决,synchronize可以保证可见性和原子性,volatile只能保证可见性
(十五)
public class Demo15 {

//int count = 0;
AtomicInteger count = new AtomicInteger(0);
public /*synchronized*/ void test(){
for (int i = 0; i < 10000; i++) {
//count ++;
count.incrementAndGet(); //count++
// 注意下面则不构成原子性,因为在get时,线程a进行判断后,但是不执行下面代码
// 线程b进行判断,执行完代码,此时代码是1000,然后线程a执行,此时结果是1001
// if (count.get() > 1000) {
// count.incrementAndGet();
// }
}
}
public static void main(String[] args) {
Demo15 demo15 = new Demo15();
List<Thread> threads = new ArrayList<Thread>();
for (int i = 0; i < 10; i++) {
threads.add(new Thread(demo15::test, "thread-" + i));
}
threads.forEach((o)->o.start()); //JDK1.8新特性
threads.forEach((o)->{ //JDK1.8新特性
try {
o.join(); //等线程执行完毕之后才执行主线程main
} catch (Exception e) {
e.printStackTrace();
}
});
System.out.println(demo15.count);
}


}
* 解决同样的问题的更高效的方法,使用AtomXXX类
* AtomXXX类本身方法都是原子性的,但不能保证多个方法连续调用是原子性
(十六)
/**
* synchronize优化
* 同步代码快中的语句越少越好
* 比较test1和test2
* @author Jcon
*
*/
public class Demo16 {

int count = 0;
public synchronized void test1(){
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
//业务逻辑中只有下面这句需要sync,这时不应该给整个方法上锁
count ++;
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public void test2(){
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
//业务逻辑中只有下面这句需要sync,这时不应该给整个方法上锁
//采用细粒度的锁,可以是线程争用时间变短,从而提高效率
synchronized (this) {
count ++;
}
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
1.业务逻辑中只有下面这句需要sync,这时不应该给整个方法上锁
2.采用细粒度的锁,可以是线程争用时间变短,从而提高效率
(十七)
/**
* 锁定某对象o,如果o的属性发生改变,不影响锁的使用
* 但是如果o变成另外一个对象,则锁定的对象发生改变
* 应该避免将锁定对象的引用变成另外一个对象
* @author Jcon
*
*/
public class Demo17 {

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) {
Demo17 demo17 = new Demo17();
//启动第一个线程
new Thread(demo17 :: test, "t1").start(); //JDK1.8新特性
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
//启动第二个线程
Thread t2 = new Thread(demo17 :: test, "t2");
demo17.o = new Object(); //锁对象发生改变,所以t2线程得以执行,如果注释掉这句话,线程t2将永远得不到执行机会
t2.start();
}
}
* 锁定某对象o,如果o的属性发生改变,不影响锁的使用
* 但是如果o变成另外一个对象,则锁定的对象发生改变
* 应该避免将锁定对象的引用变成另外一个对象
(十八)
/**
* 不要以字符串常量作为锁定的对象
* 在下面的例子中,test1和test2其实锁定的是同一个对象
* 这种情况还会发生比较诡异的现象,比如你用到了一个类库,在该类库中代码锁定了字符串"hello",
* 但是你读不到源码,所以你在自己的代码中也锁定了"hello",这时候就有可能发生非常诡异的死锁阻塞,
* 因为你的程序和你用的的类库不经意间使用了同一把锁
* @author Jcon
*
*/
public class Demo18 {

String s1 = "hello";
String s2 = "hello";
public void test1(){
synchronized (s1) {
}
}
public void test2(){
synchronized (s2) {
}
}
}
1.不要以字符串常量作为锁定的对象


posted @ 2018-06-26 15:52  星朝  阅读(3292)  评论(0编辑  收藏  举报