强烈推荐一个大神的人工智能的教程:http://www.captainbed.net/zhanghan
当多个线程同时访问某一个共享资源时,可能会出现执行结果与期待结果不一致的情况,这时候就是"非线程安全"的。解决"非线程安全"可以采用synchronized关键字锁类或锁对象,本文大部分实例来自于《Java多线程编程核心技术》
class ThreadA extends Thread {
private HasSelfPrivateNum numRef;
public ThreadA(HasSelfPrivateNum numRef) {
super();
this.numRef = numRef;
}
@Override
public void run() {
super.run();
numRef.addI("a");
}
}
class ThreadB extends Thread {
private HasSelfPrivateNum numRef;
public ThreadB(HasSelfPrivateNum numRef) {
super();
this.numRef = numRef;
}
@Override
public void run() {
super.run();
numRef.addI("b");
}
}
public class HasSelfPrivateNum {
private int num = 0; //②实例变量,存在非线程安全问题
public synchronized void addI(String username) {
// int num = 0; //①方法内的私有变量,线程安全的
try {
if (username.equals("a")) {
num = 100;
System.out.println("a set over!");
Thread.sleep(2000);
} else {
num = 200;
System.out.println("b set over!");
}
System.out.println(username + " num =" + num);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
HasSelfPrivateNum numRef = new HasSelfPrivateNum();
ThreadA athread = new ThreadA(numRef);
athread.start();
ThreadB bthread = new ThreadB(numRef);
bthread.start();
}
}
- 当我们解开①处注释,将②处注释掉,执行结果如下,证明当变量属于方法中的私有变量时,多个线程同时去访问一个没有同步的方法,不存在线程安全问题
- 当我们注释掉①处代码,将②处代码解开,执行结果如下,证明当变量属于对象的实例变量,多个线程同时去访问一个没有同步的方法,则会出现线程安全的问题
java提供了一种内置的锁机制来支持原子性,每一个java对象都可以用作一个实现同步的锁,称为内置锁,内置锁是互斥锁,即线程A获得锁后,线程B想要获得锁,就需等待线程A释放锁后才能获得,该过程是阻塞的。
加static关键字修饰的方法或变量属于类级别的,当我们在加了static关键字修饰的方法或变量上加synchronized锁,即是类锁,在非静态方法前面是给对象加锁。本实例来自https://blog.csdn.net/zhujiangtaotaise/article/details/55509939
class Task2{ public synchronized static void doLongTimeTaskA() { System.out.println("name = " + Thread.currentThread().getName() + ", begain"); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("name = " + Thread.currentThread().getName() + ", end"); } public synchronized static void doLongTimeTaskB() { System.out.println("name = " + Thread.currentThread().getName() + ", begain"); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("name = " + Thread.currentThread().getName() + ", end"); } public synchronized void doLongTimeTaskC() { System.out.println("name = " + Thread.currentThread().getName() + ", begain"); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("name = " + Thread.currentThread().getName() + ", end"); } } class Thread1 extends Thread{ private Task2 mTask2; public Thread1(Task2 tk){ mTask2 = tk; } @Override public void run() { mTask2.doLongTimeTaskA(); } } class Thread2 extends Thread{ private Task2 mTask2; public Thread2(Task2 tk){ mTask2 = tk; } @Override public void run() { mTask2.doLongTimeTaskB(); } } class Thread3 extends Thread{ private Task2 mTask2; public Thread3(Task2 tk){ mTask2 = tk; } @Override public void run() { mTask2.doLongTimeTaskC(); } } public class SynObjectOrClass { public static void main(String[] args) { Task2 mTask2 = new Task2(); Thread1 ta = new Thread1(mTask2); Thread2 tb = new Thread2(mTask2); Thread3 tc = new Thread3(mTask2); ta.setName("A"); tb.setName("B"); tc.setName("C"); ta.start(); tb.start(); tc.start(); } }
执行结果:
name = A, begain name = C, begain name = A, end name = B, begain name = C, end name = B, end
多执行几次,出现的结果依旧是A先执行完毕后,B才能执行,本实例中方法A和方法B都是类锁,方法C是对象锁。方法A和方法B是同一种锁--类锁,类锁是对该类中的所有变量和方法都起作用的,因此虽然线程ta和tb执行的是不同的方法,但是因为是类锁,所以需要等待,出现了阻塞,线程tc调用的是对象锁,与方法A和方法B的锁类型是不一样的,因此可以异步执行。
多个对象多个锁针对的是多个线程来讲,当多个线程去访问同一个加锁的方法时,synchronized取得的锁是对象锁,而不是把一段代码或方法当做锁,哪个线程先执行带synchronized关键字的方法,哪个线程就持有该方法所属对象的锁,其他线程只能等待已经获得锁的对象释放锁。如果多个线程访问的是多个对象,那么JVM会创建多个锁。如下实例:
/** * 多个对象,多把锁 */ public class MultiThread { private static int num = 0; /** * 不加static修饰,则会出现多个对象多把锁 * 加上static修饰,则将锁加在了类级别上,同一个时刻只有一个对象进入临界区 * @param tag */ public synchronized void printNum(String tag){ try{ if(tag.equals("a")){ num =100; System.out.println("tag a ,set num over!"); Thread.sleep(1000); }else { num =200; System.out.println("tag b ,set num over!"); } System.out.println("tag " + tag + ", num = " + num); }catch (InterruptedException e){ e.printStackTrace(); } } public static void main(String[] args) { final MultiThread m1 = new MultiThread(); final MultiThread m2 = new MultiThread(); Thread t1 = new Thread(new Runnable() { @Override public void run() { m1.printNum("a"); } }); Thread t2 = new Thread(new Runnable() { @Override public void run() { m2.printNum("b"); } }); t1.start(); t2.start(); } }
执行结果:
tag a ,set num over! tag b ,set num over! tag b, num = 200 tag a, num = 200
虽然线程t1和线程t2最终调用的都是方法printNum,但此时,printNum是对象锁,因此线程t1和线程t2中分别获得是两个对象的锁,所以执行结果是异步的,如果printNum方法加上static关键字修饰,则线程t1和线程t2获取的将是类锁,结果只能同步,执行结果如下:
tag a ,set num over! tag a, num = 100 tag b ,set num over! tag b, num = 200
当一个线程得到一个对象锁后,再次请求此对象锁时是可以再次得到该对象的锁的。锁重入的前提是对一个线程来讲。
/** * synchronized锁重入 */ class Service{ synchronized public void service1(){ System.out.println(Thread.currentThread().getName() + "service1"); service2(); } synchronized public void service2(){ System.out.println(Thread.currentThread().getName() + "service2"); service3(); } synchronized public void service3(){ System.out.println(Thread.currentThread().getName() + "service3"); } } public class synLockIn extends Thread{ @Override public void run(){ Service service = new Service(); service.service1(); } public static void main(String[] args) { synLockIn t = new synLockIn(); //synLockIn t2 = new synLockIn(); //1 t.start(); //t2.start(); //2 } }
执行结果如下:
证明t线程获得service对象的锁后,访问代码块中其他加锁方法时依旧可以得到对象锁,继续执行,这是针对一个线程来讲的,当多个线程时,一个线程获得锁,其余线程就需等待了。Thread-0service1 Thread-0service2 Thread-0service3
当我们锁整个方法时,假设在共享区上需要执行一个很长时间的业务,这时候锁住方法会降低效率,一个线程获得对象锁后其他都都需要等待。这时候能提高效率的简单方法是减小锁的粒度,将锁整个方法减小到锁一个代码块。
上述举的实例都是锁定对象或类的,即synchronized(this),将任意对象作为对象监视器,定义一个类变量即可。synchronized(非this)
- synchronized同步方法对其他同步方法或synchronized(this)同步代码块调用时阻塞状态
- 同一时间只有一个线程可以执行synchronized同步方法的代码
- 同一时间只有一个线程可以执行synchronized(this)同步代码块中的代码
- 在多个线程持有"对象监视器"为同一个对象的前提下,同一时间只有一个线程可执行synchronized(非this)同步代码块中的代码