并发编程基础(2)

什么是多线程安全问题:

当多个线程共享同一个全局变量,做写的操作的时候,可能受到其它线程的影响或者对其它线程的影响,做读的操作的时候不会发生多线程安全的问题。

Synchronized同步代码的方式:

1、同步代码块方式

package com.strive.memorymodel;
class SaleTicket implements Runnable{
    private int count = 100;
    private Object o = new Object();
    @Override
    public void run() {
        while(count>0) {
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            sale();
        }
    }
    private void sale(){
        synchronized(o) {
            if (count > 0) {
                System.out.println(Thread.currentThread().getName() + "售卖第" + (100 - count + 1) + "张票");
                count--;
            }
        }
    }
}

public class TicketTest {
    public static void main(String[] args) {
        SaleTicket saleTicket = new SaleTicket();
        Thread thread1 = new Thread(saleTicket,"1号窗口");
        Thread thread2 = new Thread(saleTicket,"2号窗口");
        thread1.start();
        thread2.start();
    }
}

2、非静态同步方法(非静态同步方法相当于是this对象)

class SaleTicket implements Runnable{
    private int count = 100;
    @Override
    public void run() {
        while(count>0) {
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            sale();
        }
    }

    private synchronized void sale(){
        if(count>0){
            System.out.println(Thread.currentThread().getName()+"售卖第"+(100-count+1)+"张票");
            count--;
        }
    }

    //    非静态同步方法相当于是this对象,也就是如下
    //    private void sale(){
    //        synchronized (this){
    //            if(count>0){
    //                System.out.println(Thread.currentThread().getName()+"售卖第"+(100-    count+1)+"张票");
    //                count--;
    //            }
    //
    //        }
    //    }
}

public class TicketTest {
    public static void main(String[] args) {
        SaleTicket saleTicket = new SaleTicket();
        Thread thread1 = new Thread(saleTicket,"1号窗口");
        Thread thread2 = new Thread(saleTicket,"2号窗口");
        thread1.start();
        thread2.start();
    }
}

3、静态同步方法

public synchronized static void sale(){
    if (count > 0) {
        System.out.println(Thread.currentThread().getName() + "售卖第" + (100 - count + 1) + "张票");
        count--;
    }
}

这种静态同步方法等价于下面这种同步代码块方式,也就是说静态同步方法上用的是this.class

synchronized (SaleTicket.class){
    if (count > 0) {
        System.out.println(Thread.currentThread().getName() + "售卖第" + (100 - count + 1) + "张票");
        count--;
    }
}

多线程产生死锁的原因

ThreadLocal

class Res{
    private static ThreadLocal<Integer> threadLocal= new ThreadLocal<Integer>(){
        @Override
        protected Integer initialValue() {
            return 0;
        }
    };
    public Integer getNumber(){
        int count = threadLocal.get() + 1;
        threadLocal.set(count);
        return count;
    }
}
class Thread003 extends Thread{
    private Res res;
    public Thread003(Res res){
        this.res = res;
    }
    @Override
    public void run() {
        for (int i = 0 ;i<5;i++){
            System.out.println(Thread.currentThread().getName()+"打印"+res.getNumber());
        }
    }
}
public class ThreadLocaLTest {
    public static void main(String[] args) {
        Res res = new Res();
        Thread003 thread0031 = new Thread003(res);
        Thread003 thread0032 = new Thread003(res);
        thread0031.start();
        thread0032.start();
    }
}

多线程的三大特性:

1、原子性

即多线程必须保证一个操作或者多个操作全部执行并且在执行的过程中不会被任何因素打断,要么就不执行。原子性就是保证数据一致、保证线程安全的一部分。

2、可见性

多个线程针对一共享变量,如果线程A对其进行修改,那么线程B需要在得到修改之后再进行操作。

3、有序性

一般来说处理器为了提高程序的运行效率,可能会对输入代码进行优化,这种优化不会破坏代码之间的依赖关系,叫做重排序。

重排序在单线程下不会造成任何问题,但是在多线程下可能会造成问题。

 

Java内存模型(JMM)

共享内存模型指的就是Java内存模型(JMM),java内存模型决定一个线程对共享变量进行写操作时,是否对其它线程可见的问题。

从抽象的角度来看,JMM定义了线程和主内存之间的抽象关系:线程之间的共享变量存储在主内存中,每个线程都有自己的本地内存,本地内存中存储了该线程读/写的共享变量的副本,另外本地内存是一个抽象概念,并不是真的存在。

 

如上图所示,本地内存A和B有主内存中共享变量x的副本。假设初始时,这三个内存中的x值都为0。线程A在执行时,把更新后的x值(假设值为1)临时存放在自己的本地内存A中。当线程A和线程B需要通信时,线程A首先会把自己本地内存中修改后的x值刷新到主内存中,此时主内存中的x值变为了1。随后,线程B到主内存中去读取线程A更新后的x值,此时线程B的本地内存的x值也变为了1。

从整体来看,这两个步骤实质上是线程A在向线程B发送消息,而且这个通信过程必须要经过主内存。JMM通过控制主内存与每个线程的本地内存之间的交互,来为java程序员提供内存可见性保证。

 

Volatile关键字

1、volatile关键字

Volatile关键字保证的是变量的可见性,也就是说,一旦某个线程更改了共享变量,那么就会立即将更新后的值刷新到主内存中,其它线程需要读取的时候可以立即获得修改之后的值。

class ThreadVolatileDemo extends Thread {
    public volatile boolean flag = true;

    @Override
    public void run() {
        System.out.println("子线程开始");
        while (flag){

        }
        System.out.println("子线程结束");
    }
    public void setFlag(boolean flag){
        this.flag = flag;
    }
}

public class VolitileTest {
    public static void main(String[] args) throws InterruptedException {
        System.out.println("主线程开始");
        ThreadVolatileDemo threadVolatileDemo = new ThreadVolatileDemo();
        threadVolatileDemo.start();
        Thread.sleep(3);
        threadVolatileDemo.setFlag(false);
        System.out.println("主线程结束");
    }
}

优点:

保证此变量对所有的线程的可见性,这里的“可见性”,如本文开头所述,当一个线程修改了这个变量的值,volatile 保证了新值能立即同步到主内存,以及每次使用前立即从主内存刷新。但普通变量做不到这点,普通变量的值在线程间传递均需要通过主内存(详见:Java内存模型)来完成。

禁止指令重排序优化。有volatile修饰的变量,赋值后多执行了一个“load addl $0x0, (%esp)”操作,这个操作相当于一个内存屏障(指令重排序时不能把后面的指令重排序到内存屏障之前的位置),只有一个CPU访问内存时,并不需要内存屏障;(什么是指令重排序:是指CPU采用了允许将多条指令不按程序规定的顺序分开发送给各相应电路单元处理)。

volatile 的读性能消耗与普通变量几乎相同,但是写操作稍慢,因为它需要在本地代码中插入许多内存屏障指令来保证处理器不发生乱序执行。

代码逻辑:

如果我不加volatile关键字,那么子线程将会在while循环中不出来,因为我们主线程更改了flag之后,没有将其刷新到主内存中,子线程拿到的flag值一直是true,那么就会在while循环中出不来,但是当我们使用了volatile关键字之后,主线程更改flag为false并将其刷新到主内存中,子线程就能拿到false,就能够从while循环中出来了。

2、重排序问题

编译器和处理器会为了优化执行,在执行代码的时候对于代码的执行顺序进行一些重排序操作,当然这是针对于没有对于依赖关系的部分,并且这部分指的是单线程下的依赖关系。

 

如下方代码,模拟writer和reader是两个不同的线程,如果writer线程的两行代码进行了重排序,那么就将会影响到reader中的代码,将会造成我们不想要的结果

class ReorderExample {

int a = 0;

boolean flag = false;

 

public void writer() {

    a = 1;                   //1

    flag = true;             //2

}

 

Public void reader() {

    if (flag) {                //3

        int i =  a * a;        //4

        ……

    }

}}

3、volatile和synchronized的比较

(1)我们可以看出volatile虽然具有可见性但是并不能保证原子性。

(2)性能方面,synchronized关键字是防止多个线程同时执行一段代码,就会影响程序执行效率,而volatile关键字在某些情况下性能要优于synchronized。

但是要注意volatile关键字是无法替代synchronized关键字的,因为volatile关键字无法保证操作的原子性。

4、wait和notify的区别

1.因为涉及到对象锁,他们必须都放在synchronized中来使用. Wait、Notify一定要在synchronized里面进行使用。

2.Wait必须暂定当前正在执行的线程,并释放资源锁,让其他线程可以有机会运行

3. notify/notifyall: 唤醒因锁池中的线程,使之运行

4.wait调用会释放锁,并且线程状态从运行变为阻塞;notify调用不涉及释放锁的操作,只是唤醒指定锁下对应的线程

注意:一定要在线程同步中使用,并且是同一个锁的资源

package com.strive.memorymodel;

class ResTest {
    public String name;
    public String sex;
    public boolean flag = false;
}

class ThreadWrite extends Thread {
    private ResTest resTest;

