方式1 : 同步代码块
方式2 :同步方法
方式3 :单例模式-懒加载
方式4 :同步锁
同步代码块
synchronized(同步锁){
需要同步操作的代码
}
同步锁 : 为了保证每个线程都能正常执行原子操作,java 引入线程同步机制。
同步监听对象/同步锁/同步监听器/互斥锁。
对象的同步锁只是一个概念,可以想象为在对象上标记了一个锁。
java 程序运行使用任何对象作为同步监听对象,但是一般的,我们把当前并发访问的共同资源作为同步监听对象。
注意: 在任何时候,最多允许一个线程拥有同步锁.
package com._520it.day01._05_synchronized;
// 使用同步代码块
class Apple1 implements Runnable{
private int num = 50 ;
public void run() {
for(int i = 0 ; i<50; i++) {
// 同步代码块
synchronized(this) {
// 该对象就是共享资源
if(num>0) {
// 模拟网络延迟
System.out.println(Thread.currentThread().getName()+"吃了编号为" +num+"的苹果");
}
try {
Thread.sleep(10);
}
catch(InterruptedException e ) {
e.printStackTrace();
}
num-- ;
}
}
}
}
public class SynchronizedBlockDemo {
public static void main(String[] args) {
Apple1 a = new Apple1() ;
new Thread(a,"小A").start();
new Thread(a,"小B").start();
new Thread(a,"小C").start();
}
}
同步方法 : 使用 synchronized 修饰的方法,就叫同步方法,保证A线程执行该方法时,其他线程只能在方法外等待
synchronized public void doWork(){
}
问题 : 同步锁是谁啊 ?
对于非static 方法,同步锁就是this
对于static 方法, 我们使用当前方法所在的类的字节码对象 (Apple2.class)
注意 : 不要使用 synchronized 修饰 run 方法,修饰之后,某个线程就执行完了所有的功能.就好比多个线程出现串行 。
解决方案 : 把需要同步操作的代码定义在一个新的方法中,并且该方法使用 synchonized 修饰,再在run 方法中调用新的方法即可。
package com._520it.day01._05_synchronized;
// 使用同步代码块
class Apple1 implements Runnable{
private int num = 50 ;
public void run() {
for (int i = 0 ;i<50 ; i ++ ) {
eat() ;
}
}
synchronized private void eat() {
if(num>0) {
// 模拟网络延迟
System.out.println(Thread.currentThread().getName()+"吃了编号为" +num+"的苹果");
}
try {
Thread.sleep(10);
}
catch(InterruptedException e ) {
e.printStackTrace();
}
num-- ;
}
}
public class SynchronizedBlockDemo {
public static void main(String[] args) {
Apple1 a = new Apple1() ;
new Thread(a,"小A").start();
new Thread(a,"小B").start();
new Thread(a,"小C").start();
}
}
synchroized的好与坏
好处 : 保证了多线程并发访问时的同步操作,避免线程的安全性问题。
缺点 : 代码块性能降低。/ 尽量减少synchroized 代码块
单例模式 :
单例模式 -饿汉式
package com._520it.day01._05_synchronized;
// 单例模式-饿汉型
public class ArrayUtill1 {
// 外界不能创建对象了,
private ArrayUtill1() {
}
private static ArrayUtill1 instance = new ArrayUtill1() ;
// 对外的接口
public static ArrayUtill1 getInstance() {
return instance ;
}
}
单例模式-懒汉式
package com._520it.day01._05_synchronized;
// 单例模式 - 懒汉型
public class ArrayUtil2 {
private ArrayUtil2() {
}
private static ArrayUtil2 instance = null ;
// 同步方法 : 此时 线程同步对象是 ArrayUtil2.class
synchronized public static ArrayUtil2 getinstance() {
// 在获取时才创建对象;
if(instance == null ) {
instance = new ArrayUtil2() ;
}
return instance ;
}
}
此时 懒汉型代码是存在线程安全问题的, 我们当然可以直接加 synchronized 修饰整个代码块,但是为了提升性能,我们常常会对有安全隐患的代码段加加修饰。
"双重检查加锁" :
可以使用"双重检查加锁"的方式实现,就可以既可以实现线程安全,又能够使性能不受很大的影响。
所谓的"双重检查加锁" ,就是 ,并不是每次进入 getinstance 方法都需要同步,而是先不同步,进入方法后,先检查实例是否存在,如果不存在才进行下面的同步块,这是第一重检查,进入同步块后,再次检查实例是否存在,如果不存在,就在同步的情况下创建一个实例,这是第二重检查。
volatile ,他的意思是: 被volatile 修饰的变量的值,将不会被本地线程缓存,所有对该变量
的读写都是直接操作共享内存,从而确保多个线程能正确处理该变量。
注意 :由于 volatile 关键字可能会屏蔽jvm中的一些必要的代码优化,所以运行效率不是很高。
package com._520it.day01._05_synchronized;
// 单例模式 - 懒汉型
public class ArrayUtil2 {
private ArrayUtil2() {
}
private static volatile ArrayUtil2 instance = null ;
// 同步方法 : 此时 线程同步对象是 ArrayUtil2.class
public static ArrayUtil2 getinstance() {
// 在获取时才创建对象;
if (instance == null) {
synchronized (ArrayUtil2.class) {
if(instance == null ) {
instance = new ArrayUtil2() ;
}
}
}
return instance ;
}
}
同步锁 :
package com._520it.day01._05_synchronized;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
class Apple3 implements Runnable {
private int num = 50 ;
private final Lock lock = new ReentrantLock() ;
public void run() {
for (int i = 0 ;i<50 ; i ++ ) {
eat() ;
}
}
//
private void eat() {
if(num>0) {
lock.lock(); // 获取锁
try {
System.out.println(Thread.currentThread().getName()+"吃了编号为" +num+"的苹果");
Thread.sleep(10);
num-- ;
}
catch (Exception e) {
e.printStackTrace();
}finally {
lock.unlock();
}
}
}
}
public class lockDemo {
public static void main(String []args) {
Apple3 a = new Apple3() ;
new Thread(a,"小A").start();
new Thread(a,"小B").start();
new Thread(a,"小C").start();
}
}