线程安全
java允许多线程并发控制,当多个线程同时操作一个可共享的资源变量时(如数据的增删改查),将会导致数据不准确,相互之间产生冲突,即一是存在共享数据,二是存在多条线程共同操作共享数据。因此为了解决这个问题,我们可能需要这样一个方案,当存在多个线程操作共享数据时,需要保证同一时刻有且只有一个线程在操作共享数据,其他线程必须等到该线程处理完数据后再进行,从而保证了该变量的唯一性和准确性。
解决方法
1.synchronized
Java语言的关键字,当它用来修饰一个方法或者一个代码块的时候,能够保证在同一时刻最多只有一个线程执行该段代码。
- 同步代码块
即有synchronized关键字修饰的语句块 - 同步方法
即有synchronized关键字修饰的方法。由于java的每个对象都有一个内置锁,当 用此关键字修饰方法时,内置锁会保护整个方法。在调用该方法前,需要获得内置锁,
public synchronized void active(){}
- 同步静态方法
public static synchronized void sendSms(){}
八锁
即关于锁的八个问题
线程八锁的关键:
A:同步代码块这里的锁对象可以是任意对象。
B:同步方法这里的锁对象是this
C:静态同步方法这里的锁对象是当前类的字节码文件对象
2.java.util.concurrent.locks包下使用重入锁(Lock)实现线程同步
1.Lock
Lock是一个接口:
public interface Lock {
void lock();
void lockInterruptibly() throws InterruptedException;
boolean tryLock();
boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
void unlock();
Condition newCondition();
}
tryLock()方法是有返回值的,它表示用来尝试获取锁,如果获取成功,则返回true,如果获取失败(即锁已被其他线程获取),则返回false,也就说这个方法无论如何都会立即返回。在拿不到锁时不会一直在那等待。
tryLock(long time, TimeUnit unit)方法和tryLock()方法是类似的,只不过区别在于这个方法在拿不到锁时会等待一定的时间,在时间期限之内如果还拿不到锁,就返回false。如果如果一开始拿到锁或者在等待期间内拿到了锁,则返回true。
一般格式:上锁——处理事务(try-catch)——释放锁
Lock lock = ...;
lock.lock();
try{
//处理任务
}catch(Exception ex){
}finally{
lock.unlock(); //释放锁
}
Lock,必须主动去释放锁,并且在发生异常时,不会自动释放锁。因此一般来说,使用Lock必须在try{}catch{}块中进行,并且将释放锁的操作放在finally块中进行,以保证锁一定被被释放,防止死锁的发生。
ReentrantLock(可重入锁)是唯一实现了Lock接口的类
synchornized和lock 的区别
- synchronized是关键字,是JVM层面的底层什么都帮我们做了,而Lock是一个接口,是JDK层面的有丰富的API。
- synchronized会自动释放锁,而Lock必须手动释放锁(try-catch-finally模式)。
- synchronized是不可中断的线程,Lock可以中断线程。
- 通过Lock可以通过tryLock()方法知道线程有没有拿到锁,而synchronized不能。
- synchronized能锁住方法和代码块,而Lock只能锁住代码块。
案例卖电影票
方式一、同步方法
public class SellTicket {
public static void main(String[] args) {
Ticket ticket = new Ticket();
new Thread(()->{
for(int i=1;i<50;i++){
ticket.sale();
}
},"A").start();
new Thread(()->{
for(int i=1;i<50;i++){
ticket.sale();
}
},"B").start();
new Thread(()->{
for(int i=1;i<50;i++){
ticket.sale();
}
},"C").start();
}
}
class Ticket{
private int num = 50;
//买票方式
public synchronized void sale() {
if(num>0) {
System.out.println(Thread.currentThread().getName()+"卖出了" + (num--) +"张票,剩余" + num);
}
}
}
方式二、使用lock
public class SellTicket2 {
public static void main(String[] args) {
Ticket ticket = new Ticket();
new Thread(()->{for(int i=1;i<50;i++)ticket.sale();},"A").start();
new Thread(()->{for(int i=1;i<50;i++)ticket.sale();},"B").start();
new Thread(()->{for(int i=1;i<50;i++)ticket.sale();},"C").start();
new Thread(()->{for(int i=1;i<50;i++)ticket.sale();},"D").start();
}
}
class Ticket2{
//加锁三步曲
//1.new ReentrantLock()
//2.lock.lock 加锁
//3.finally lock.unlock 释放锁
private int num = 50;
Lock lock = new ReentrantLock();
//买票方式
public void sale() {
lock.lock();
try {
//业务
if(num>0) {
System.out.println(Thread.currentThread().getName()+"卖出了" + (num--) +"张票,剩余" + num);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
//释放锁
lock.unlock();
}
}
}
线程通信
1、借助于Object类的wait()、notify()和notifyAll()实现通信
线程执行wait()后,就放弃了运行资格,处于冻结状态;线程再次运行时依然再这此处复活;
线程运行时,内存中会建立一个线程池,冻结状态的线程都存在于线程池中,notify()执行时唤醒的也是线程池中的线程,线程池中有多个线程时唤醒第一个被冻结的线程。
notifyall(), 唤醒线程池中所有线程。
wait(), notify(),notifyall()都用在同步里面,因为这3个函数是对持有锁的线程进行操作,而只有同步才有锁,所以要使用在同步中;
wait(),notify(),notifyall(), 在使用时必须标识它们所操作的线程持有的锁,因为等待和唤醒必须是同一锁下的线程;而锁可以是任意对象,所以这3个方法都是Object类中的方法
单个生产者和消费者
public class PrCoDemo {
public static void main(String[] args) {
Data data = new Data();
new Thread(() -> {
for(int i=0; i<10;i++ ){
try {
data.increment();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"A").start();
new Thread(() -> {
for (int i = 0; i < 10; i++) {
try {
data.decrement();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"B").start();
}
}
class Data {
private int num = 0;
public synchronized void increment() throws InterruptedException {
if(num != 0) {
this.wait();
}
//业务
num++;
System.out.println(Thread.currentThread().getName() + "=>" + num);
//通知唤醒
this.notify();
}
public synchronized void decrement() throws InterruptedException {
//判断等待
if(num == 0) {
this.wait();
}
//业务
num--;
System.out.println(Thread.currentThread().getName() +"=>" + num);
//通知唤醒
this.notify();
}
}
多个生产者和消费者
/*
线程之间的通信问题,生产者和消费者,等待唤醒,通知唤醒
线程交替执行,A、B操作同一变量 num=0
判断等待,业务、通知、
*/
public class Produtor {
public static void main(String[] args) {
Date date = new Date();
new Thread(()->{ for(int i=0;i<10;i++)
try {
date.decrement();
} catch (InterruptedException e) {
e.printStackTrace();
}
},"A" ).start();
new Thread(()->{for(int i=0;i<10;i++)
try {
date.increment();
} catch (InterruptedException e) {
e.printStackTrace();
}
},"B" ).start();
new Thread(()->{for(int i=0;i<10;i++)
try {
date.increment();
} catch (InterruptedException e) {
e.printStackTrace();
}
},"C" ).start();
new Thread(()->{for(int i=0;i<10;i++)
try {
date.increment();
} catch (InterruptedException e) {
e.printStackTrace();
}
},"D" ).start();
}
}
class Date{
private int number = 0;
public synchronized void increment() throws InterruptedException {
//if判断一次
//原先是if,现在改成while,这样消费者线程从冻结状态醒来时,还会再次判断
while(number != 0) {
//等待
this.wait();
}
number++;
System.out.println(Thread.currentThread().getName() + "=>"+number);
this.notifyAll();
}
public synchronized void decrement() throws InterruptedException {
while(number == 0) {
this.wait();
}
number --;
//通知其他线程,我完毕了
System.out.println(Thread.currentThread().getName() + "=>"+number);
this.notifyAll();//用notifyAll(),这样消费者线程消费完一个商品后可以将等待中的生产者线程唤醒,防止出现所有生产者和消费者都在wait()的情况。
}
}
/*
A执行完调用B,B执行完调用C
*/
public class OrderConsumer {
public static void main(String[] args) {
Data data = new Data();
new Thread(() -> {
for(int i=0;i<10;i++) {
data.printlnA();
}
},"A").start();
new Thread(() -> {
for(int i=0;i<10;i++) {
data.printlnB();
}
},"B").start();
new Thread(() -> {
for(int i=0;i<10;i++) {
data.printlnC();
}
},"C").start();
}
}
class Data {
private Lock lock = new ReentrantLock();
private Condition condition1 = lock.newCondition();
private Condition condition2 = lock.newCondition();
private Condition condition3 = lock.newCondition();
private int num = 1;
public void printlnA() {
//上锁
lock.lock();
//判断->执行
try {
while ( num != 1){
condition1.await();
}
System.out.println(Thread.currentThread().getName() + "=>AAAAAAAA");
//唤醒
num = 2;
condition2.signal();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
//释放锁
lock.unlock();
}
}
public void printlnB() {
//上锁
lock.lock();
try {
//判断-> 执行
while( num != 2) {
condition2.await();
}
//业务
System.out.println(Thread.currentThread().getName() + "=> BBBBBB");
//唤醒
num = 3;
condition3.signal();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
//释放锁
lock.unlock();
}
}
public void printlnC() {
//上锁
lock.lock();
try {
//判断执行
while( num != 3) {
condition3.await();
}
//业务
System.out.println(Thread.currentThread().getName()+ "=> CCCCCC");
//唤醒
num = 1;
condition1.signal();
} catch(Exception e) {
e.printStackTrace();
} finally {
//释放锁
lock.unlock();
}
}
}
sleep和wait的区别
1、来自不同的类
wait => Object
sleep => Thread
2、关于锁的释放
wait 会释放锁,sleep 睡觉了,抱着锁睡觉,不会释放!
3、使用的范围是不同的
wait 必须在同步代码块中
sleep 可以再任何地方睡