同步:就是开启多线程的时候,如果需要对同一个对象进行操作,这个时候可能会同时对其进行修改,那么需要先把这个对象进行锁定,然后进行操作,这个过程就是同步。
首先,我们想给一个int对象+1,总共加5次,初始化为0,每次输出加 1 后的值。那么我们如果在单线程的方式中我们直接一个for循环,从1加到5,即可实现。如果使用多线程不加锁的情况下,可能产生问题
public class MainThread {
static int index=0;
public static void main(String[] args) {
for(int i=0;i<5;i++){
new Thread(new Runnable() {
public void run() {
index+=1;
System.out.println(index);
}
}).start();
}
}
}
结果可能出现错误:
1.Synchronized
仅仅使用synchronized进行锁定。
A: 锁定对象
public class MainThread {
static int index = 0;
public static void main(String[] args) {
for (int i = 0; i < 5; i++) {
new Thread(new Runnable() {
public void run() {
synchronized (MyConstants.lock) { //这里加锁之后只会去先去获得所锁的所有权,不然一直处于等待状态
index += 1;
System.out.println(index);
}
}
}).start();
}
}
}
B.锁定方法
例如:你新建了一个老师对象,有一个方法用于看学生的试卷,假设学生在任意时间都有可能在交试卷,而老师只能一次看一个,所以一个批改试卷的方法只能同步执行,如果改了一个试卷就给老师改试卷的数加1
public class Teacher {
private int checkPaperNumber=0;
public void checkStudentPaper(){ //没有加synchronized关键字
checkPaperNumber+=1;
System.out.println(checkPaperNumber);
}
}
public class MainThread {
static Teacher teacherWang = new Teacher();
public static void main(String[] args) {
for (int i = 0; i < 10; i++) { //开启10个线程
new Thread(new Runnable() {
public void run() {
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
teacherWang.checkStudentPaper(); //执行老师批卷子的方法
}
}).start();
}
}
}
可能出现的结果
添加synchronized后:
public synchronized void checkStudentPaper(){
C:与wait()和nitify()方法使用
假设有一个仓库WAREHOUSE,容积为10,现在有一个Producer,生产过程需要200ms,每次生产一个,放到仓库,有一个Consumer赶到仓库需要200ms,每次消费一个。现在假设有
生产线程和10个消费线程。只有一个仓库,约定:只要阻塞线程大于5,唤醒所有阻塞线程,小于5唤醒一个阻塞线程
public class MyConstants {
public static WareHouse wareHouse = new WareHouse(10); //定义的共有仓库
public static int thread_wait_number = 0; //现在阻塞线程数
Producer.java
public class Producer extends Thread{
private String name;
public Producer(String name){
this.name = name;
}
@Override
public void run() {
super.run();
while(true){
try {
Thread.sleep(200); //生产过程与仓库无关
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (wareHouse){ //需要向仓库记账就锁定仓库,拿出账本完成记账过程
if(wareHouse.getCubage() <10){ //如果仓库有空余的地方那么就直接放入仓库并查看阻塞线线程数
wareHouse.produceGoods();
System.out.println("Producer Name"+name+":"+wareHouse.getCubage());
if(wareHouse.getCubage()<10 && wareHouse.getCubage()>0 ){
if( MyConstants.thread_wait_number >5) { //阻塞线程大于5时,就可以唤醒所有线程
System.out.println("消费者"+name+"唤醒所有阻塞线程");
wareHouse.notifyAll();
thread_wait_number=0;
}else if(MyConstants.thread_wait_number>0){ //小于5时就唤醒一个线程
System.out.println("消费者"+name+"唤醒单个阻塞线程");
wareHouse.notify();
thread_wait_number-=1;
}
}
}else{
try {
MyConstants.thread_wait_number +=1;
System.out.println("Producer Name"+name+":"+"阻塞中...");
wareHouse.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
}
Consumer.java
public class Consumer extends Thread{
private String name;
public Consumer(String name) {
this.name = name;
}
@Override
public void run() {
super.run();
while(true){
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (wareHouse){
if(wareHouse.getCubage() >0){
wareHouse.consumerGoods();
System.out.println("Consumer Name"+name+":"+wareHouse.getCubage());
if(wareHouse.getCubage()<10 && wareHouse.getCubage()>0 ){
if( MyConstants.thread_wait_number >5) {
System.out.println("消费者"+name+"唤醒所有阻塞线程");
wareHouse.notifyAll();
}else if(MyConstants.thread_wait_number>0){
System.out.println("消费者"+name+"唤醒单个阻塞线程。");
wareHouse.notify();
}
}
}else{
try {
MyConstants.thread_wait_number +=1;
System.out.println("Consumer Name"+name+":"+"阻塞中...");
wareHouse.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
}
Main函数
public class MyThread {
public static void main(String[] args) {
for(int i=0;i<10;i++){
Producer producer = new Producer(String.valueOf(i));
producer.start();
}
for(int i=0;i<10;i++){
Consumer consumer = new Consumer(String.valueOf(i));
consumer.start();
}
}
}
运行结果:
总结:synchronized关键字是获取对象的锁,当自己代码所包裹的区域执行完毕可以释放对对象的控制权,而wait()方法是将对象的持有权放弃,并将本线程放入阻塞线程中。notify()方法是将想持有本对象的所有阻塞线程中的一个唤醒,notifyAll()是唤醒所有阻塞线程。光使用synchronized关键字是可以达到目的的,但是可能造成资源的浪费,长期霸占CPU运行时间,也可能因为持有其他对象导致其他线程无法正常运行。
2.Volatile关键字
一个变量int count=0,开启5个线程,每个线程都等待count的值不等于1的时候,另开启一个线程用于改变count的值
public class VolatileThread {
private static int count = 0;
public static void main(String[] args) throws InterruptedException {
for(int i=0;i<5;i++){
new Thread(new Runnable() {
public void run() {
while(count == 0){ //监听count的值是否为
}
System.out.println(Thread.currentThread().getName()+"执行完毕");
}
}).start();
}
Thread.sleep(200);
new Thread(new Runnable() { //用于改变count的值
public void run() {
count +=1;
}
}).start();
}
}
private volatile static int count = 0; //添加volatile关键字后
可使用的场景多个线程读,一个线程写。
3.ReetrantLock解析
1.简述
1.方法lock()
直接锁定声明的lock对象,如果锁对象可用就直接锁定,不可用进入等待,线程阻塞
2.方法unlock()
接触锁定的锁,没有锁定锁而进行的unlock会报错
[图片上传失败...(image-6da169-1552810272527)]
3.方法tryLock()
获取锁的状态,如果锁可用直接锁定,不可用直接返回不等待,不用再进行锁定,tryLcok返回true后直接就已经锁定了锁对象
4.方法tryLock(时间,时间单位)
在给定的时间内去等待锁如果锁获取到就执行下面的东西,没有在给定时间内尝试获得锁,过了时间就直接返货false不在继续等待
同样的同步的前提是锁定同样一个锁
5.Condition
1.Lock.newCondition
创建一个condition
2.Condition的阻塞和释放
MyReentrantLock程序有两个方法,1.对count+1 2.对count-1
约定如果加方法可以获取到锁,那么就判断是否现在的总数大于10, 1.大于10对addcondition进行阻塞。2.小于10,对总数加一并且释放一个减的程序,同样的减程序如果发现总数小于0,那么阻塞,否则减一并释放一个加程序
public class MyReentrantLock {
public void addMethod(){
try{
while(true) {
if (LockTest.lock.tryLock(100,TimeUnit.SECONDS)) {
TimeUnit.SECONDS.sleep(1);
if (LockTest.count >= 10) {
System.out.println(Thread.currentThread().getName()+":"+"加----功能阻塞");
LockTest.addcondition.await();
}else {
LockTest.count++;
if(LockTest.count>0){
System.out.println(Thread.currentThread().getName()+":"+"释放减功能");
LockTest.mincondition.signal();
}
System.out.println(Thread.currentThread().getName()+":"+LockTest.count);
LockTest.lock.unlock();
}
} else {
System.out.println("没获取到锁");
}
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public void minMethod(){
try{
while(true) {
if (LockTest.lock.tryLock(100,TimeUnit.SECONDS)) {
TimeUnit.SECONDS.sleep(1);
if (LockTest.count < 0) {
System.out.println(Thread.currentThread().getName()+":"+"减---开始阻塞!");
LockTest.mincondition.await();
}
else {
LockTest.count--;
if(LockTest.count<10) {
System.out.println(Thread.currentThread().getName()+":"+"释放加功能");
LockTest.addcondition.signal();
}
System.out.println(Thread.currentThread().getName()+":"+LockTest.count);
LockTest.lock.unlock();
}
} else {
System.out.println("没获取到锁");
}
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
我们开启5个线程用于加开启5个线程用于减
public class LockTest {
public static ReentrantLock lock = new ReentrantLock();
public static Condition addcondition = lock.newCondition();
public static Condition mincondition = lock.newCondition();
public static int count=0;
public static void main(String[] args) {
for(int i=0;i<5;i++){
new Thread(new Runnable() {
@Override
public void run() {
MyReentrantLock myReentrantLock = new MyReentrantLock();
myReentrantLock.addMethod(); //add
}
}).start();
}
for(int i=0;i<5;i++){
new Thread(new Runnable() {
@Override
public void run() {
MyReentrantLock myReentrantLock = new MyReentrantLock();
myReentrantLock.minMethod(); //min
}
}).start();
}
}
}
2.ReentrantLock与synchronized关键字的区别
1、增加了tryLock方法不至于一直使程序进入等待状态
2、可实现公平锁:多个线程在等待同一个锁时,必须按照申请锁的时间顺序排队等待,而非公平锁则不保证这点,在锁释放时,任何一个等待锁的线程都有机会获得锁。synchronized中的锁时非公平锁,ReentrantLock默认情况下也是非公平锁,但可以通过构造方法ReentrantLock(ture)来要求使用公平锁。
3、可以声明多个condition每个condition直接来源于lock的newCondition(),不用像synchronized去先使用synchronized锁定这个对象然后再执行wait方法
synchronized (obj1){
synchronized (obj2){
if(condition1) {
try {
obj1.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}else{
if(condition2){
try {
obj2.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}