Synchronized关键字
synchronized有加锁的作用,所有的synchronized修饰方法都会顺序执行(占用CPU的顺序)
当synchronized关键字修饰静态方法(static)时升级为类锁,如果不是静态方法两个对象之间没有关系
Synchronized代码块
可以做更细粒度的 可以做对象锁 可以做类锁 可以任意对象锁 同类型锁互斥,不同类型锁互不干扰
不要在线程内部修改锁的引用,引用改变会导致锁失效,在线程中修改锁的属性而不修改锁的引用则不会失效,不会产生线程安全问题
同一个对象内的多个synchronized方法可以锁重入 父子类方法也可以锁重入
Synchronized关键字执行方式
首先尝试获得锁
如果获得锁,则执行synchronized方法体的内容
如果没有获得锁则等待释放锁,并不断尝试获得锁,一旦锁被释放,多个线程会去尝试获得锁,造成锁竞争
锁竞争问题,如果在高并发,线程数量高,则会引起CPU占用高,或者直接宕机
Synchronized代码演示
public static void main(String[] args) {
ThreadDemo01 td = new ThreadDemo01();
ExecutorService es = Executors.newFixedThreadPool(2);
es.submit(() -> td.add());
try {
Thread.sleep(1000); // 确保add方法限制性,验证抛出异常锁释放
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
es.submit(() -> td.get());
}
public synchronized void add() {
for (int i = 0; i <= 10; i++) {
System.out.println("当前ID值为=" + (++id));
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
if (id == 10) {
throw new RuntimeException("抛出异常释放锁");
}
}
}
public synchronized void get() {
System.out.println("get方法获取ID=" + id);
}
对象锁/类锁/任意锁与同步异步问题?
对象锁之针对synchronized方法生效,对象中所有的synchronized方法都会同步执行,而非synchronized关键字修饰的方法则异步执行
类中有两个synchronized方法,两个线程同时调用两个方法,相互之间是有锁竞争关系的,因为两个方法属于一个对象,我们加的是对象锁
如果修改了锁的引用 锁的就失效了
/**
* 对象锁
*/
public void run1() {
synchronized(this) { //当前对象锁
try {
System.out.println(Thread.currentThread().getName()+"--> run1() --> 对象锁");
Thread.sleep(2000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
public void run2() {
synchronized(ThreadDemo02.class) { //类锁
try {
System.out.println(Thread.currentThread().getName()+"--> run2() --> 类锁");
Thread.sleep(2000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
private Object obj = new Object();
public void run3() {
synchronized(obj) { //任意对象锁
try {
System.out.println(Thread.currentThread().getName()+"--> run3() --> 任意锁");
Thread.sleep(2000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
public static void TestLock(int type) {
ExecutorService es = Executors.newFixedThreadPool(2);
ThreadDemo02 td1 = new ThreadDemo02();
ThreadDemo02 td2 = new ThreadDemo02();
if(type == 1) {
es.submit(()->{
td1.run1();
});
es.submit(()->{
td1.run1();
});
}else if(type == 2) {
es.submit(()->{
td1.run2();
});
es.submit(()->{
td2.run2();
});
}else if(type == 3 ){
es.submit(()->{
td1.run3();
});
es.submit(()->{
td1.run3();
});
}else {
es.submit(()->{
td1.run3();
});
es.submit(()->{
td1.run2();
});
}
es.shutdown();
}
public static void main(String[] args) {
// TestLock(1); 互斥
// TestLock(2); 互斥
// TestLock(3); 互斥
TestLock(4); // 同类型互斥,不同类型互不干扰
}
脏读
由于同步与异步执行的个性,如果不从全局考虑,就很容易引起数据的不一致,这就是脏读
多线程访问同一个资源,在线程修改数据的过程中,有另外的数据来读取数据,就会引发脏读
为了避免脏读我们一定要确定修改数据的原子性,并且对读取数据进行同步控制
抛出异常释放锁
一个线程在获得锁以后执行操作,发生错误抛出异常,则自动释放锁 可以利用抛出异常主动释放锁 程序异常时主动抛出异常防止死锁,无法释放
如果非主动抛出异常而是程序错误抛出的异常释放锁可能导致不一致
太简单没有案例
并发与死锁
是指两个进程或以上的进程在执行过程中,由于竞争资源或者由于彼此通信而造成的一种堵塞现象,若无外力作用,他们都将无法推进下去, 线程间的通讯
每个线程都是独立的运行个体,线程通信能让多个线程协同合作 Object类中wait与notify方法可以实现线程通讯
wait与notify必须与synchronized一同使用 wait是释放锁的,notify是不释放锁的
Notify只会通知一个wait中的线程,并把锁给他不会产生锁竞争问题但是该线程完成之后必须再次notify或者notifyAll,完成类似链式操作notifyAll 会通知所有的wait中的线程,会产生锁竞争
- 死锁代码
private Object lock1 = new Object();
private Object lock2 = new Object();
public void execute1() {
synchronized (lock1) {
System.out.println(Thread.currentThread().getName() + " 获取lock1的锁了");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
synchronized (lock2) {
System.out.println(Thread.currentThread().getName() + " 获取lock2的锁了");
}
}
}
public void execute2() {
synchronized (lock2) {
System.out.println(Thread.currentThread().getName() + " 获取lock2的锁了");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
synchronized (lock1) {
System.out.println(Thread.currentThread().getName() + " 获取lock1的锁了");
}
}
}
/**
* 是指两个以上的进程或这两个执行过程中,由于竞争资源或者彼此通信而造成的一种堵塞的现象,
* 若无外力作用,他们都将无法推进下去,此时称系统处于死锁状态或者说系统产生死锁,这些互相等待的线程称为死锁进程
* @param args
*/
public static void main(String[] args) {
ThreadDemo04 td = new ThreadDemo04();
new Thread(() -> {
td.execute1();
}).start();
new Thread(() -> {
td.execute2();
}).start();
}
2.解决死锁
private volatile ArrayList<String> list = new ArrayList<String>();
public synchronized void add() {
for (int i = 0; i < 10; i++) {
try {
Thread.sleep(1000);
} catch (InterruptedException e1) {
e1.printStackTrace();
}
list.add("A");
System.out.println(Thread.currentThread().getName() + "添加第" + i + "个");
if (i == 5) {
this.notify();
System.out.println("发出通知了");
}
}
}
public synchronized void get() {
if (list.size() == 0) {
try {
System.out.println("长度不到5无法继续");
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
for (String str : list) {
System.out.println(str);
}
}
public static void main(String[] args) {
ThreadDemo06 td = new ThreadDemo06();
new Thread(() -> {
td.get();
}).start();
new Thread(() -> {
td.add();
}).start();
}