一、多线程同步机制,线程安全
- 要想查看一个程序是否是线程安全的,就看该程序的方法是不是同步方法。线程安全其实就是在该类进行操作的时候不会被其他操作所影响,会保证数据的安全。对于程序来说,要保证多个线程在去进行同一个数据的操作的时候,数据要保证一致,需要的结果和运行结果要是一致的。
- 线程不安全:指的是多线程进行同一个数据的操作时,数据是不一致的,尤其是对数据进行写操作。
- 使用runnable创建线程类,多个thread线程其实是同时在对同一个runnable线程对象在进行操作。 最终run方法是在操作同一个runnable对象,无论属性是静态的还是非静态 ,都可以保证操作的是同一个数据 ,(同一个对象的内存中的属性值),只有在第一次抢占的时候会导致数据冲突 。 使用thread静态和非静态下结果是不同的,非静态下会卖双份。
1、Synchronized关键字
1、1、同步代码块
package com.xingyun.synchronizeds;
/**
* 使用同步代码块
* @author langlang
*
* 2021年1月8日上午11:48:08
*/
public class Ticket1 implements Runnable{
int num = 10;
//创建一个对象
Object lock = new Object();
@Override
public void run(){
//使用同步代码块,参数是一个对象,必须是同一个对象
synchronized(lock){
boolean flag = true;
while(flag) {
if(this.num>0) {
System.out.println(Thread.currentThread().getName()+"计算后的值是"+num);
this.num--;
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}else {
flag = false;
}
}
}
}
}
package com.xingyun.synchronizeds;
/**
*
* @author langlang
*
* 2021年1月8日上午9:51:30
*/
public class Demo1 {
public static void main(String [] args) {
//实例化线程
Ticket1 mt = new Ticket1();
Thread t01 = new Thread(mt,"t01");
Thread t02 = new Thread(mt,"t02");
//启动线程
t01.start();
t02.start();
}
}
1、2同步方法
- 使用synchronized关键字来修饰方法
package com.xingyun.synchronizeds;
/**
* 使用同步代码块
* @author langlang
*
* 2021年1月8日上午11:48:08
*/
public class Ticket2 implements Runnable{
int num = 10;
//创建一个对象
Object lock = new Object();
@Override
public void run(){
ticket();
}
//同步方法
public synchronized void ticket() {
boolean flag = true;
while(flag) {
if(this.num>0){
System.out.println(Thread.currentThread().getName()+"计算后的值是"+num);
this.num--;
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}else {
flag = false;
}
}
}
}
1、3静态同步方法
- 使用synchronized关键字来修改静态方法,使用反射的思想来获取同一个对象
package com.xingyun.synchronizeds;
/**
* 使用同步代码块
* @author langlang
*
* 2021年1月8日上午11:48:08
*/
public class Ticket3 implements Runnable{
static int num = 10;
//创建一个对象
Object lock = new Object();
@Override
public void run(){
ticket();
}
//同步方法
public static synchronized void ticket() {
boolean flag = true;
while(flag) {
if(num>0){
System.out.println(Thread.currentThread().getName()+"计算后的值是"+num);
num--;
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}else {
flag = false;
}
}
}
}
1、4避免死锁问题
- 被锁定的两个对象之间进行相互调用就会出现死锁
public class Ticket03 implements Runnable{
static int tnum=10;
// 创建一个对象
final Object locka=new Object();
final Object lockb=new Object();
Random rd=new Random();
// 执行状态
@Override
public void run() {
// 获取随机数
int num = rd.nextInt(2);//0 1
if(num%2==0) {
synchronized (locka) {
System.out.println("A对象锁定");
synchronized (lockb) {
System.out.println("我要使用lockb");
}
}
}else {
synchronized (lockb) {
System.out.println("B对象锁定");
synchronized (locka) {
System.out.println("我要使用locka");
}
}
}
}
}
2、Lock锁的方式(互斥锁、可重复锁)
- 在需要加锁的地方加锁,加完锁,再将锁释放掉
- 步骤
创建锁:Lock ReetrantLock
使用完记得释放锁
在线程的run方法中,加了多少回锁,就需要释放多少次锁
package com.xingyun.lock;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
*
* @author langlang
*
* 2021年1月10日下午3:47:29
*/
public class TicketLock implements Runnable{
//共享的资源
static int tnum =5;
//创建锁对象
ReentrantLock lock01 = new ReentrantLock(true);
Lock lock02 = new ReentrantLock(true);
@Override
public void run(){
boolean flag = true;
while(flag) {
//加锁
lock01.lock();
lock01.lock();
lock01.lock();
lock01.lock();
lock01.lock();
if(tnum>0){
System.out.println(Thread.currentThread().getName()+"窗口卖出去的是第"+tnum+"张票");
tnum--;//先读后写
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}else {
flag = false;
}
System.out.println("锁的次数"+lock01.getHoldCount());
//返回正等待获取此锁定的线程估计数。
System.out.println("估计数"+lock01.getQueueLength());
System.out.println("是不是公平锁"+lock01.isFair());
System.out.println("是不是由任意线程所有"+lock01.isLocked());
}
}
}
3、Volatile关键字
1、代码演示及结果分析
package com.nebula.volat;
public class VolatileDemo extends Thread{
//声明变量
// volatile boolean flag = false;//标志 程序可以正常停止
boolean flag = false;//标志 程序是无法停止的
int i = 0; //数字
// 线程的run方法
@Override
public void run() {
// 循环进行i++
while (!flag) {
i++;
}
}
public static void main(String[] args) throws Exception {
//创建线程对象
VolatileDemo vt = new VolatileDemo();
//启动线程
vt.start();
//主程序 休息 2秒钟
Thread.sleep(2000);
// 修改标志位
vt.flag = true;
// 输出 i的值
System.out.println("stope" + vt.i);
}
}
- 当标记flag不使用volatile关键字描述的时候,线程无法停止,使用volatile关键字描述之后,可以正常停止。
- Java语言规范中提到过,JVM中存在一个主存区,Java中所有变量都是存在主存中的。每个线程又存在自己的工作内存,工作内存中保存的是主存中某些变量的拷贝,线程对所有变量的操作并非发生在主存区,而是发生在工作内存中,而线程之间是不能直接相互访问,变量在程序中的传递,是依赖主存来完成的。
flag不使用volatile 我们所有的操作是对线程内存中的flag进行读取, vt.flag = true;是对主内存区的变量的值进行的修改,对线程对象内存是不可见 - 使用volatile 修饰的属性,是主内存中的变量,当进行直接的赋值,修改的是主内存中的变量值,
2、java内存模型的特点
[1]原子性:
这一点说明了该模型定义的规则针对原子级别的内容存在独立的影响,对于模型设计最初,这些规则需要说明的仅仅是最简单的读取和存储单元写入的的一些操作,这种原子级别的包括——实例、静态变量、数组元素,只是在该规则中不包括方法中的局部变量。
[2]可见性:
在该规则的约束下,定义了一个线程在哪种情况下可以访问另外一个线程或者影响另外一个线程,从JVM的操作上讲包括了从另外一个线程的可见区域读取相关数据以及将数据写入到另外一个线程内。
[3]可排序性:
该规则将会约束任何一个违背了规则调用的线程在操作过程中的一些顺序,排序问题主要围绕了读取、写入和赋值语句有关的序列。
3、总结 - Volatile可以实现变量的资源共享(考虑到主内存中的数据),Volatile修饰的变量需要考虑该变量的原子性、可见性 (只有单一的读取或者修改才能保证)。
- Volatile不能替代 synchronized或者 lock锁的操作。 在必要的时候需要将Volatile和synchronized结合使用 。
二、生产者和消费者问题
要想买一辆汽车,先生产一台汽车。做一台卖一台 ,没有的话就生产 有的话不生产光卖。
- 使用object的等待唤醒机制:先生产后消费,程序启动之后要先进行生产操作,后面再进行消费操作,重复此种循环操作
- wait:将当前操作进入阻塞状态
- notify:唤醒正在阻塞中的线程
- Wait将当前线程进入阻塞状态 当遇到notify的时候 会再次唤醒wait线程。程序继续从我们的wait位置运行
package com.xingyun.car2;
import java.util.Random;
/**
*
* @author langlang
*
* 2021年1月8日下午6:53:13
*/
public class Car{
private String name;
private String color;
public String getName(){
return name;
}
public void setName(String name) {
this.name = name;
}
public String getColor() {
return color;
}
public void setColor(String color) {
this.color = color;
}
public Car(String name, String color) {
super();
this.name = name;
this.color = color;
}
public Car() {
super();
}
//定义一个标记位
boolean flag = true;
//生产汽车的方法
@SuppressWarnings("unused")
public synchronized void prodCar(String name,String color) {
if(!true) {//不要生产
try {
wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
//给对象赋值
this.name=name;
this.color=color;
System.out.println("生产一台汽车");
System.out.println("------");
flag = false;//已经生产了汽车,需要进入等待销售操作
notify();//唤醒之前的销售被阻塞的状态
}
//销售汽车的方法
public synchronized void sellCar(){
if(flag){//需要等待生产一台汽车
try {
wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
System.out.println("消费了一台汽车");
System.out.println("汽车的名字是"+name+"汽车的颜色"+color);
System.out.println("******");
flag = true;//消费完了需要再次等待了
notify();//唤醒之前等待的线程,就是生产汽车的线程
}
}
package com.xingyun.car2;
/**
* 消费者
* @author langlang
*
* 2021年1月8日下午6:53:20
*/
public class Customer implements Runnable {
// 引用car
Car car;
public Customer(Car car) {
super();
this.car = car;
}
public Customer() {
super();
}
// 消费汽车
int count = 0;
@Override
public void run() {
while (count <= 20) {
synchronized (this) {
count++;
car.sellCar();
}
// 生产的时间是1秒
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
package com.xingyun.car2;
import java.util.Random;
/**
*
* @author langlang
*
* 2021年1月8日下午6:53:27
*/
public class Producer implements Runnable{
// 引用Car类
Car car;
public Producer() {
super();
// TODO Auto-generated constructor stub
}
public Producer(Car car) {
super();
this.car = car;
}
int count=0;
// 实际生产汽车的方法
@Override
public void run() {
while(count<=20) {
count++;
synchronized (this) {
int num= new Random().nextInt(2);
if(num%2==0) {
car.prodCar("奥迪"+count, "黑色");
}else {
car.prodCar("宝马"+count, "红色");
}
}
//睡眠
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
package com.xingyun.car2;
/**
*
* @author langlang
*
* 2021年1月8日下午6:53:33
*/
public class Test {
public static void main(String[] args) {
//实例化Car
Car car = new Car();
//实例化runnable 接口
Producer pd=new Producer(car);
Customer ct=new Customer(car);
// 创建thread线程
Thread pt=new Thread(pd);
Thread tc=new Thread(ct);
// 启动线程
pt.start();
tc.start();
}
}