目录
一、线程安全
二、线程安全问题
三、线程安全
1.同步代码块
2.同步方法
3.Lock锁
3.1常用方法:
3.2 死锁
3.3 练习:
四、生产者和消费者(线程通信问题)
如果有多个线程在同时运行,而这些线程可能会同时运行这些代码,程序每次运行的结果和单线程每次运行的结果是一样的,就是线程安全的,反之则是非线程安全的。
现在有如下场景,电影院要卖票,我们模拟电影院的网上卖票过程。本次电影的座位共
100个(本场电影只能卖100张票)。我们来模拟电影院的售票窗口,实现多个窗口同时卖这
场电影票(多个窗口一起卖这100张票)。
售票窗口我们可以用线程来模拟。票数我们可以在 Runnable 的实现类中设定。
package thread;
public class Demo {
public static void main(String[] args) {
SellTicket sellTicket=new SellTicket();
Thread t1 = new Thread(sellTicket,"1号窗口");
Thread t2 = new Thread(sellTicket,"2号窗口");
Thread t3 = new Thread(sellTicket,"3号窗口");
Thread t4 = new Thread(sellTicket,"4号窗口");
Thread t5 = new Thread(sellTicket,"5号窗口");
t1.start();
t2.start();
t3.start();
t4.start();
t5.start();
}
}
class SellTicket implements Runnable {
private int tickets=100;//总票数
private Object object=new Object();//锁
@Override
public void run() {
//售票窗口一直开放
while (true){
//同步
synchronized (object){
//还有票
if (tickets>0){
try {
Thread.sleep(100);//模拟出票
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println(Thread.currentThread().getName()+"正在出售第【"+tickets--+"】张票");
}
}
}
}
}
线程安全问题都是由全局变量及静态变量引起的, 若每个线程中对全局变量及静态变量只有读操作、没有写操作,一般来说,这个全局变量是线程安全的;若有多个线程同时执行写操作,一般都需要考虑线程同步,否则的话就可能影响线程安全。
安全问题出现的条件:
如果解决多线程安全问题?
当我们使用多个线程访问同一资源的时候,且多个线程中对资源有写的操作,就容易出现线程安全问题。要解决多线程并发访问同一资源的问题:也就是解决重复票和不存在票问题,Java中提供了同步机制来解决。
也就是说一号窗口线程进入操作的时候,其他线程只能在外等着,一号窗口操作结束,其他代码(也包括一号窗口)才有机会去执行。也就是说在某个线程修改共享资源的时候,其他线程不能修改该资源,等待修改完毕同步之后,才能去抢夺CPU资源,完成对应的操作。
为了保证每个线程都能执行原子操作,Java提供了同步机制。
原子操作时不需要synchronized的,所谓原子操作是不能被线程调度机制打断的操作,这种操作一旦开始执行,就一直运行到结束,中间不会发生切换。
如果这个操作所处的层的更高层不能发现其内部实现和结构,那么这个操作就是一个原子操作。
原子操作可以是一个步骤,也可以是多个步骤,但是它的顺序不能被打乱,而且不能被切割只执行其中的一部分。
将整个操作视作一个整体是原子性的核心特征。
Java提供了三种同步机制:
synchronized关键字可以用于方法中的某个区块中,表示只对这个区块的资源实行互斥访问。
synchronized(同步锁){
//同步的代码
}
同步锁:对象的同步锁只是一个概念,可以想象成在任何一个对象上标记了一个锁。
使用synchronized修饰的方法,叫做同步方法,保证A线程执行该方法的时候,其他线程只能在方法外等着。
public synchronized void method(){
// 可能出现安全问题的代码
}
或者
synchronized public void method(){
//可能出现安全问题的代码
}
同步方法中的同步锁是什么?
对于实例方法,同步锁就是this。
对于static方法,同步锁就是方法所在类的字节码文件(类名.class)。
java.util.concurrent.locks.Lock机制提供了比synchronized代码块和synchronized方法更广泛的锁定操作,同步方法/同步代码块有的功能Lock都有,除此之外更强大,更体现面向对象。在JDK5引入了ReentrantLock,ReentrantLock重入锁,是实现Lock接口的一个类,也是在实际编程中总是用皮率很高的一个锁,支持重入性,表示能够对共享资源重复加锁,即当前线程获取该锁后再次获取不会被阻塞,支持公平锁和非公平锁两种方式。
线程死锁是指由于两个或多个线程互相持有对方所需要的资源 ,导致这些线程处于等待状态,无法前往执行。
package thread;
public class Demo04 {
public static void main(String[] args) {
Object A = new Object();
Object B = new Object();
new Thread(()->{
while (true){
synchronized (A){
synchronized (B){
System.out.println("线程1");
}
}
}
}).start();
new Thread(()->{
while (true){
synchronized (B){
synchronized (A){
System.out.println("线程2");
}
}
}
}).start();
}
}
写5个线程对 i 进行 100次 加一操作,再写 5 个线程对 i 进行100次 减一操作,输出结果.
package thread;
import java.util.concurrent.CountDownLatch;
/**
* 写5个线程对 i 进行 100次 加一操作,再写 5 个线程对 i 进行100次 减一操作,输出结果
*/
public class Five {
// public static int i=0;
public static void main(String[] args) {
//第一种
// Thread[] t1 = new Thread[5];//加1的线程
// Thread[] t2 = new Thread[5];//减1的线程
//
// //加1的五个线程
// for (int j = 0; j < 5; j++) {
// t1[j]=new Thread(()->{
// synchronized (Five.class){
// for (int k = 0; k < 1000; k++) {
// i++;
// }
// }
//
// });
// t1[j].start();
// }
//
// //减1的五个线程
// for (int j = 0; j < 5; j++) {
// t2[j]=new Thread(()->{
// synchronized (Five.class){
// for (int k = 0; k <1000; k++) {
// i--;
// }
// }
//
// });
// t2[j].start();
// }
//
// for (int j = 0; j < 5; j++) {
// try {
// t1[j].join();
// t2[j].join();
// } catch (InterruptedException e) {
// throw new RuntimeException(e);
// }
// }
// System.out.println(Thread.currentThread().getName()+":"+i);
Operate operate = new Operate();
//线程计数
CountDownLatch countDownLatch = new CountDownLatch(10);
//定义Runnable对向执行加1操作
Runnable addRun=()->{
for (int j = 0; j < 100; j++) {
operate.add();
}
//减少计数器的值
countDownLatch.countDown();
};
//定义Runnable对向执行减1操作
Runnable subRun=()->{
for (int j = 0; j < 100; j++) {
operate.sub();
}
countDownLatch.countDown();
};
for (int j = 0; j < 5; j++) {
new Thread(addRun).start();
}
for (int j = 0; j < 5; j++) {
new Thread(subRun).start();
}
try {
countDownLatch.await();//等待线程数为0,说明十个线程都执行结束了
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println(operate.getI());
}
}
class Operate{
private int i=0;
public void add(){
synchronized (this){
i++;
}
}
public void sub(){
synchronized (this){
i--;
}
}
public int getI(){
return i;
}
}
生产者消费者模式是一种十分经典的多线程协作模式。
所谓生产者消费者问题,实际上主要包含了两类线程:
一种是生产者线程用于生产数据
一种是消费者线程用于消费数据
为了解耦生产者和消费者的关系,通常会采用共享的数据区域,就像是一个仓库。
生产者生产数据之后直接放到共享数据区中,并不需要关心消费者的行为
消费者只需要在共享数据区中去获取数据,并不需要关心生产者的行为。
调用wait()/notify()/notifyAll()方法时必须在同步方法/同步代码块中调用
package thread;
import java.time.LocalDateTime;
/**
* 线程间操作的必要性
* 生产者消费者问题
*/
public class Demo07 {
public static void main(String[] args) {
Cook cook = Cook.getCOOK();
//厨师线程
Maker maker = new Maker(cook, "邹厨师");
//顾客
Eater e1 = new Eater(cook,"浩哥");
Eater e2 = new Eater(cook, "龙哥");
Eater e3 = new Eater(cook, "王亮");
maker.start();
e1.start();
e2.start();
e3.start();
}
}
//厨师类
class Cook{
private String bread;//面包
private static final Cook cook=new Cook();
private Cook(){}
public static Cook getCOOK() {
return cook;
}
//同步 制作面包
public synchronized void make(){
if (bread!=null){
try {
System.out.println(Thread.currentThread().getName()+"睡了");
wait();
System.out.println(Thread.currentThread().getName()+"醒了");
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
//制作面包的过程
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
bread= LocalDateTime.now().toString();
notify();
System.out.println(Thread.currentThread().getName()+"做了一个面包"+bread);
}
//吃
public synchronized void eat(){
if (bread==null){
try {
System.out.println(Thread.currentThread().getName()+"睡了");
wait();
System.out.println(Thread.currentThread().getName()+"醒了");
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}else {
try {
//吃面包的过程
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println(Thread.currentThread().getName()+"吃了"+bread);
bread=null;
notifyAll();//确保把厨师唤醒
}
}
}
//顾客类---主要是吃
class Eater extends Thread{
Cook cook;
public Eater(Cook cook,String name) {
super(name);
this.cook = cook;
}
@Override
public void run() {
while (true){
cook.eat();
}
}
}
//制作面包的类
class Maker extends Thread{
Cook cook;
public Maker(Cook cook,String name){
super(name);
this.cook=cook;
}
@Override
public void run() {
while (true){
cook.make();
}
}
}