目的是避免多个线程在操作同一个资源(共享资源)线程安全问题,其中多个线程操作的动作存在不同。
生产者线程对共享资源做写操作,消费者线程读共享资源,生产者表示发布资源,消费者表示读取资源,他们都同时对共享资源进行操作,但是操作的动作不同。
共享资源线程不安全演示代码:
package duoxiancheng;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
/**
* 共享资源实体类
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
class Res {
private String userName;
private String sex;
}
/**
* 生成者线程
*/
@EqualsAndHashCode(callSuper = true)
@Data
@NoArgsConstructor
@AllArgsConstructor
class Producer extends Thread {
private Res res;
public void run() {
int count = 0;
while (true) {
if (count == 0) {
res.setUserName("小红");
res.setSex("女");
} else {
res.setUserName("小明");
res.setSex("男");
}
// 每次循环count依次取:0 1 0 1
count = (count + 1) % 2;
}
}
}
/**
* 消费者
*/
@EqualsAndHashCode(callSuper = true)
@Data
@AllArgsConstructor
@NoArgsConstructor
class Consumer extends Thread {
private Res res;
@Override
public void run() {
while (true){
System.out.println(res.toString());
}
}
}
public class ShengchanXiaofei {
public static void main(String[] args) {
// 共享资源res
Res res = new Res();
Producer producer = new Producer(res);
Consumer consumer = new Consumer(res);
producer.start();
consumer.start();
}
}
这段代码会产生线程安全问题,小红性别出现了男,出现的这样结果是因为消费者线程读共享资源的时候,读到一半刚好被暂停了,生产者线程又修改了资源,就导致了数据出错的问题。
生产者线程和消费者线程之间要进行同步!
是两个线程都要进行同步,这样才能保证两个线程之间的数据的原子性问题。
用synchronized代码块,并且两个线程使用同一把锁,故不能使用this锁,因为这个两个线程是不同类。 他们共享Res类资源,故可以用res对象作为锁。
/**
* 生成者线程
*/
@EqualsAndHashCode(callSuper = true)
@Data
@NoArgsConstructor
@AllArgsConstructor
class Producer extends Thread {
private Res res;
public void run() {
int count = 0;
while (true) {
synchronized (res){
if (count == 0) {
res.setUserName("小红");
res.setSex("女");
} else {
res.setUserName("小明");
res.setSex("男");
}
// 每次循环count依次取:0 1 0 1
count = (count + 1) % 2;
}
}
}
}
/**
* 消费者
*/
@EqualsAndHashCode(callSuper = true)
@Data
@AllArgsConstructor
@NoArgsConstructor
class Consumer extends Thread {
private Res res;
@Override
public void run() {
while (true){
synchronized (res){
System.out.println(res.toString());
}
}
}
}
但是这个代码还是有缺陷,和实际的生成应用还是有差别,消费者拿数据应该是一男一女交替,而不是一片连在一起。
写完一个,读一个,保证这样的同步。生产者没有任何生产,消费者不能读。消费者没有消费完生产者不能生产。某程度上等于没有缓冲区的生产情况。
使用wait和notify方法可以解决。
wait让当前线程从运行状态变为休眠状态,让出cpu执行权,释放当前锁的资源,把资源给阻塞中的线程。
notify让当前线程从休眠状态变为运行状态
package duoxiancheng;
import lombok.*;
/**
* 共享资源实体类
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
class Res {
private String userName;
private String sex;
// false时生产者运行,true时消费者运行
private Boolean flag = false;
}
/**
* 生成者线程
*/
@EqualsAndHashCode(callSuper = true)
@Data
@NoArgsConstructor
@AllArgsConstructor
class Producer extends Thread {
private Res res;
@SneakyThrows
public void run() {
int count = 0;
while (true) {
synchronized (res) {
if (res.getFlag()) {
// flag为true时变成休眠状态,并且释放锁的资源
res.wait();
}
if (count == 0) {
res.setUserName("小红");
res.setSex("女");
} else {
res.setUserName("小明");
res.setSex("男");
}
// 每次循环count依次取:0 1 0 1
count = (count + 1) % 2;
// 写操作完成,更改flag值,再进行生产者线程时将变为休眠状态
res.setFlag(true);
// 唤醒当前被wait的线程
res.notify();
}
}
}
}
/**
* 消费者
*/
@EqualsAndHashCode(callSuper = true)
@Data
@AllArgsConstructor
@NoArgsConstructor
class Consumer extends Thread {
private Res res;
@SneakyThrows
@Override
public void run() {
while (true) {
synchronized (res) {
// flag为true时消费者运行,如果flag为false时线程应休眠,释放对象锁。
if(!res.getFlag()){
res.wait();
}
System.out.println(res.toString());
// 消费者执行完毕,更改flag值
res.setFlag(false);
// 唤醒当前被wait的线程
res.notify();
}
}
}
}
public class ShengchanXiaofei {
public static void main(String[] args) {
// 共享资源res
Res res = new Res();
Producer producer = new Producer(res);
Consumer consumer = new Consumer(res);
producer.start();
consumer.start();
}
}
两点重要的点
一是wait让线程让出cpu执行权,释放当前锁
二是notify唤醒使用wait的线程,即通知正在等待控制权的线程可以继续运行
唤醒所有等待
wait可以释放锁的资源
sleep不会释放锁的资源
都是做休眠
wait需要notify才能从休眠变为运行状态
sleep只要时间到了就变为运行状态
Object类中有哪些方法?
wait,notify,notifyAll,equals,hashCode,getClass,clone,toString,finalize
synchronized代码块什么时候开始上锁?什么时候开始释放锁?
代码开始和代码结束
synchronized有什么缺点?
效率低,扩展性不高,不能自定义
你了解哪些java并发包?
Atomic中的原子类如AtomicInteger,AtomicIntegerArray,AtomicReference等,Lock锁
多线程并发和网页并发的区别?
多线程并发是多个Thread线程同时操作同一个资源,网页并发是多个请求同时访问一台服务器。
用来实现锁的功能,Lock接口提供了与synchronized关键字类似的同步功能,但需要在使用时手动获取锁和释放锁。
lock锁一般定义在共享资源中,多个线程使用同一个lock对象
Lock lock = new ReentrantLock();
lock.lock();
try{
}
finally {
lock.unlock();
}
wait和notify不能在这里使用,只能在synchronized代码块中使用!
package duoxiancheng;
import com.sun.org.apache.xml.internal.security.signature.reference.ReferenceNodeSetData;
import lombok.*;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* 共享资源实体类
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
class Res {
private String userName;
private String sex;
// false时生产者运行,true时消费者运行
private Boolean flag = false;
private Lock lock = new ReentrantLock();
}
/**
* 生成者线程
*/
@EqualsAndHashCode(callSuper = true)
@Data
@NoArgsConstructor
@AllArgsConstructor
class Producer2 extends Thread {
private Res res;
@SneakyThrows
public void run() {
int count = 0;
while (true) {
try {
// 开始上锁
res.getLock().lock();
if (count == 0) {
res.setUserName("小红");
res.setSex("女");
} else {
res.setUserName("小明");
res.setSex("男");
}
// 每次循环count依次取:0 1 0 1
count = (count + 1) % 2;
res.setFlag(true);
} catch (Exception e) {
e.printStackTrace();
} finally {
// 释放锁
res.getLock().unlock();
}
}
}
}
/**
* 消费者
*/
@EqualsAndHashCode(callSuper = true)
@Data
@AllArgsConstructor
@NoArgsConstructor
class Consumer2 extends Thread {
private Res res;
@SneakyThrows
@Override
public void run() {
while (true) {
try {
res.getLock().lock();
System.out.println(res.toString());
res.setFlag(false);
} catch (Exception e) {
e.printStackTrace();
} finally {
// 释放锁
res.getLock().unlock();
}
}
}
}
public class LockDemo {
public static void main(String[] args) {
// 共享资源res
Res res = new Res();
Producer2 producer = new Producer2(res);
Consumer2 consumer = new Consumer2(res);
producer.start();
consumer.start();
}
}
日后其他博客里再写
Condition一般和Lock锁一起使用。
Condition的功能类似于传统技术中的Object.wait和notify。
在线程中实例化Condition
Condition condition = res.getLock().newCondition();
使用await和signal方法等待和唤醒
示例代码:
package duoxiancheng;
import lombok.*;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
@Data
@AllArgsConstructor
@NoArgsConstructor
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) {
try {
res.lock.lock();
if (res.flag) {
try {
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;
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) {
try {
res.lock.lock();
if (!res.flag) {
try {
newCondition.await();
} catch (Exception e) {
// TODO: handle exception
}
}
System.out.println(res.userName + "," + res.sex);
res.flag = false;
newCondition.signal();
} catch (Exception e) {
// TODO: handle exception
} finally {
res.lock.unlock();
}
}
}
}
public class LockDemo {
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();
}
}
说说lock锁和synchronized(同步)的区别?
程序执行到一半的时候,直接把程序stop,程序中断,后面一半程序没有执行完,先前执行的代码也不会回滚,即程序不会恢复,不安全。
根据stop的缺点进行优化,代码执行完毕再停止。无非让循环停止。
代码设计:
package ch3;
import lombok.SneakyThrows;
class MyThread extends Thread {
private volatile Boolean flag = true;
@Override
public void run() {
System.out.println("start loop");
while (flag) {
}
System.out.println("stop loop");
}
public void stopThread() {
this.flag = false;
}
}
public class StopDemo {
@SneakyThrows
public static void main(String[] args) {
MyThread myThread = new MyThread();
myThread.start();
for (int i = 0; i < 2; i++) {
System.out.println("main thread, waiting");
Thread.sleep(1000);
}
myThread.stopThread();
for (int i = 0; i < 5; i++) {
System.out.println("main thread, waiting");
Thread.sleep(1000);
}
}
}
但是这样的设计还有什么问题呢?如果while循环中存在wait等待,则不会到while判断为false退出循环,直到线程被notify唤醒,然而这样不是我们希望的,因此正确的用法是使用interrupt方法,interrupt方法会抛出异常,在异常处理中处理停止线程。
package ch3;
import lombok.SneakyThrows;
class MyThread extends Thread {
private volatile Boolean flag = true;
@Override
public synchronized void run() {
try{
System.out.println("start loop");
while (flag) {
wait();
}
}catch (Exception e){
stopThread();
System.out.println("stop loop");
}
}
public void stopThread() {
this.flag = false;
}
}
public class StopDemo {
@SneakyThrows
public static void main(String[] args) {
MyThread myThread = new MyThread();
myThread.start();
for (int i = 0; i < 2; i++) {
System.out.println("main thread, waiting");
Thread.sleep(1000);
}
myThread.interrupt();
for (int i = 0; i < 5; i++) {
System.out.println("main thread, waiting");
Thread.sleep(1000);
}
}
}
是本地线程, 为每一个线程提供一个局部变量。
线程t1、t2共享主内存中的数据,使用ThreadLoacl后,在本地线程提供局部变量,将共享的数据变成本地的变量。
package ch3;
import lombok.AllArgsConstructor;
class ResNumber {
public int count = 0;
public static ThreadLocal<Integer> threadLocal = new ThreadLocal<Integer>() {
@Override
protected Integer initialValue() {
// threadLocal起始值赋值为0
return 0;
}
};
public String getNumber() {
count = threadLocal.get() + 1;
threadLocal.set(count);
return String.valueOf(count);
}
}
@AllArgsConstructor
class LocalThread extends Thread {
public ResNumber resNumber;
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(getName() + ":" + resNumber.getNumber());
}
}
}
public class ThreadLoca {
public static void main(String[] args) {
ResNumber resNumber = new ResNumber();
LocalThread localThread1 = new LocalThread(resNumber);
LocalThread localThread2 = new LocalThread(resNumber);
LocalThread localThread3 = new LocalThread(resNumber);
localThread1.start();
localThread2.start();
localThread3.start();
}
}
Map
底层代码
set方法:
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
get方法:
public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
return setInitialValue();
}