目录
一、引言
先介绍两个概念
线程安全:经常用来描绘一段代码。指在并发的情况之下,该代码经过多线程使用,线程的调度顺序不影响任何结果。
同步:Java中的同步指的是通过人为的控制和调度,保证共享资源的多线程访问成为线程安全,来保证结果的准确。
为什么使用同步?
多线程给我们带来了很大的方便,但是同时也给我们带来了一个致命的问题,当我们对线程共享数据进行非原子操作时,会带来知名的错误。当多个线程同时操作一个可共享的资源变量时(如数据的增删改查),将会导致数据不准确,相互之间产生冲突,因此加入同步锁以避免在该线程没有完成操作之前,被其他线程的调用,从而保证了该变量的唯一性和准确性。这就是多线程同步提上议程的原因,解决多线程安全问题。
举个例子:假设银行里某一用户账户有1000元,线程A读取到1000,并想取出这1000元,并且在栈中修改成了0但还没有刷新到堆中,线程B也读取到1000,此时账户刷新到银行系统中,则账户的钱变成了0,这个时候也想去除1000,再次刷新到行系统中,账号的钱变成0,这个时候A,B都取出1000元,但是账户只有1000,显然出现了问题。针对上述问题,假设我们添加了同步机制,那么就可以很容易的解决。
tips:java1.5新引入了concurrent包,里面都是关于并发的,里面还有locks和atomic两个包,很值得学习,使用方便。
二、synchronize同步
1、synchronize代码块
这里举了个复杂点的例子,模仿的生产者消费者的模式,每次添加一个数,然后读出一个数,添加后不能再次添加,读出后不能再次读出。synchronize(this){}中this为同步锁,可以是任意对象,这里是用的该类本身,只要每次锁一样就能保证两个线程再synchronize代码块中的执行是同步的,所以每次只能读取或者添加。
wait和notify是线程等待和唤醒线程的意思,每当线程wait后被notify会从等待位置继续执行,使用这个是为了在线程中进行通信,使多个线程按照自己的想法有序执行(当添加数据线程添加后在添加该线程就会wait,当读取数据线程读取数据后就会notify添加线程继续执行)。
创建多线程类
package com.lock;
public class Testlock {
public static void main(String [] args)
{
data();
}
/***
* synchronize
*/
static void data()
{
final DataSynchronize data=new DataSynchronize();
new Thread(new Runnable() {
public void run() {
for(int i=0;i<1000000000;i++)
{
data.write();
}
}
}).start();
new Thread(new Runnable() {
public void run() {
for(int i=0;i<1000000000;i++)
{
data.get();
}
}
}).start();
}
}
封装的共享数据及方法类
package com.lock;
/**
* synchronize
* 两个线程,一个存数据一个取数据——多个线程全部互斥
*/
class DataSynchronize {
boolean hasdata;
int x;
public void write()
{
synchronized (this)
{
//有数据
if (hasdata==true)
{
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//没数据
System.out.println(Thread.currentThread().getName()+"准备添加数据");
x=(int)(Math.random()*100000);
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"添加数据"+x);
//通过上述步骤有了数据,并唤醒取数据
hasdata=true;
this.notify();
}
}
public synchronized void get()
{
synchronized (this)
{
//没有数据
if (hasdata==false)
{
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName()+"准备读取数据");
//x=(int)Math.random();
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"读取数据为"+x);
//通过上述步骤没有数据,并唤醒添加数据
hasdata=false;
this.notify();
}
}
}
2、synchronize方法
这里第一个类没有变化,主要对第二个类进行了改变,把synchronize放在方法上了,说明此方法是线程同步安全的,如在单例模式中getinstance方法中也要使用synchronize关键字。这个比较简单,需要注意的是synchronize加在非静态方法上锁是对象锁,加在静态对象上市类锁。
创建多线程类
package com.lock;
public class Testlock {
public static void main(String [] args)
{
data();
}
/***
* synchronize
*/
static void data()
{
final DataSynchronize data=new DataSynchronize();
new Thread(new Runnable() {
public void run() {
for(int i=0;i<1000000000;i++)
{
data.write();
}
}
}).start();
new Thread(new Runnable() {
public void run() {
for(int i=0;i<1000000000;i++)
{
data.get();
}
}
}).start();
}
}
封装共享数据及方法类
/**
* synchronize
* 两个线程,一个存数据一个取数据——多个线程全部互斥
*/
class DataSynchronize {
boolean hasdata;
int x;
public synchronized void write()
{
//有数据
if (hasdata==true)
{
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//没数据
System.out.println(Thread.currentThread().getName()+"准备添加数据");
x=(int)(Math.random()*100000);
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"添加数据"+x);
//通过上述步骤有了数据,并唤醒取数据
hasdata=true;
this.notify();
}
public synchronized void get()
{
//没有数据
if (hasdata==false)
{
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName()+"准备读取数据");
//x=(int)Math.random();
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"读取数据为"+x);
//通过上述步骤没有数据,并唤醒添加数据
hasdata=false;
this.notify();
}
}
三、lock同步
1、普通lock+condition
首先最简单的lock就是把synchronize代码块外皮去掉,在代码块最开始添加lock.lock(),在代码块末尾添加lock.unlock(),所以很简单就不写例子了,lock是在jdk1.5出来的,在lock包里面。
这个方法要达到的效果和上述例子中一样,所以使用了condition,condition主要起条件限制的作用,它的await与signal与上面的wait和notify一个意思,为了使多个线程间通信控制线程执行顺序。
创建多线程类
public class Testlock {
public static void main(String [] args)
{
//data();
datacondition();
//dataconditonOrder();
//dataLockReadWrite();
}
/**
* lock+condition
*/
static void datacondition()
{
final DataLockcondition data=new DataLockcondition();
new Thread(new Runnable() {
public void run() {
for(int i=0;i<1000000000;i++)
{
data.write();
}
}
}).start();
new Thread(new Runnable() {
public void run() {
for(int i=0;i<1000000000;i++)
{
data.get();
}
}
}).start();
}
}
封装共享数据和方法类
/**
* lock condition
* 两个线程,一个存数据一个取数据——多个线程全部互斥
*/
public class DataLockcondition {
boolean hasdata;
int x;
Lock lock=new ReentrantLock();
Condition condition=lock.newCondition();
public void write()
{
lock.lock();
try{
//有数据
if (hasdata==true)
{
try {
condition.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//没数据
System.out.println(Thread.currentThread().getName()+"准备添加数据");
x=(int)(Math.random()*100000);
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"添加数据"+x);
//通过上述步骤有了数据,并唤醒取数据
hasdata=true;
condition.signal();
}finally {
lock.unlock();
}
}
public void get()
{
lock.lock();
try{
//没有数据
if (hasdata==false)
{
try {
condition.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName()+"准备读取数据");
//x=(int)Math.random();
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"读取数据为"+x);
//通过上述步骤没有数据,并唤醒添加数据
hasdata=false;
condition.signal();
}finally {
lock.unlock();
}
}
}
2、lock+condition高级应用
condition既然和synchronize中的wait和notify差不多为什么还使用它呢?是因为condition可以创建多个控制节点,然而synchronize中的代码块只有一个锁可以控制,下面展示下强大的功能。
让主线程、sub1线程、sub2线程中的循环逐条按顺序输出,多么牛逼!!
创建多线程类
public class Testlock {
public static void main(String [] args)
{
//data();
//datacondition();
dataconditonOrder();
//dataLockReadWrite();
}
/**
* lock+condition+order
*/
static void dataconditonOrder()
{
final DataLockConditonOrder lockConditonOrder=new DataLockConditonOrder();
new Thread(new Runnable() {
public void run() {
for (int i=0;i<10;i++)
{
lockConditonOrder.main(i);
}
}
}).start();
new Thread(new Runnable() {
public void run() {
for (int i=0;i<10;i++)
{
lockConditonOrder.sub1(i);
}
}
}).start();
for (int i=0;i<10;i++)
{
lockConditonOrder.sub2(i);
}
}
}
封装共享数据和方法类
/**
* 三个线程,按照顺序执行——多个线程互斥,有序
*/
public class DataLockConditonOrder {
int flag=0;
Lock lock=new ReentrantLock();
Condition condition1=lock.newCondition();
Condition condition2=lock.newCondition();
Condition condition3=lock.newCondition();
void main(int order)
{
lock.lock();
try{
//没有轮到1执行
if (flag!=0)
{
try {
condition1.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//轮到1执行
for(int i=0;i<10;i++)
{
System.out.println("主线程:"+Thread.currentThread().getName()+"第"+order+"次循环输出"+i);
}
flag=1;
condition2.signal();
}
finally {
lock.unlock();
}
}
void sub1(int order)
{
lock.lock();
try{
//没有轮到2执行
if(flag!=1)
{
try {
condition2.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//轮到2执行
for(int i=0;i<20;i++)
{
System.out.println("sub1线程:"+Thread.currentThread().getName()+"第"+order+"次循环输出"+i);
}
flag=2;
condition3.signal();
}
finally {
lock.unlock();
}
}
void sub2(int order)
{
lock.lock();
//没有轮到3执行
try{
if (flag!=2)
{
try {
condition3.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//轮到3执行
for(int i=0;i<30;i++)
{
System.out.println("sub2线程:"+Thread.currentThread().getName()+"第"+order+"次循环输出"+i);
}
flag=0;
condition1.signal();
}
finally {
lock.unlock();
}
}
}
输出结果
主线程:Thread-0第0次循环输出0
主线程:Thread-0第0次循环输出1
主线程:Thread-0第0次循环输出2
主线程:Thread-0第0次循环输出3
主线程:Thread-0第0次循环输出4
主线程:Thread-0第0次循环输出5
主线程:Thread-0第0次循环输出6
主线程:Thread-0第0次循环输出7
主线程:Thread-0第0次循环输出8
主线程:Thread-0第0次循环输出9
sub1线程:Thread-1第0次循环输出0
sub1线程:Thread-1第0次循环输出1
sub1线程:Thread-1第0次循环输出2
sub1线程:Thread-1第0次循环输出3
sub1线程:Thread-1第0次循环输出4
sub1线程:Thread-1第0次循环输出5
sub1线程:Thread-1第0次循环输出6
sub1线程:Thread-1第0次循环输出7
sub1线程:Thread-1第0次循环输出8
sub1线程:Thread-1第0次循环输出9
sub1线程:Thread-1第0次循环输出10
sub1线程:Thread-1第0次循环输出11
sub1线程:Thread-1第0次循环输出12
sub1线程:Thread-1第0次循环输出13
sub1线程:Thread-1第0次循环输出14
sub1线程:Thread-1第0次循环输出15
sub1线程:Thread-1第0次循环输出16
sub1线程:Thread-1第0次循环输出17
sub1线程:Thread-1第0次循环输出18
sub1线程:Thread-1第0次循环输出19
sub2线程:main第0次循环输出0
sub2线程:main第0次循环输出1
sub2线程:main第0次循环输出2
sub2线程:main第0次循环输出3
sub2线程:main第0次循环输出4
sub2线程:main第0次循环输出5
sub2线程:main第0次循环输出6
sub2线程:main第0次循环输出7
sub2线程:main第0次循环输出8
sub2线程:main第0次循环输出9
sub2线程:main第0次循环输出10
sub2线程:main第0次循环输出11
……………………………………………………
3、lock+读写锁
读写锁跟数据库读写锁很像,就是读与读异步,读与写同步,写与写同步。从而达到数据不会出错。
程序目的达到上述目的,为读数据上读锁,写数据上写锁。
创建线程类
public class Testlock {
public static void main(String [] args)
{
//data();
//datacondition();
//dataconditonOrder();
dataLockReadWrite();
}
/**
* lockReadwrite
*/
static void dataLockReadWrite()
{
final DataLockReadwrite data=new DataLockReadwrite();
for (int i=0;i<3;i++) {
new Thread(new Runnable() {
public void run() {
data.write();
}
}).start();
}
for (int i=0;i<3;i++){
new Thread(new Runnable() {
public void run() {
data.get();
}
}).start();
}
}
}
封装共享数据和方法类
/**
* 多个线程,读取相同对象——多个线程,分类
*/
public class DataLockReadwrite {
boolean hasdata;
int x;
ReentrantReadWriteLock lock=new ReentrantReadWriteLock();
//Condition condition=lock.newCondition();
public void write()
{
//有数据
/* if (hasdata==true)
{
try {
condition.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}*/
//没数据
lock.writeLock().lock();
System.out.println(Thread.currentThread().getName()+"准备添加++数据");
x=(int)(Math.random()*100000);
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"添加++数据"+x);
//通过上述步骤有了数据,并唤醒取数据
lock.writeLock().unlock();
}
public void get()
{
//没有数据
/* if (hasdata==false)
{
try {
condition.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}*/
lock.readLock().lock();
System.out.println(Thread.currentThread().getName()+"准备读取——数据");
//x=(int)Math.random();
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"读取数据——为"+x);
//通过上述步骤没有数据,并唤醒添加数据
lock.readLock().unlock();
}
}
输出结果:
添加数据内部都为原子操作,而读取数据可以被打断
Thread-0准备添加++数据
Thread-0添加++数据60461
Thread-2准备添加++数据
Thread-2添加++数据98717
Thread-1准备添加++数据
Thread-1添加++数据18315
Thread-5准备读取——数据
Thread-3准备读取——数据
Thread-4准备读取——数据
Thread-5读取数据——为18315
Thread-3读取数据——为18315
Thread-4读取数据——为18315
四、总结
- synchronize代码块
- synchronize方法
- lock基本操作
- lock+condition
- lock+读写锁