    public ThreadWrite(ResTest resTest) {
        this.resTest = resTest;
    }

    @Override
    public void run() {
        int count = 0;
        while (true) {
            synchronized (resTest) {

                try {
                    if (resTest.flag) {
                        resTest.wait();
                    }
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }


                if (count == 0) {
                    resTest.name = "A1";
                    resTest.sex = "男";
                } else {
                    resTest.name = "B1";
                    resTest.sex = "女";
                }
                count = (count + 1) % 2;
                resTest.flag = true;
                resTest.notify();
            }
        }
    }
}

class ThreadRead extends Thread {
    private ResTest resTest;

    public ThreadRead(ResTest resTest) {
        this.resTest = resTest;
    }

    @Override
    public void run() {
        while (true) {
            synchronized (resTest) {
                if (!resTest.flag) {
                    try {
                        resTest.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                System.out.println(resTest.name + ":" + resTest.sex);
                resTest.flag = false;
                resTest.notify();
            }
        }
    }
}

public class WaitAndNotify {
    public static void main(String[] args) {
        ResTest resTest = new ResTest();
        ThreadWrite threadWrite = new ThreadWrite(resTest);
        ThreadRead threadRead = new ThreadRead(resTest);
        threadWrite.start();
        threadRead.start();
    }
}

5、wait和sleep的区别

  1. sleep()属于Thread类下的,wait()属于Object类下
  2. 调用sleep()方法,当前线程不会释放对象锁;
  3. 调用wait()方法,当前线程会释放掉对象锁

 

Lock锁

1、写法

Lock lock  = new ReentrantLock();

lock.lock();

try{

//可能会出现线程安全的操作

}finally{

//一定在finally中释放锁

//也不能把获取锁在try中进行,因为有可能在获取锁的时候抛出异常

  lock.unlock();

}

 

2、lock和synchronized

1、Lock 接口可以尝试非阻塞地获取锁 当前线程尝试获取锁。如果这一时刻锁没有被其他线程获取到,则成功获取并持有锁。
2、Lock 接口能被中断地获取锁 与 synchronized 不同,获取到锁的线程能够响应中断,当获取到的锁的线程被中断时,中断异常将会被抛出,同时锁会被释放。

3、Lock 接口在指定的截止时间之前获取锁,如果截止时间到了依旧无法获取锁,则返回。

3、Condition用法

 Condition的功能类似于在传统的线程技术中的,Object.wait()和Object.notify()的功能。

class Res {
	public String userName;
	public String sex;
	public boolean flag = false;
	Lock lock = new ReentrantLock();
}

class InputThread extends Thread {
	private Res res;
	Condition newCondition;
	public InputThread(Res res,	Condition newCondition) {
		this.res = res;
		this.newCondition=newCondition;
	}

	@Override
	public void run() {
		int count = 0;
		while (true) {
			// synchronized (res) {

			try {
				res.lock.lock();
				if (res.flag) {
					try {
//						res.wait();
						newCondition.await();
					} catch (Exception e) {
						// TODO: handle exception
					}
				}
				if (count == 0) {
					res.userName = "小王";
					res.sex = "男";
				} else {
					res.userName = "小红";
					res.sex = "女";
				}
				count = (count + 1) % 2;
				res.flag = true;
//				res.notify();
				newCondition.signal();
			} catch (Exception e) {
				// TODO: handle exception
			}finally {
				res.lock.unlock();
			}
		}

		// }
	}
}

class OutThrad extends Thread {
	private Res res;
	private Condition newCondition;
	public OutThrad(Res res,Condition newCondition) {
		this.res = res;
		this.newCondition=newCondition;
	}

	@Override
	public void run() {
		while (true) {
//			synchronized (res) {
			try {
				res.lock.lock();
				if (!res.flag) {
					try {
//						res.wait();
						newCondition.await();
					} catch (Exception e) {
						// TODO: handle exception
					}
				}
				System.out.println(res.userName + "," + res.sex);
				res.flag = false;
//				res.notify();
				newCondition.signal();
			} catch (Exception e) {
				// TODO: handle exception
			}finally {
				res.lock.unlock();
			}
//			}
		}

	}
}

public class ThreadDemo01 {

	public static void main(String[] args) {
		Res res = new Res();
		Condition newCondition = res.lock.newCondition();
		InputThread inputThread = new InputThread(res,newCondition);
		OutThrad outThrad = new OutThrad(res,newCondition);
		inputThread.start();
		outThrad.start();
	}

}
posted @ 2019-02-25 15:51  柚子味儿的西瓜  阅读(16)  评论(0)    收藏  举报