一、线程安全
线程安全的问题,是针对多线程的程序。单线程的情况下,是不存在线程安全问题。
产生线程安全问题的原因:多个线程同时访问同一块代码。但是实际上我们希望该代码块是原子性的,在某一个时间点,只希望一个线程单独访问,不希望多个线程同时访问。
解决方案:
1:同步代码块。
synchronized (this) {
//被同步的代码块;
}
synchronized(同步监视器对象) :java 关键字
{}:同步代码块:希望在某一个时间点只有一个线程访问的代码块。
执行过程:
1:线程1 执行到了同步代码块。要对同步监视器进行检测,看是否被其他的线程上锁了。发现没有上锁,那么线程1对同步监视器对象 上锁,并开始访问 同步代码块。
2:线程2 执行到了同步代码块,检测同步监视器,发现监视器已经被 线程1 上锁了。就进入就绪状态,等待cpu 下次调度执行。
3:线程1 执行完毕同步代码块,然后对 同步监视器对象 解锁。并执行后续的代码。
4:当 线程2 被再次调度执行的时候,发现同步监视器对象已经被解锁,那么就对同步监视器对象 加锁 并访问 同步代码块。
类似于上厕所:没人(有人就等着),进去,锁门,方便,然后冲水开门出去。下一个。
2:同步方法:
相当于把整个方法体都同步了。
同步监视器对象是this(实例方法)。
同步方法,会导致在任意时刻,只能有一个线程访问该方法。一个线程执行完毕方法之后,其他的线程才能访问。
同步块和同步方法都会导致程序的效率降低。多了检测监视器对象是否加锁,加锁和解锁的过程。
3:如果同步方法是静态方法,那么同步监视器对象是当前类的大 Class 对象。
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* 模拟两个人取钱,说明线程安全的问题。
* 张三和 张三媳妇,一起去取钱。
* 一个在ATM
* 一个在柜台。
* 一共1500
* 每个人都想取1000块钱。
*
*/
public class AccountTest {
public static void main(String[] args) {
PersonRunnable runnable = new PersonRunnable();
Thread zhangSan = new Thread(runnable, "张三");
Thread zhangSanXiFu = new Thread(runnable, "张三媳妇");
zhangSan.start();
zhangSanXiFu.start();
}
}
/**
* 账户类
*
*/
class Account{
private int money = 1500;
//第三种线程同步的实现 jdk1.5出现的。
private Lock lock = new ReentrantLock();
/**
* 取现
* @param money 取钱的钱数
* @return 如果取钱成功,返回 true ,否则返回 false。
*/
public /*synchronized*/ boolean withDrawMoney(int money) {
//同步代码块
//synchronized (this) {
//在try 外面将 需要同步的代码锁住,然后将需要同步的代码,放到try 块中。
lock.lock();
try {
if(this.money >= money){
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
this.money -= money;
System.out.println(Thread.currentThread().getName() + "--> 取钱[成功]。余额为:"+this.money);
return true;
}
}finally{
//必须执行
lock.unlock();
}
// }
System.out.println(Thread.currentThread().getName() + "--> 取钱[失败]。余额为:"+this.money);
return false;
}
}
class PersonRunnable implements Runnable{
//唯一的账户
private Account account = new Account();
public void run() {
account.withDrawMoney(1000);
}
}
二、关于监视器的选择
如果想实现线程间的互斥访问同步代码块,那么监视器对象必须唯一。只有一个实例存在。
1:this:保证只有一个this。这个对象只被实例化了一次。
2:final static Object obj = new Object();
3: 当一个类加载的时候,类加载只有一次,会产生2部分内容。1部分是类的字节码元数据(方法区)。还会生成一个对象(堆区)。对象的类型是 描述类的类型 Class。
这个大Class 类型的对象也是唯一的。可以通过 类名.class 来得到这个唯一的描述当前类的对象。
好处:不可变以及唯一性。
当前类的大Class 对象。
/**
* 继承线程Thread 类,来模拟火车站售票。
* 每一个售票窗口都代表了一个线程。
* 所有的售票员,都访问同一个售票系统。
*
*/
public class TestTicket {
public static void main(String[] args) {
TicketSystem system = new TicketSystem();
Saler saler1 = new Saler(system);
saler1.setName("售票员小姐姐-1");//设置线程的名字
Saler saler2 = new Saler(system);
saler2.setName("售票员小姐姐-2");//设置线程的名字
Saler saler3 = new Saler(system);
saler3.setName("售票员小姐姐-3");//设置线程的名字
Saler saler4 = new Saler(system);
saler4.setName("售票员小姐姐-4");//设置线程的名字
Saler saler5 = new Saler(system);
saler5.setName("售票员小姐姐-5");//设置线程的名字
saler1.start();
saler2.start();
saler3.start();
saler4.start();
saler5.start();
}
}
//售票系统
class TicketSystem{
//剩余票数
private int count = 100;
// static Object o = new Object();
/**
* @param count 本次卖票的张数
* @return 是否售票成功
*/
public /*synchronized*/ boolean sale(int count){
//使用当前类的 大 Class 对象 作为 监视器。具有不可变和唯一的特性。
synchronized (TicketSystem.class) {
if(this.count >= count){
this.count -= count;
System.out.println(Thread.currentThread().getName() + " 卖出了 "+count+" 张票,剩余票数为:"+this.count);
//让当前线程等待一下下 100
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
return true;
}
}
//
System.out.println(Thread.currentThread().getName() + " 售票失败 ,余票不足 ,剩余票数为:"+this.count);
return false;
}
}
//售票员
class Saler extends Thread{
//售票员需要访问唯一的票务系统
//多个线程对象,访问同一个对象的数据,可以使用同时持有唯一对象引用的方法。
private TicketSystem system;
public Saler(TicketSystem system) {
this.system = system;
}
//线程任务的主体部分。
public void run() {
boolean result = system.sale(1);
while(result){
result = system.sale(1);
}
}
}
三、单例模式最终版
public class SingleTonTest {
static int num = 0;
public static void main(String[] args) {
//在线程1 中 得到唯一的实例。
new Thread(){
@Override
public void run() {
MySingleton singleton1 = MySingleton.getInstance();
System.out.println(singleton1);
}
}.start();
//在线程2中再得到一次
new Thread(){
@Override
public void run() {
MySingleton singleton2 = MySingleton.getInstance();
System.out.println(singleton2);
}
}.start();
//使用Runnable 接口,匿名内部类
new Thread(new Runnable() {
@Override
public void run() {
}
}).start();
//面试题
new Thread(new Runnable() {// 11111
@Override
public void run() {
System.out.println(22222);
}
}){
public void run() {
System.out.println(11111);
};
}.start();
}
}
//懒汉模式单例类
class MySingleton{
private int num = 10;
private static MySingleton singleton;
private MySingleton(){}
//对外的提供的唯一的 方位 唯一实例的方法
public /*synchronized*/ static MySingleton getInstance(){
//为了提高效率,后续的该方法的访问都不会再进行 加锁和解锁的 和监视器是否有锁的判断
if(singleton == null){
synchronized (MySingleton.class) {
//为了保证唯一的实例
if(singleton == null){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
singleton = new MySingleton();
}
}
}
return singleton;
}
public void setNum(int num){
this.num = num;
}
public int getNum(){
return num;
}
}
四、线程死锁
线程死锁:DeadLock。两个进程相互上锁,当两个进程同时被锁住的时候,程序将无法继续向下执行,此时即为死锁。
static Object o1 = new Object()
static Object o2 = new Object();
//线程-1 执行的代码
synchronized(o1){
// thread-1 stop here
synchronized(o2){
}
}
//线程-2 执行 代码
synchronized(o2){
// thread-2 stop here
synchronized(o1){
}
}
出现死锁的条件:
1:同步代码块嵌套。
2:多个线程之间已经锁住的资源为彼此请求锁住的资源。
如果死锁已经产生,那么没有办法解决,只能在编码的时候避免出现死锁。
避免死锁:避免出现嵌套的同步代码块。
public class DeadLockTest {
public static void main(String[] args) {
new DeadLockThread(0).start();
new DeadLockThread(1).start();
}
}
class DeadLockThread extends Thread{
//两个监视器对象
final static Object O1 = new Object();
final static Object O2 = new Object();
private int id;
public DeadLockThread(int id) {
this.id = id;
}
@Override
public void run() {
if(id == 0){
synchronized (O1) {
System.out.println(Thread.currentThread().getName()+"--O1-->O2");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (O2) {
System.out.println(Thread.currentThread().getName()+"--O1-->O2--------");
}
}
}else{
synchronized (O2) {
System.out.println(Thread.currentThread().getName()+"--O2-->O1");
synchronized (O1) {
System.out.println(Thread.currentThread().getName()+"--O2-->O1--------");
}
}
}
}
}
五、线程间通信-管道流
使用管道流实现多个线程之间的信息的交互。
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.PipedInputStream;
import java.io.PipedOutputStream;
/**
* 两个线程 独立的 类,进行信息的交互
* 管道输出流写出的数据,被管道输入流读取到。
* 在一个线程中用管道输出流写出数据。
* 在另外一个线程中使用管道输入流读取 管道输出流写出的数据
*
*/
public class PipedStreamTest {
public static void main(String[] args) throws Exception {
PipedInputStream pis = new PipedInputStream();
PipedOutputStream pos = new PipedOutputStream();
//可以通过构造方法将管道输入输出流建立关联,也可以通过方法。
pis.connect(pos);
PipedOutputStreamThread thread1 = new PipedOutputStreamThread(pos);
PipedInputStreamThread thread2 = new PipedInputStreamThread(pis);
new Thread(){
public void run() {
thread2.start();
};
}.start();
Thread.sleep(5000);
new Thread(){
public void run() {
thread1.start();
};
}.start();
}
}
//管道输出流的线程类
class PipedOutputStreamThread extends Thread{
//持有管道输出流的引用。
private PipedOutputStream pos;
public PipedOutputStreamThread(PipedOutputStream pos) {
this.pos = pos;
}
public void run() {
try {
//使用管道输出流写出数据
String str = "多年不见,你还好么?";
// byte[] bs = str.getBytes();
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(pos));
bw.write(str);
// pos.write(bs);
bw.newLine();
bw.flush();
bw.close();
} catch (Exception e) {
e.printStackTrace();
}finally {
try {
pos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
//用于读取管道输出流写出数据的输入流
class PipedInputStreamThread extends Thread{
private PipedInputStream pis;
public PipedInputStreamThread(PipedInputStream pis) {
this.pis = pis;
}
@Override
public void run() {
try {
BufferedReader br = new BufferedReader(new InputStreamReader(pis));
String str = br.readLine();
System.out.println("接收到的内容为:"+str);
br.close();
} catch (Exception e) {
e.printStackTrace();
}finally{
try {
pis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
六、生产者消费者典型案例
都是Object类的方法。
Object obj = new Object();
obj .wait():让当前线程在当前对象上等待。 让执行这句代码的线程在obj上等待。
obj .notify(): 将在obj 对象上等待的某个线程唤醒。
obj .notifyAll(): 将在obj 对象上等待的所有的线程唤醒。
这三个方法使用的环境:必须让当前线程持有当前对象监视器的锁。
obj .wait() 表示 当前线程必须对 obj 上锁了。
import java.util.Arrays;
/**
* 自定义容器 模拟栈
* 底层使用数组来实现。Object
*
*/
public class MyStack {
//定义栈的初始化容量
public static final int INIT_CAPACITY = 1;
//栈顶指针 所有对元素的操作都是 通过栈顶指针来完成的。
private int index;
//存放元素数组
private Object[] elementData;
public MyStack() {
elementData = new Object[INIT_CAPACITY];
index = 0;
}
/**
* 将 e 压入栈顶
* @param e
*/
public void push(E e){
synchronized (this) {
if(isFull()){
//如果栈已满
//让生产者线程在 当前容器上等待。当前线程进入阻塞状态。并对当前对象解锁。
try {
this.wait();
} catch (InterruptedException e1) {
e1.printStackTrace();
}
}
try {
Thread.sleep(300);
} catch (InterruptedException e1) {
e1.printStackTrace();
}
//生产者顺利的生产了一个面包
elementData[index++] = e;
System.out.println(Thread.currentThread().getName()+"--生产了一个:"+e);
//TODO 通知消费者你可以继续消费了
//生产者线程通知消费者线程可以继续消费了,消费者线程如果处于阻塞状态,那么消费者线程从阻塞状态进入 就绪状态,等待cpu 的下次的调度执行。
this.notify();
}
}
/**
* 弹栈操作
* @return
*/
public synchronized void pop() {
if(isEmpty()){
//消费者线程,发现容器空了,那么就在容器上等待。导致消费者线程进入阻塞状态,并释放对 this 的锁
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
try {
Thread.sleep(300);
} catch (InterruptedException e1) {
e1.printStackTrace();
}
//先指针下移
index --;
E e = (E)elementData[index];
elementData[index] = null;
System.out.println(Thread.currentThread().getName() + "--消费了:"+e);
//如果生产者线程此刻处于阻塞状态。那么会让生产者线程从阻塞状态进入就绪状态,等待cpu 的下次调度,如果被调度执行,那么生产者从上次等待的位置继续执行。
//消费者线程通知在 this 上等待的生产者线程,可以继续生产了。
this.notify();
}
/**
* 获取栈顶元素,但是不删除
* @return
*/
public E peek()throws Exception{
if(isEmpty()){
Exception e = new Exception("囊中羞涩,别摸了!");
throw e;
}else{
return (E)elementData[index-1];
}
}
/**
* 容器中是否包含 e
* @param e
* @return
*/
public boolean search(E e){
if(isEmpty())
return false;
//如果 e 是 null 判断 elementData 是否有null
if(e == null){
for(int i=0;i
if(elementData[i] == null)
return true;
}
}else{//e 不是null
for(int i=0;i
if(e.equals(elementData[i]))
return true;
}
}
return false;
}
/**
* 判断栈是否满了
* @return
*/
private boolean isFull(){
return index == elementData.length;
}
/**
* 栈是否为空
* @return
*/
public boolean isEmpty(){
return index == 0;
}
/**
* 得到元素的个数
* @return
*/
public int size(){
return index;
}
/**
* 容量
* @return
*/
public int capacity(){
return elementData.length;
}
@Override
public String toString() {
return Arrays.toString(Arrays.copyOf(elementData, index));
}
}
public class PCTest {
public static void main(String[] args) {
MyStack stack = new MyStack<>();
Producer producer = new Producer(stack);
producer.setName("巴莉甜甜");
Consumer consumer = new Consumer(stack);
consumer.setName("YiBao");
producer.start();
consumer.start();
}
}
/**
* 消费者线程 ,消费面包
*
*/
public class Consumer extends Thread{
private MyStack stack;
Consumer(MyStack stack) {
super();
this.stack = stack;
}
@Override
public void run() {
for(int i=0;i<10;i++){
stack.pop();
}
}
}
/**
* 面包类
*
*/
public class Bread {
private int id;
Bread(int id) {
super();
this.id = id;
}
@Override
public String toString() {
return "面包 [id=" + id + "]";
}
}
/**
* 生产者线程
*
*/
public class Producer extends Thread{
private MyStack stack;
Producer(MyStack stack) {
super();
this.stack = stack;
}
//生产十个面包
public void run() {
for(int i=0;i<10;i++){
stack.push(new Bread(i));
}
}
}
七、多个消费者生产者出现的问题
1:消费者1 开始消费,等待了。
2:消费者2 开始消费,等待了。
3:生产者 生产了一个馒头,唤醒了 消费者1. 生产者继续生产,等待了。
4:消费者1 开始消费,结果把消费者2 唤醒了。消费者1 等待了。
5:消费者2 开始消费,容器已经是空的了。 数组下标越界。
解决:
import java.util.Arrays;
/**
* 自定义容器 模拟栈
* 底层使用数组来实现。Object
*
*/
public class MyStack {
//定义栈的初始化容量
public static final int INIT_CAPACITY = 1;
//栈顶指针 所有对元素的操作都是 通过栈顶指针来完成的。
private int index;
//存放元素数组
private Object[] elementData;
public MyStack() {
elementData = new Object[INIT_CAPACITY];
index = 0;
}
//让生产者线程在o1上等待。 在o2上唤醒。
static Object o1 = new Object();
//让消费者线程在o2 上等待。在o1上唤醒。
static Object o2 = new Object();
/**
* 将 e 压入栈顶
* @param e
*/
public void push(E e){
synchronized (o1) {
while(isFull()){//如果栈已满
//让生产者线程在 当前容器上等待。当前线程进入阻塞状态。并对当前对象解锁。
try {
o1.wait();
} catch (InterruptedException e1) {
e1.printStackTrace();
}
}
// try {
// Thread.sleep(300);
// } catch (InterruptedException e1) {
// e1.printStackTrace();
// }
//生产者顺利的生产了一个面包
elementData[index++] = e;
System.out.println(Thread.currentThread().getName()+"--生产了一个:"+e);
//TODO 通知消费者你可以继续消费了
//生产者线程通知消费者线程可以继续消费了,消费者线程如果处于阻塞状态,那么消费者线程从阻塞状态进入 就绪状态,等待cpu 的下次的调度执行。
// this.notifyAll();
synchronized (o2) {
o2.notify();
}
}
}
/**
* 弹栈操作
* @return
*/
public /*synchronized */void pop() {
synchronized (o2) {
while(isEmpty()){
//消费者线程,发现容器空了,那么就在容器上等待。导致消费者线程进入阻塞状态,并释放对 this 的锁
try {
o2.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// try {
// Thread.sleep(300);
// } catch (InterruptedException e1) {
// e1.printStackTrace();
// }
//先指针下移
index --;
E e = (E)elementData[index];
elementData[index] = null;
System.out.println(Thread.currentThread().getName() + "--消费了:"+e);
//如果生产者线程此刻处于阻塞状态。那么会让生产者线程从阻塞状态进入就绪状态,等待cpu 的下次调度,如果被调度执行,那么生产者从上次等待的位置继续执行。
//消费者线程通知在 this 上等待的生产者线程,可以继续生产了。
// this.notifyAll();
synchronized (o1) {
o1.notify();
}
}
}
/**
* 获取栈顶元素,但是不删除
* @return
*/
public E peek()throws Exception{
if(isEmpty()){
Exception e = new Exception("囊中羞涩,别摸了!");
throw e;
}else{
return (E)elementData[index-1];
}
}
/**
* 容器中是否包含 e
* @param e
* @return
*/
public boolean search(E e){
if(isEmpty())
return false;
//如果 e 是 null 判断 elementData 是否有null
if(e == null){
for(int i=0;i
if(elementData[i] == null)
return true;
}
}else{//e 不是null
for(int i=0;i
if(e.equals(elementData[i]))
return true;
}
}
return false;
}
/**
* 判断栈是否满了
* @return
*/
private boolean isFull(){
return index == elementData.length;
}
/**
* 栈是否为空
* @return
*/
public boolean isEmpty(){
return index == 0;
}
/**
* 得到元素的个数
* @return
*/
public int size(){
return index;
}
/**
* 容量
* @return
*/
public int capacity(){
return elementData.length;
}
@Override
public String toString() {
return Arrays.toString(Arrays.copyOf(elementData, index));
}
}
八、jdk1.5的针对同步嵌套的解决
import java.util.Arrays;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* 自定义容器 模拟栈
* 底层使用数组来实现。Object
*
*/
public class MyStack {
//定义栈的初始化容量
public static final int INIT_CAPACITY = 2;
//栈顶指针 所有对元素的操作都是 通过栈顶指针来完成的。
private int index;
//存放元素数组
private Object[] elementData;
public MyStack() {
elementData = new Object[INIT_CAPACITY];
index = 0;
}
//使用jdk1.5 的针对线程死锁的解决方案。
private Lock lock = new ReentrantLock();
//需要两个Condition 对象。
//生产者的Condition
private Condition proCon = lock.newCondition();
//消费者的Condition
private Condition conCon = lock.newCondition();
/**
* 将 e 压入栈顶
* @param e
*/
public void push(E e){
lock.lock();
try {
while(isFull()){//如果栈已满
//让生产者线程在 当前容器上等待。当前线程进入阻塞状态。并对当前对象解锁。
try {
// o1.wait();
proCon.await();
} catch (InterruptedException e1) {
e1.printStackTrace();
}
}
//生产者顺利的生产了一个面包
elementData[index++] = e;
System.out.println(Thread.currentThread().getName()+"--生产了一个:"+e);
//TODO 通知消费者你可以继续消费了
//生产者线程通知消费者线程可以继续消费了,消费者线程如果处于阻塞状态,那么消费者线程从阻塞状态进入 就绪状态,等待cpu 的下次的调度执行。
// this.notifyAll();
// o2.notify();
conCon.signal();
} finally {
lock.unlock();
}
}
/**
* 弹栈操作
* @return
*/
public /*synchronized */void pop() {
lock.lock();
try {
while(isEmpty()){
//消费者线程,发现容器空了,那么就在容器上等待。导致消费者线程进入阻塞状态,并释放对 this 的锁
try {
// o2.wait();
conCon.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//先指针下移
index --;
E e = (E)elementData[index];
elementData[index] = null;
System.out.println(Thread.currentThread().getName() + "--消费了:"+e);
//如果生产者线程此刻处于阻塞状态。那么会让生产者线程从阻塞状态进入就绪状态,等待cpu 的下次调度,如果被调度执行,那么生产者从上次等待的位置继续执行。
//消费者线程通知在 this 上等待的生产者线程,可以继续生产了。
// this.notifyAll();
// o1.notify();
proCon.signal();
} finally {
lock.unlock();
}
}
/**
* 获取栈顶元素,但是不删除
* @return
*/
public E peek()throws Exception{
if(isEmpty()){
Exception e = new Exception("囊中羞涩,别摸了!");
throw e;
}else{
return (E)elementData[index-1];
}
}
/**
* 容器中是否包含 e
* @param e
* @return
*/
public boolean search(E e){
if(isEmpty())
return false;
//如果 e 是 null 判断 elementData 是否有null
if(e == null){
for(int i=0;i
if(elementData[i] == null)
return true;
}
}else{//e 不是null
for(int i=0;i
if(e.equals(elementData[i]))
return true;
}
}
return false;
}
/**
* 判断栈是否满了
* @return
*/
private boolean isFull(){
return index == elementData.length;
}
/**
* 栈是否为空
* @return
*/
public boolean isEmpty(){
return index == 0;
}
/**
* 得到元素的个数
* @return
*/
public int size(){
return index;
}
/**
* 容量
* @return
*/
public int capacity(){
return elementData.length;
}
@Override
public String toString() {
return Arrays.toString(Arrays.copyOf(elementData, index));
}
}