序言:有了并发程序,可以同时做多件事情,但是两个或多个线程同时访问一个资源那么冲突就出现了。
示列一:资源冲突
当一个对象中的实例变量、静态字段、构成数组对象的元素被多个线程访问的时候,会出资源冲突的现象
public class ExampleDemo {
String str="sads";
public void info(String s){
str+=s;
System.out.println(str);
}
public static void main(String[] args) {
ExampleDemo demo = new ExampleDemo();
new Thread(()->{
demo.info("11");
}).start();
new Thread(()->{
demo.info("22");
}).start();
}
}
示列二:资源不冲突
但是一些方法参数、局部变量去被多个线程访问的时候却不会出现冲突的情况。
public class ExampleDemo {
String str="sads";
public void info(String s){
String str1="sas"+s;
System.out.println(str1);
}
public static void main(String[] args) {
ExampleDemo demo = new ExampleDemo();
new Thread(()->{
demo.info("11");
}).start();
new Thread(()->{
demo.info("22");
}).start();
}
}
不正确的访问资源:
abstract public class IntGenerator {
private volatile boolean canceled=false;
public abstract int next();
public void canceled(){
canceled=true;
}
public boolean isCanceled(){
return canceled;
}
}
该抽象类用来产生偶数。
public class EvenChecker implements Runnable {
private IntGenerator intGenerator;
private final int id;
EvenChecker(IntGenerator g,int i){
intGenerator=g;
id=i;
}
@Override
public void run() {
while (!intGenerator.isCanceled()){
int val =intGenerator.next();
if (val%2!=0){
System.out.println(val+"不是偶数");
intGenerator.canceled();
}
}
}
public static void test(IntGenerator gp,int count){
System.out.println("Press Control-C to exit");
ExecutorService service = Executors.newCachedThreadPool();
for (int i = 0; i <count ; i++) {
service.execute(new EvenChecker(gp,i));
}
service.shutdown();
}
public static void test(IntGenerator gp){
test(gp,10);
}
}
该任务用来检查偶数的正确性,如果不是偶数,则改循环的条件来结束任务。
public class EvenGenerator extends IntGenerator {
private int currentEventValue=0;
@Override
public int next() {
++currentEventValue;
++currentEventValue;
return currentEventValue;
}
public static void main(String[] args) {
EvenChecker.test(new EvenGenerator());
}
}
3467不是偶数
3473不是偶数
3469不是偶数
3471不是偶数
两次的++操作,一定是偶数,但是结果却出乎意料。我们在使用线程的时候,不知道一个线程会在什么时间运行,又因为在java中++的操作不是原子性的,很容易导致线程被挂起。所以当一个任务执行完第一个++之后,可能另一个任务已经带着这个数组返回了。所以才会产生非偶数。
一:解决共享资源冲突(synchronized):
上面列子中,几个线程访问同一个资源,可能一个线程刚把数字变为奇数,另一个线程已经带着这个数字返回了。这正是并发程序中的共享资源问题。所以需要一种方式,来防止两个任务同时访问一个资源。防止这个问题就是在一个任务执行的时候为其加上锁。
1.synchronized:
java中提供了关键字synchronized,为防止资源冲突提供了支持。 当任务要执行被synchronized关键字保护的代码片段时候,它将检查锁是否可用,然后获取锁,执行代码,释放锁。
注意:如果一个对象同一个实例中的synchronized 方法被一个线程抢占,则该线程释放锁之前,其他线程无法调用synchronized方法
public class SynchronizedDemo {
public synchronized void tese() {
System.out.println("进入方法test()");
try {
TimeUnit.SECONDS.sleep(20);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("退出该方法");
}
public synchronized void info() {
System.out.println("进入方法info");
System.out.println("退出方法info");
}
public static void main(String[] args) {
final SynchronizedDemo demo = new SynchronizedDemo();
final SynchronizedDemo demo2 = new SynchronizedDemo();
final Thread thread = new Thread(() -> {
demo.tese();
});
final Thread thread2 = new Thread(() -> {
demo2.info();
});
thread.start();
thread2.start();
}
}
示列:同步控制EvenGenertor:
同步:一个方法访问,另一个方法不能访问
public class EvenGenerator extends IntGenerator {
private int currentEventValue=0;
@Override
public synchronized int next() {
++currentEventValue;
++currentEventValue;
return currentEventValue;
}
public static void main(String[] args) {
EvenChecker.test(new EvenGenerator());
}
}
这样任何时刻就只有一个任务执行next方法(),数字也一直时偶数了<br />**①:对象锁:**形如方法锁和同步代码块 (此时的锁针对同一个实例有用)
public class MultiLock {
static volatile int count=30;
// private static AtomicInteger count = new AtomicInteger(20);
public synchronized void f1() {//买票一
if ( count--> 0) {
System.out.println(Thread.currentThread().getName() + " f1() " + count);
}
try {
TimeUnit.MILLISECONDS.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public synchronized void f2() {//买票2.
if (count--> 0) {
System.out.println(Thread.currentThread().getName() + " f2() " + count);
}
try {
TimeUnit.MILLISECONDS.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public static void main(String[] args) throws Exception {
MultiLock multiLock = new MultiLock();
new Thread("Thread1-1->") {
public void run() {
while (MultiLock.count>0)
multiLock.f1();
}
}.start();
new Thread("Thread 1-2->") {
public void run() {
while (MultiLock.count>0)
multiLock.f2();
}
}.start();
MultiLock multiLock1 = new MultiLock();
new Thread("Thread 2-1->") {
@Override
public void run() {
while (MultiLock.count>0)
multiLock1.f1();
}
}.start();
new Thread("Thread 2-2->") {
@Override
public void run() {
while (MultiLock.count>0)
multiLock1.f2();
}
}.start();
}
}
四个线程,有两个对象,所以当其他对象访问方法的时候还是会出现资源冲突的问题。
②:类锁:针对每一个类,也有一个锁,synchroized static 方法可以在类的范围内防止对static 数据的并发访问。
public class MultiLock {
static volatile int count=30;
// private static AtomicInteger count = new AtomicInteger(20);
public synchronized static void f1() {//买票一
if ( count--> 0) {
System.out.println(Thread.currentThread().getName() + " f1() " + count);
}
try {
TimeUnit.MILLISECONDS.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public synchronized static void f2() {//买票2.
if (count--> 0) {
System.out.println(Thread.currentThread().getName() + " f2() " + count);
}
try {
TimeUnit.MILLISECONDS.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
这样既使是多个实例也不会出现冲突的现象。<br />**③:临界区:**<br /> 有时候你只希望防止多个线程同时访问方法内部的部分代码而不是整个方法。通过这种方式分离出来的代码段被称为临界区,他也适用**synchronized**关键字建立
synchronized(synObject){
// java block
}
这里,synchronized被用来指定某个对象,此对象的锁被用来对花括号内的代码进行同步控制。这也被称为同步代码块:在进入此段代码前,必须得到synObject对象的锁。如果其他线程已经得到这个锁,那么就得到锁被释放以后,才能进入临界区。
④:在其他对象上同步:
synchronized快必须给定一个在其上进行同步的对象,并且最合理的方式是,使用其方法正在被调用的当前对象:synchronized(this)。有时必须在另一个对象上同步,但这样就必须要保证所有相关的任务都是在同一个对象上进行的。
class DualSynch {
private Object syncobject = new Object();
public synchronized void f() {
for (int i = 0; i < 5; i++) {
System.out.println("f()");
Thread.yield();
}
}
public void g() {
synchronized (syncobject) {
for (int i = 0; i < 5; i++) {
System.out.println("g()");
Thread.yield();
}
}
}
}
public class SyncObject {
public static void main(String[] args) {
DualSynch dualSynch = new DualSynch();
new Thread() {
@Override
public void run() {
dualSynch.f();
}
}.start();
dualSynch.g();
}
}
两个任务在同时进入一个对象。因为这两个同步是相互独立的是不针对不同的对象同步的。因此任何一个方法都没有因为对另一个方法的同步所阻塞。
2.线程本地存储
解决线程冲突的第二种方式是根除对变量的共享(一个对象中的实例变量、静态字段、构成数组对象的元素),通过使用ThreadLocal可以来创建和管理线程的本地存储。<br /> ** ThreadLocal实现机制**:运用了map结合的特性实现来为每一个线程开辟一个自己储存空间<br /> ThreadLocal部分源码
public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
return setInitialValue();
}
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
ThreadLocal正是将每个线程当做mao集合中的key,将每个线程的对应的值作为mao中的value,所以才做到为每一个线程开辟一个存储空间。<br /> **示列**:通过threaLocal来管理线程本地资源
package com.package21.utils;
public class ThreadLoclDemo {
static ThreadLocal<String> threadLocal = new ThreadLocal() {
@Override
protected String initialValue() {//设置初始值
return "adc";
}
};
public static void main(String[] args) {
new Thread(() -> {
String s = threadLocal.get();
threadLocal.set(s + " aaa");
System.out.println(threadLocal.get());
}).start();
new Thread(() -> {
String s = threadLocal.get();
threadLocal.set(s + " bbb");
System.out.println(threadLocal.get());
}).start();
}
}
ThreadLocal会为每个线程创建一个独属的变量存储空间,通过重写initialValue()来随机为每个线程返回一个变量的初始值。通过调用get方法可以获取到这个值,而set方法会将参数插入到线程的存储空间中。即便值有一个对象,但是程序运行的时候,每个线程都被分配了自己的存储,都去跟踪了自己的数值。
3.使用显示的Lock对象:
javase5中的concurrent类库中还包括有定义在Locks中显示的互斥机制(加锁)。Lock对象必须被显示的创建、锁定和释放,与synchronized相比,更加灵活。<br />** 示列:使用显示的lock重写偶数列子**
public class MutexEvenGenerator extends IntGenerator{
private int i=0;
private Lock lock=new ReentrantLock();//创建锁
@Override
public int next() {
lock.lock();//加锁
try{
++i;
++i;
return i;
}finally {
lock.unlock();//释放锁
}
}
public static void main(String[] args) {
EvenChecker.test(new MutexEvenGenerator());
}
}
注意:在使用显示的lock锁时,对lock的调用,必须放置在finally子句中带有unlock的try-fianlly语句中。同时
return子句必须在try中,以防止unlock不会过早发生,从而将数据暴露给第二个任务。
①:显示的lock优点:
1. 在使用synchronized关键字时,如果某些事物失败了,那么就会抛出一个异常,但是却没有机会去做任何的清理工作,使用显示的lock时就可以使用fianlly子句将系统维护在正确的状态了。
2.ReentrantLock允许但一个线程获取锁的时候,另一个线程尝试去获取但未获取,可以去离开做其他的事情。
package com.package21.lock;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;
public class AttemotLocking {
private ReentrantLock lock=new ReentrantLock();
public void untimed(){
boolean captured=lock.tryLock();
try {
System.out.println("trylock "+captured);
}finally {
if (captured){
lock.unlock();
}
}
}
public void timed(){
boolean captured=false;
try{
captured=lock.tryLock(200, TimeUnit.MILLISECONDS);//尝试在2号码内获取锁,与untimes是同一把锁
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
try{
System.out.println("lock.tryLock(2, TimeUnit.SECONDS); "+captured);
}
finally {
if (captured)
lock.unlock();
}
}
public static void main(String[] args) throws InterruptedException {
AttemotLocking locking = new AttemotLocking();
locking.untimed();
locking.timed();
new Thread(){
@Override
public void run() {
boolean b = false;
try {
b = locking.lock.tryLock(2, TimeUnit.MILLISECONDS);
} catch (InterruptedException e) {
System.out.println(e);
}finally {
if (b)
System.out.println("成功获得锁");
else
System.out.println("失败 执行其他事情");
}
}
}.start();
}
}
3.ReentrantLock中的lockInterruptibly()方法去获取锁,如果锁被占用,一样会阻塞,但可以调用interrupt()当断线程,同时会抛出InterruptedException。
示列:lockInterruptibly()的运用
public class IockInterruptiblyDemo {
public static void main(String[] args) throws InterruptedException {
Lock lock = new ReentrantLock();
Thread thread1= new Thread(()->{
System.out.println(Thread.currentThread().getName() + "获得了锁");
lock.lock();
try {
TimeUnit.SECONDS.sleep(10);
lock.unlock();
} catch (InterruptedException e) {
System.out.println("获取锁失败。。。。。。。。。。。。");
}
});
thread1.start();
TimeUnit.SECONDS.sleep(1);
Thread thread=new Thread(()->{
System.out.println("lockInterruptibly");
try {
lock.lockInterruptibly();
System.out.println("===========");
} catch (InterruptedException e) {
System.out.println(e);
}
});
thread.start();
System.out.println("开始打断线程");
TimeUnit.SECONDS.sleep(2);
thread.interrupt();
}
}
但synchronized则会一直处于阻塞状态,不能去打断。
class Block {
public synchronized void f() {
try {
TimeUnit.SECONDS.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("========f()============");
}
public synchronized void info() {
System.out.println("=========info()==========");
}
}
public class IockInterruptiblyDemo2 {
static Block soo = new Block();
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(() -> {
System.out.println(Thread.currentThread().getName()+"获得了锁");
soo.f();
});
thread.start();
TimeUnit.SECONDS.sleep(1);
Thread thread2 = new Thread(() -> {
try {
System.out.println(Thread.currentThread().getName()+"thread2准备获取锁");
soo.info();
} catch (Exception e) {
System.out.println(e);
}
});
TimeUnit.SECONDS.sleep(2);
System.out.println("开始打断");
thread2.interrupt();
System.out.println("打断已经完成");
}
}
虽然执行打断语句,但是线程二还会一直阻塞,知道线程一释放锁再去执行。
4.原子类
javase5引入了AtomicInteger、AtomicLong、AtomicRefernce等特殊的原子性变量类,在涉及性能优化的时候,用处就很大了。
public class AtomicIntegeTest implements Runnable {
private AtomicInteger integer = new AtomicInteger();
public int getInteger() {
return integer.get();
}
public void evenIncrement() {
integer.addAndGet(2);
}
@Override
public void run() {
while (true) {
evenIncrement();//一直去加2
}
}
public static void main(String[] args) {
ExecutorService service = Executors.newCachedThreadPool();
AtomicIntegeTest test = new AtomicIntegeTest();
new Timer().schedule(new TimerTask() {//定时任务,五秒后执行任务
@Override
public void run() {
System.out.println("Aborting");
System.exit(0);
}
},5000);
service.execute(test);
while (true) {
int integer = test.getInteger();
if (integer % 2 != 0) {
System.out.println(integer);
System.exit(0);
}
}
}
}
由于AtomicInteger是原子类,所以在 evenIncrement()的时候是原子操作。尽管没有使用synchronized,该程序也不会产生非偶数。同时,原子类的操作并不是加锁的操作,所以说当加锁的方法被阻塞时,对原子类操作并无影响。
二、可视性和原子性
1.可视性:
volatile关键字确保了应用中的可视性,如果将一个域声明为valatile的那么只要对这个域产生了写操作,那么所以的读取操作都可以看到这个改变。
原理:因为valatile域会被立即写入到主存中,而读取操作就发生在主存中。
注意:volatile通常会配合Boolean一起使用。
2.原子性:
**①:原子性的定义:**<br /> 只有一步操作的被称为原子性:如赋值,和返回等和Boolean类型。注意:java中的i++不是原子性的, <br /> **②:如果一个值的变化不是原子性的,那么对于这个值的读取和赋值操作都要加锁**<br />** 示列一:值的变化不是原子性的**
public class AtomcityTest implements Runnable{
private int i = 0;
//返回是原子操作
public int getValue(){
return i;
}
//方法是同步的,但是对变量的修改 其他线程可以看到
//错误的理解:synchronized同步的方法,内部对成员变量的处理,
//在通过其他非同步方法访问的时候也是同步的
public synchronized void evenIncrement(){
i++;//非原子操作
i++;
}
@Override
public void run() {
while (true) {
//System.out.println("---"+i+"---");
evenIncrement();
}
}
public static void main(String[] args) {
AtomcityTest atomcityTest = new AtomcityTest();
ExecutorService service = Executors.newCachedThreadPool();
service.execute(atomcityTest);
while (true){
int value = atomcityTest.getValue();
System.out.println("==="+value+"===");
if(value % 2 !=0){
System.out.println(value);
System.exit(0);
}
}
}
}
虽然i++采用了加锁机制,而且return也是原子性的操作,但是对变量的修改 其他线程在使用getValue时也是可以看到的。所以说这个时候原子性的操作也无用武之地。必须在读取值的方法上也加锁才能保证程序的同步性。<br />** 示列二:值的变化是原子性的**
public class AtomcityTest2 implements Runnable{
private boolean flag = false;
public boolean getValue(){
return flag;
}
//此处不加synchronized也是可以的
public synchronized boolean evenIncrement(){
return flag = !flag;
}
@Override
public void run() {
while (true) {
System.out.println("---"+flag+"---");
evenIncrement();
}
}
public static void main(String[] args) {
AtomcityTest2 test2 = new AtomcityTest2();
ExecutorService service = Executors.newCachedThreadPool();
service.execute(test2);
while (true){
boolean flag = test2.getValue();
System.out.println("==="+flag+"===");
if(flag){
System.out.println(flag);
System.exit(0);
}
}
}
}
Boolean只有true和false两种状态 ,所以说既是在读取的时候不加锁也是没问题的。同样在赋值的时候不加入锁也是没问题的。