在我的理解中,Java要学习的有三点Collection集合类
,线程相关
,io流
;但每一个模块的学习都是需要比较久的时间,如果你是学生,请一步一步啃完,每点都不要错过。如果你是工作了几年的,请先将三个模块中必看的过一遍,记录下来。然后学习你工作相关的知识,将必须掌握的先学习,然后在后面的时间中补充这些模块中的知识,形成系统。
Synchronize是我们实现线程互斥同步的常用手法,Synchronize本身是一个悲观机制的独占锁,并且可重入、非公平。
Synchronize加锁后,线程访问同步方式时会被阻塞,阻塞状态无法手动解除
使用Synchronized修饰方法
和代码块
,当线程A访问“某对象”的Synchronized方法,那么其他线程访问“该对象”的该Synchronized方法或Synchronized代码块将被阻塞;并且访问该对象的其他Synchronized方法或Synchronized代码块也将阻塞;访问“该对象”的非同步代码块将被允许
public class Animal {
//同步方法
public synchronized void eat() {
for (int i = 0; i < 10; i++) {
try {
System.out.println(Thread.currentThread().getName() + "eat:" + i);
Thread.sleep(100);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
//同步方法
public synchronized void run() {
for (int i = 0; i < 10; i++) {
try {
System.out.println(Thread.currentThread().getName() + "run:" + i);
Thread.sleep(100);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
//同步代码块,同步对象this
public void sleeps() {
synchronized (this) {
for (int i = 0; i < 10; i++) {
try {
System.out.println(Thread.currentThread().getName() + "sleeps:" + i);
Thread.sleep(100);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
//同步代码块,同步对象Animal.class
public static void readBook() {
synchronized (Animal.class) {
for (int i = 0; i < 10; i++) {
try {
System.out.println(Thread.currentThread().getName() + "readBook:" + i);
Thread.sleep(100);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
public void shout() {
for (int i = 0; i < 10; i++) {
try {
System.out.println(Thread.currentThread().getName() + "shout:" + i);
Thread.sleep(100);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
验证1:当线程A访问“某对象”的Synchronized方法,那么其他线程访问“该对象”的该Synchronized方法或Synchronized代码块将被阻塞;并且访问该对象的其他Synchronized方法或Synchronized代码块也将阻塞
public class SyncTest {
public static void main(String[] args) {
Animal animal = new Animal();
Thread t = new Thread(new Runnable() {
@Override
public void run() {
// TODO Auto-generated method stub
animal.eat();
}
});
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
// TODO Auto-generated method stub
animal.sleeps();
}
});
t.start();
t2.start();
}
}
打印如下:当线程t调用同步方法eat()时,另一个线程t2同步方法阻塞;当线程t调用同步方法animal.eat()方法时,另一个线程t2同步方法animal.sleeps()阻塞
验证2:当线程A访问“某对象”的Synchronized方法,访问“该对象”的非同步代码块将被允许。
public class SyncTest {
public static void main(String[] args) {
Animal animal = new Animal();
Thread t = new Thread(new Runnable() {
@Override
public void run() {
// TODO Auto-generated method stub
animal.eat();
}
});
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
// TODO Auto-generated method stub
animal.shout();;
}
});
t.start();
t2.start();
}
}
打印如下:线程调用交替进行,当一个对象的同步方法被调用的时候,其他线程调用当前对象的其他非同步方法将不被阻塞
上述特点都是实例锁的相关特点,在Synchronized中如果将锁使用在非静态方法或类上那么这就是一个实例锁,如果将锁使用在静态方法上或者类上,那这就是全局锁
。
实例锁是一个对象锁,如果该对象是单例模式,那么其具有全局锁的效果
全局锁是锁在类上或者静态方法上,无论拥类有多少个对象,线程都共享该锁
Animal 有同步方法 eat()、run() , 同步代码块sleeps()、静态同步代码块readBook()
接下来我们研究
1、结果::相互交替执行。因为synchronized对象锁是存在于各自的对象animal1和animal2上的属于各自实例,使用的是各自的实例锁,故相互交替执行。
2、结果:相互交替执行。animal1.eat()是调用的animal1的对象锁,属于实例锁,而animal2.readBook()相等于调用的是一个静态方法,静态方法不属于实例类,本身相等于调用的Animal.readBook(),属于全局锁,所以不是同一个锁。
3、结果:被阻塞。因为readBook()是一个静态方法,那么两个方法其实都相当于Animal.readBook(使用的全局锁, 全局锁不管这个类实例多少个对象,其锁都是在SomeThing类上,所以会被阻塞。
我们知道Synchronized可以修饰方法和代码块,在使用Synchronized修饰代码块
的时候得避免死锁的产生,死锁的产生主要是线程1获得了同步锁A,并且在A中要获取同步锁B,但在同步锁A的时候,有另一个线程2已经获取了同步锁B,并在B中获取同步锁A
, 那么线程A会等待线程B解锁,但线程B又等待线程A解锁,这时候就产生了死锁。Synchronized的阻塞过程不能手动结束
public class syncTest2 {
private static Object o1;
private static Object o2;
public static void main(String[] args) {
new syncTest2().deadLock();
}
public void deadLock() {
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
synchronized (o1) {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (o2) {
System.out.println(Thread.currentThread().getName() );
}
}
}
});
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
synchronized (o2) {
synchronized (o1) {
System.out.println(Thread.currentThread().getName() );
}
}
}
});
t1.start();
t2.start();
}
}
避免死锁:
1、使用Lock接口的tryLock(long timeout, TimeUnit unit)方法,设置超时时间,超时可以退出防止死锁
2、降低锁的使用粒度,只加锁共享变量,这样既增加效率,同时尽量避免死锁
3、减少或替代Synchronized的使用
它有多个队列,当多个线程一起访问某个对象监视器的时候,对象监视器会将这些线程存储在不同的容器中。
Contention List
:竞争队列,所有请求锁的线程首先被放在这个竞争队列中;
Entry List
:Contention List中那些有资格成为候选资源的线程被移动到Entry List中;
Wait Set
:哪些调用wait方法被阻塞的线程被放置在这里;
OnDeck
:任意时刻,最多只有一个线程正在竞争锁资源,该线程被成为OnDeck;
Owner
:当前已经获取到所资源的线程被称为Owner;
!Owner
:当前释放锁的线程。
JVM每次从队列的尾部取出一个数据用于锁竞争候选者(OnDeck)
,但是并发情况下,ContentionList会被大量的并发线程进行CAS访问,为了降低对尾部元素的竞争,JVM会将一部分线程移动到EntryList中作为候选竞争线程
。Owner线程会在unlock
时,将ContentionList中的部分线程迁移到EntryList中,并指定EntryList中的某个线程为OnDeck线程(一般是最先进去的那个线程)
。Owner线程并不直接把锁传递给OnDeck线程,而是把锁竞争的权利交给OnDeck,OnDeck需要重新竞争锁。这样虽然牺牲了一些公平性
,但是能极大的提升系统的吞吐量
,在JVM中,也把这种选择行为称之为“竞争切换”。
OnDeck线程获取到锁资源后会变为Owner线程
,而没有得到锁资源的仍然停留在EntryList中
。如果Owner线程被wait方法阻塞,则转移到WaitSet队列中,直到某个时刻通过notify或者notifyAll唤醒
,会重新进去EntryList
中。
处于ContentionList、EntryList、WaitSet中的线程都处于阻塞状态,该阻塞是由操作系统来完成的
(Linux内核下采用pthread_mutex_lock内核函数实现的)。
Synchronized是非公平锁。 Synchronized在线程进入ContentionList
时,等待的线程会先尝试自旋获取锁
,如果获取不到就进入ContentionList,这明显对于已经进入队列的线程是不公平的,还有一个不公平的事情就是自旋获取锁的线程还可能直接抢占OnDeck线程的锁资源
//消费
public class ConsumptionFactory implements Runnable {
private Warehouse warehouse;
public ConsumptionFactory(Warehouse warehouse) {
this.warehouse = warehouse;
}
@Override
public void run() {
// TODO Auto-generated method stub
onConsumption();
}
private void onConsumption() {
while (true) {
synchronized (warehouse) {
// 如果数量够,就消费
if (warehouse.getCommodityCount() > 0) {
warehouse.delete();
System.out.println(Thread.currentThread().getName() + "====消费后剩余:" + warehouse.getCommodityCount());
warehouse.notifyAll();
} else {
try {
warehouse.wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
}
}
//生产
public class ProductionFactiry implements Runnable {
private Warehouse warehouse;
public ProductionFactiry(Warehouse warehouse) {
this.warehouse = warehouse;
}
@Override
public void run() {
// TODO Auto-generated method stub
onProduction();
}
private void onProduction() {
while (true) {
synchronized (warehouse) {
warehouse.add();
System.out.println(Thread.currentThread().getName() + "---生产后剩余" + warehouse.getCommodityCount());
warehouse.notifyAll();
try {
warehouse.wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
}
public class Warehouse {
private int commodityCount = 0;
public synchronized void add() {
commodityCount++;
}
public synchronized void delete() {
commodityCount--;
}
public synchronized int getCommodityCount() {
return commodityCount;
}
}
//生产消费
public class ThreadTest {
public static void main(String[] args) {
Warehouse warehouse = new Warehouse();
ConsumptionFactory consumptionFactory = new ConsumptionFactory(warehouse);
ProductionFactiry productionFactiry = new ProductionFactiry(warehouse);
Thread c1 = new Thread(consumptionFactory);
Thread c2 = new Thread(consumptionFactory);
Thread p1 = new Thread(productionFactiry);
Thread p2 = new Thread(productionFactiry);
c1.start();
c2.start();
p1.start();
p2.start();
}
}
Java并发——Synchronized关键字和锁升级,详细分析偏向锁和轻量级锁的升级
Java – 偏向锁、轻量级锁、自旋锁、重量级锁