线程安全的单例模式
单利模式是一种常见的软件设计模式,核心思想就是一个类只有一个实例对象
单线程下的单例模式
public class SingleTonDemo {
private static SingleTonDemo singleTonDemo;
private SingleTonDemo(){
System.out.println("创建了SingleTonDemo...");
}
public static SingleTonDemo getInstance(){
if (singleTonDemo == null){
singleTonDemo=new SingleTonDemo();
}
return singleTonDemo;
}
}
测试类
public class Test {
public static void main(String[] args) {
SingleTonDemo singleTonDemo = SingleTonDemo.getInstance();
SingleTonDemo singleTonDemo1 = SingleTonDemo.getInstance();
System.out.println(singleTonDemo==singleTonDemo1);
}
}
多线程下的单例模式
public class SingleTonDemo {
private static SingleTonDemo singleTonDemo;
private SingleTonDemo(){
System.out.println("创建了SingleTonDemo...");
}
public synchronized static SingleTonDemo getInstance(){
if (singleTonDemo == null){
singleTonDemo=new SingleTonDemo();
}
return singleTonDemo;
}
}
测试代码
public class Test {
public static void main(String[] args) {
new Thread(new Runnable() {
@Override
public void run() {
SingleTonDemo singleTonDemo = SingleTonDemo.getInstance(i);
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
SingleTonDemo singleTonDemo = SingleTonDemo.getInstance(b);
}
}).start();
}
}
但是我们虽然这种做法保证了多线程下的线程安全,但是效率降低了,必需要等一个线程释放锁之后其他线程才能进来,但是这时候如果还有其他的不需要的考虑线程安全的业务代码也在这个上了锁的方法里面,那就显得效率太低了,因此这种单例模式也不是最好的,那麽还有如下一种实现方法
双重检测,synchronized修饰代码块
1.线程同步是为了实现线程安全,如果只创建一个对象,那摩线程是安全。
2.如果synchronized锁定的是多个线程共享的数据(同一个对象),那摩线程就是安全的
public class SingleTonDemo {
private volatile static SingleTonDemo singleTonDemo;
private SingleTonDemo(){
System.out.println("创建了SingleTonDemo...");
}
public static SingleTonDemo getInstance(Integer i){
if (singleTonDemo == null){
synchronized (i){
if (singleTonDemo==null){
singleTonDemo=new SingleTonDemo();
}
}
/**
* 其他不需要保证线程安全的业务
*/
}
return singleTonDemo;
}
}
测试代码如下
public class Test {
public static void main(String[] args) {
new Thread(new Runnable() {
@Override
public void run() {
Integer i = Integer.parseInt("1");
SingleTonDemo singleTonDemo = SingleTonDemo.getInstance(i);
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
Integer b = Integer.parseInt("1");
SingleTonDemo singleTonDemo = SingleTonDemo.getInstance(b);
}
}).start();
}
}
volatile的作用可以使内存中的数据对线程可见。
当一个线程想要操作主内存中的数据的时候,它的操作过程是先将主内存中的数据复制一份到工作内存,然后在工作内存中对数据进行操作,操作完成之后释放锁,然后再将操作后的数据还给主内存,假设在这种时候在线程1刚好释放锁之后,还没来得及将数据还给主内存,这个时候线程2冲进来了,发现主内存中的数据并没有发生改变,于是他也重复线程1之前的操作,那摩这种情况就不是线程安全的,及不是单例的,因此这种方法在理论上还不是百分百单例,因此需要使用volatile关键字
主内存对线程是不可见的,添加volatile关键字之后,主内存对线程可见,
单例模式的三种形式
线程同步
并发、并行
使用并发编程的目的?为了充分利用计算机的资源,提高性能,企业以已盈利为目的。
并发:多个线程访问同一个共享资源,前提是计算机是单核cpu,多个线程不是在同时访问,而是交替进行,只是因为cpu运行速度太快,看起来像是同时在执行。
并行:多核cpu,多个线程是真正同时在运行,各自占用不同的cpu,相互之间没有影响,也不会争夺资源。
Java默认线程有两个,main(主线程),GC(垃圾回收机制)
synchronized关键字实现线程同步,让在访问同一个资源的多个线程排队去完成业务,避免出现数据错乱的情况
死锁DeadLock
前提:一个线程完成业务需要同时访问两个资源
死锁:多个线程同时在完成业务,出现正争强资源的情况,A线程需要B线程中的某个资源,B线程需要A线程的某个资源,两两不相让,因此造成死锁
public class DeadLockRunnable implements Runnable {
//1.编号
public int num;
// 资源
private static Chopsticks chopsticks1 = new Chopsticks();
private static Chopsticks chopsticks2 = new Chopsticks();
/**
* num = 1 拿到chopsticks1. 等待chopsticks2
* mun = 2 拿到chopsticks2 ,等待chopsticks1
*
*/
@Override
public void run() {
if (num == 1){
System.out.println(Thread.currentThread().getName()+"拿到chopsticks1,等待获取chopsticks2");
synchronized (chopsticks1){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (chopsticks2){
System.out.println(Thread.currentThread().getName()+“用餐完毕”);
}
}
}
if (num == 2){
System.out.println(Thread.currentThread().getName()+"拿到chopsticks2,等待获取chopsticks1");
synchronized (chopsticks2){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (chopsticks1){
System.out.println(Thread.currentThread().getName()+“用餐完毕”);
}
}
}
}
}
测试代码
public class DeadLockTest {
public static void main(String[] args) {
DeadLockRunnable deadLockRunnable1 = new DeadLockRunnable();
deadLockRunnable1.num = 1;
DeadLockRunnable deadLockRunnable2 = new DeadLockRunnable();
deadLockRunnable2.num = 2;
new Thread(deadLockRunnable1,"张三").start();
new Thread(deadLockRunnable2,"李四").start();
}
}
如何破解死锁
不要让多线程并发访问
public class DeadLockTest {
public static void main(String[] args) {
DeadLockRunnable deadLockRunnable1 = new DeadLockRunnable();
deadLockRunnable1.num = 1;
DeadLockRunnable deadLockRunnable2 = new DeadLockRunnable();
deadLockRunnable2.num = 2;
new Thread(deadLockRunnable1,"张三").start();
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(deadLockRunnable2,"李四").start();
}
}
使用lamda表达式简化代码开发
public class LamdaTest {
public static void main(String[] args) {
new Thread(()->{
for (int i = 0; i <100 ; i++) {
System.out.println("++++++++Runnable");
}
}).start();
}
}