多线程的使用、同步和异步、synchronized、线程安全的单例模式、死锁、解决死锁
DAY6.1 Java核心基础
多线程
线程同步
java中是允许线程并行访问的,线程在同一时间段的时候会完成各自的操作
为什么要使用线程同步
示例代码:
创建一个Account类来统计一个访问量
public class Account implements Runnable{
private int num = 0;
@Override
public void run() {
num++;
try {
Thread.sleep(200);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println(Thread.currentThread().getName()+"是第 "+num+" 位访客");
}
}
public static void main(String[] args) {
Account account = new Account();
Thread thread1 = new Thread(account);
Thread thread2 = new Thread(account);
thread1.setName("张三");
thread2.setName("李四");
thread1.start();
thread2.start();
}
输出:
可以看见张三和李四都是第2位访客,为什么呢,因为先进来的线程在遇到了Thread.sleep(200)休眠导致线程阻塞,然后另外一个线程现在也进来了,最后导致num++执行了两次,之后两个线程再执行的各自的输出
怎么解决呢?
是因为休眠导致的吗?答案是否定的
就算把休眠关闭,还是会有几率导致num++执行两次,因为首先进入程序的线程如果再num++之后被阻塞了,后面进来的线程抢占了CPU资源,然后之后的线程执行了num++,还是会导致num++两次
知识小点:
- 同步:多个线程按顺序执行,每次只能一个线程执行,执行完毕之后才能让第二个线程开始执行
- 异步:多个线程同时执行,不需要排队按顺序执行
解决方法:加线程锁,这里只讲最简单的synchronized关键字
简单来说就是一个ATM机取钱,然后每个线程进去取钱的时候会关门锁上,然后等待取完钱再开门,别人才能进来
synchronized:同步关键字
synchronized 修饰实例方法(非静态方法,没有static修饰的方法)
形参:上锁资源,钥匙
@Override
public synchronized void run() {
try {
num++;
Thread.sleep(2000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println(Thread.currentThread().getName() + "是第 " + num + " 位访客");
}
加完锁之后再运行就正常输出了
synchronized 修饰静态方法
public class Account implements Runnable {
private int num = 0;
@Override
public synchronized void run() {
try {
num++;
Thread.sleep(2000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println(Thread.currentThread().getName() + "是第 " + num + " 位访客");
}
public synchronized static void test(){
System.out.println("begin...");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("end.......");
}
}
public static void main(String[] args) {
new Thread(Account::test).start();
new Thread(Account::test).start();
new Thread(Account::test).start();
new Thread(Account::test).start();
}
synchronized 修饰代码块
上面锁方法的时候效率不是很高,因为锁住整个方法会导致整个方法都是同步执行的,而其实有些无关代码可以设置为异步执行,效率更高
@Override
public void run() {
System.out.println("不需要锁的代码");
System.out.println("不需要锁的代码");
synchronized (this){
try {
num++;
Thread.sleep(2000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println(Thread.currentThread().getName() + "是第 " + num + " 位访客");
}
System.out.println("不需要锁的代码");
System.out.println("不需要锁的代码");
}
线程安全的单例模式
是一种常见的设计模式
核心思想:一个类只能有一个实例方法,多个线程共享实例对象,spring里面的bean对象就是单例模式
1、将构造函数私有化 private
2、提供静态方法来返回实例化对象
public class SingleDemo {
private static SingleDemo singleDemo;
private SingleDemo() {
System.out.println("创建了SingleDemo实例化对象");
}
public static SingleDemo getInstance() {
synchronized (SingleDemo.class) {
if (singleDemo == null)
singleDemo = new SingleDemo();
}
return singleDemo;
}
}
测试类创建10个线程异步执行,测试是否为单例
public static void main(String[] args) {
for (int j=0;j<10;j++) {
new Thread(()->{
for (int i = 0; i < 5; i++)
System.out.println(SingleDemo.getInstance());
}).start();
}
}
部分输出
可以看见每一个SIngleDemo地址都是一样的,只创建了一个对象
死锁
使用synchronized 可以实现线程同步,解决并行访问的数据安全问题
但是也可能带来新的问题
死锁就是线程争夺同一个资源而带来的互斥问题
简单来说:死锁就是老王和老李想看有些画,然后老王正在看蒙娜丽莎,老李在看向日葵,然后老王不满足,在看蒙娜丽莎的同时也要看向日葵,不然就不走了,同时老李也是在看向日葵的时候也要看蒙娜丽莎,不然也不走,这时候就形成了死锁,资源不释放,谁都得不到资源,所以线程都处于阻塞状态
如何解决死锁
某个线程做出让步,贡献自己的资源给其它资源使用
示例代码:
区分老王和老李
public class TastRunnable implements Runnable{
private String name;
private static Draw draw1 =new Draw("蒙娜丽莎");
private static Draw draw2 =new Draw("向日葵");
public TastRunnable(String name) {
this.name = name;
}
@Override
public void run() {
if(name.equals("老王")){
//老王需要获取蒙娜丽莎
synchronized (draw1){
try {
System.out.println("老王获取了蒙娜丽莎,等待获取向日葵");
Thread.sleep(100);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
//老王还想获取向日葵
synchronized (draw2){
System.out.println("老王获取了蒙娜丽莎和向日葵");
}
}
}else if(name.equals("老李")){
//老李获取向日葵
synchronized (draw2){
try {
System.out.println("老李获取了向日葵,等待获取蒙娜丽莎");
Thread.sleep(100);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
//老李还想获取蒙娜丽莎
synchronized (draw1){
System.out.println("老李获取了蒙娜丽莎和向日葵");
}
}
}
}
}
public static void main(String[] args) {
TastRunnable task1 = new TastRunnable("老王");
TastRunnable task2 = new TastRunnable("老李");
Thread thread1 = new Thread(task1);
Thread thread2 = new Thread(task2);
thread1.start();
thread2.start();
}
出现死锁的情况,程序还在运行,但是卡住了
如何解决
解决方法1:
不要让他们一起执行,让老王和老李某一个慢一点启动,在主线程加一个休眠就行了
public static void main(String[] args) {
TastRunnable task1 = new TastRunnable("老王");
TastRunnable task2 = new TastRunnable("老李");
Thread thread1 = new Thread(task1);
Thread thread2 = new Thread(task2);
thread1.start();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
thread2.start();
}
结果: