线程同步
线程同步其实就是一种等待机制,多个需要同时访问此对象的线程进入这个对象的等待池形成队列,等待前面线程使用完毕,下一个线程再使用。
synchronized锁
为了保证多线程并发安全,在访问时加入锁机制synchronized,当一个线程获得对象的排他锁,独占资源,其他线程必须等待,使用后释放锁即可,存在以下问题:
- 一个线程持有锁会导致其他所有需要此锁的线程挂起
- 在多线程竞争下,加锁,释放锁会导致比较多的上下文切换和调度延时,引起性能问题;
- 如果一个优先级高的线程等待一个优先级低的线程释放锁,会导致优先级倒置,引起性能问题
线程不安全例子
-
不安全的买票
public class unsafeByTickets { public static void main(String[] args) { BuyTickets station = new BuyTickets(); //2、三个线程买票 new Thread(station,"小明").start(); new Thread(station,"小红").start(); new Thread(station,"老师").start(); } } //1、首先要有一个买票的类 class BuyTickets implements Runnable{ //票 private int tickets = 10; boolean flag = true; @Override public void run() { while (flag){ try { buy(); } catch (InterruptedException e) { e.printStackTrace(); } } } private void buy() throws InterruptedException { if(tickets <= 0){ flag = false; return; } System.out.println(Thread.currentThread().getName()+"买了第"+tickets--+"票"); Thread.sleep(100); } }
执行结果:线程不安全
-
不安全的取钱
public class UnsafeBank { public static void main(String[] args) { Accout accout = new Accout("结婚基金",100); //两个进程取同一个账户里面的钱 new Bank(accout,100,new Person("小王",0)).start(); new Bank(accout,50,new Person("小红",0)).start(); } } //1、账户类 class Accout { private int accoutMoney;//账户里的钱 private String name;//账户名 public Accout(String name, int accoutMoney) { this.name = name; this.accoutMoney = accoutMoney; } public int getAccoutMoney() { return accoutMoney; } public void setAccoutMoney(int accoutMoney){ this.accoutMoney = accoutMoney; } } //2、取款人 class Person{ private String name;//姓名 private int personMoney;//手里的钱 public Person(String name,int personMoney){ this.name = name; this.personMoney = personMoney; } public String getName() { return name; } public int getPersonMoney() { return personMoney; } public void setPersonMoney(int personMoney) { this.personMoney = personMoney; } } //3、银行类,可以直接继承Thread class Bank extends Thread{ Accout accout; int drawingMoney;//取的钱 Person person; @Override public void run() { //模拟延时 try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } //判断账户里面的钱够不够取 if(drawingMoney > accout.getAccoutMoney()){ System.out.println(person.getName()+"钱不够了"); return; } accout.setAccoutMoney(accout.getAccoutMoney() - drawingMoney); person.setPersonMoney(person.getPersonMoney()+drawingMoney); System.out.println(person.getName()+"取钱后,账户里的钱:" +accout.getAccoutMoney()); System.out.println(person.getName()+"手里的钱:"+person.getPersonMoney()); } //构造函数 public Bank(Accout accout,int drawingMoney,Person person){ this.accout = accout; this.drawingMoney = drawingMoney; this.person = person; } }
执行结果:两人都可以成功取钱,线程不安全
-
不安全的集合:arraylistt为例子
import java.util.ArrayList; public class UnsafeList { public static void main(String[] args) { ArrayList<String> list = new ArrayList<String>(); for (int i = 0; i < 10000; i++) { new Thread(()->{ list.add(Thread.currentThread().getName()); }).start(); } System.out.println(list.size()); } }
结果:list容量不足10000,说明有多个线程操作了同一个list位置
同步方法
-
由于我们可以通过private关键字来保证数据对象只能被方法访问,所以我们只需要针对方法提出一套机制——synchronized关键字,它包括两种用法:synchronized方法和synchronized块
public synchronized void method(int args){}//同步方法
-
synchronized方法控制对“对象"的访问,每个对象对应一把锁,每个synchronized方法必须获得该方法的锁才能执行,否则线程会阻塞。方法一旦执行,就独占该锁,直到方法返回才释放锁,后面被阻塞的线程才能获得这个锁,继续执行
缺陷:若将一个大的方法申明为synchronized将会影响效率
-
方法里面需要修改的内容才需要锁,锁得太多,浪费资源
同步块
-
同步块:synchronized(obj){}
-
obj称为同步监视器
- obj可以是任何对象,但是推荐使用共享资源作为同步监视器
- 同步方法中无需指定同步监视器,因为同步方法得同步监视器就是this这个对象本身,或者是class
-
同步监视器得执行过程
1、第一个线程访问,锁定同步监视器,执行其中代码
2、第二个线程访问,发现同步监视器被锁定,无法访问
3、第一个线程访问完毕,解锁同步监视器
4、第二个线程访问,发现同步监视器没有锁,然后锁定并访问
线程安全例子
-
取票安全
```java public class unsafeByTickets { public static void main(String[] args) { BuyTickets station = new BuyTickets(); //2、三个线程买票 new Thread(station,"小明").start(); new Thread(station,"小红").start(); new Thread(station,"老师").start(); } } //1、首先要有一个买票的类 class BuyTickets implements Runnable{ //票 private int tickets = 10; boolean flag = true; @Override public void run() { while (flag){ try { buy(); } catch (InterruptedException e) { e.printStackTrace(); } } } //同步方法 private synchronized void buy() throws InterruptedException { if(tickets <= 0){ flag = false; return; } System.out.println(Thread.currentThread().getName()+"买了第"+tickets--+"票"); Thread.sleep(100); } } ```
-
取钱安全
public class UnsafeBank { public static void main(String[] args) { Accout accout = new Accout("结婚基金",100); //两个进程取同一个账户里面的钱 new Bank(accout,100,new Person("小王",0)).start(); new Bank(accout,50,new Person("小红",0)).start(); } } //1、账户类 class Accout { private int accoutMoney;//账户里的钱 private String name;//账户名 public Accout(String name, int accoutMoney) { this.name = name; this.accoutMoney = accoutMoney; } public int getAccoutMoney() { return accoutMoney; } public void setAccoutMoney(int accoutMoney){ this.accoutMoney = accoutMoney; } } //2、取款人 class Person{ private String name;//姓名 private int personMoney;//手里的钱 public Person(String name,int personMoney){ this.name = name; this.personMoney = personMoney; } public String getName() { return name; } public int getPersonMoney() { return personMoney; } public void setPersonMoney(int personMoney) { this.personMoney = personMoney; } } //3、银行类,可以直接继承Thread class Bank extends Thread{ Accout accout; int drawingMoney;//取的钱 Person person; @Override public void run() { //如果使用同步方法,监视器对象默认是Bank,没有效果 //同步代码块,监视器对象是accout synchronized (accout) { //模拟延时 try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } //判断账户里面的钱够不够取 if (drawingMoney > accout.getAccoutMoney()) { System.out.println(person.getName() + "钱不够了"); return; } accout.setAccoutMoney(accout.getAccoutMoney() - drawingMoney); person.setPersonMoney(person.getPersonMoney() + drawingMoney); System.out.println(person.getName() + "取钱后,账户里的钱:" + accout.getAccoutMoney()); System.out.println(person.getName() + "手里的钱:" + person.getPersonMoney()); } } //构造函数 public Bank(Accout accout,int drawingMoney,Person person){ this.accout = accout; this.drawingMoney = drawingMoney; this.person = person; } }
-
集合安全
import java.util.ArrayList; public class UnsafeList { public static void main(String[] args) throws InterruptedException { ArrayList<String> list = new ArrayList<String>(); for (int i = 0; i < 10000; i++) { new Thread(()->{ synchronized (list){ list.add(Thread.currentThread().getName()); } }).start(); } //加上延时,不然可能线程没操作完 Thread.sleep(1000); System.out.println(list.size()); } }
Lock锁
Lock锁介绍
-
java提供了更强大的线程同步机制——通过显式定义(synchronized是隐式的)同步锁对象来实现同步。同步锁使用Lock对象充当。
-
java.util.concurrent.locks.Lock接口是控制多个线程对共享资源进行访问的工具。锁提供了对共享资源的独占访问,每次只能有一个线程对Lock对象加锁,线程开始访问共享资源前应先获得Lock对象。
-
ReentrantLock类(可重入锁)实现了Lock,它拥有与synchronized相同的并发性和内存语义,在实现线程安全的控制中,比较常用的是ReentrantLock,可以显式加锁、释放锁。
-
格式:
class A{ private final ReentrantLock lock = new ReentantLock(); public void m(){ lock.lock();//加锁 try{ //需要加锁的代码 } finally{ lock.unlock;//解锁 } } }
synchronized和Lock对比
-
Lock是显式锁(手动开启和关闭锁);synchronized是隐式锁,出了作用域自动释放
-
Lock只有代码块锁,synchronized有代码块锁和方法锁
-
使用Lock锁,JVM将花费较少的时间来调度线程,性能更好。并且具有更好的扩展性(提供更多的子类)
优先使用顺序:Lock>同步代码块>同步方法
死锁
死锁定义
多个线程各自占有一些共享资源,并且互相等待其他线程占有得资源才能与逆行,而导致两个或多个线程都在等待对方释放资源,都停止执行的情形。某一个同步块同时拥有两个以上对象的锁时,就有可能会发生死锁。
例子:
public class DeadLock {
public static void main(String[] args) {
//4、两个女孩互相持有资源并僵持
new MakeUp("白雪公主",0).start();
new MakeUp("灰姑娘",1).start();
}
}
//1、先准备两份资源:镜子和口红
class Mirror{
}
class Lipstick{
}
//2、化妆类
class MakeUp extends Thread{
//使用static保证每个资源只有一份
//记得new一下,因为static必须初始化,否则会NullPointerException
static Mirror mirror = new Mirror();
static Lipstick lipstick = new Lipstick();
private String girlName;
private int ID;
MakeUp(String girlName,int ID){
this.girlName = girlName;
this.ID = ID;
}
//3、准备一个化妆方法
private void makeUp() throws InterruptedException {
if(ID == 0){
//一个同步块里面有两个锁,就可能发生死锁
synchronized (mirror){//获得镜子的锁
Thread.sleep(1000);
System.out.println(girlName+"获得镜子的锁");
synchronized (lipstick){//获得口红的锁
System.out.println(girlName+"获得口红的锁");
}
}
}else {
synchronized (lipstick){//获得口红的锁
Thread.sleep(1000);
System.out.println(girlName+"获得口红的锁");
synchronized (mirror){//获得镜子的锁
System.out.println(girlName+"获得镜子的锁");
}
}
}
}
@Override
public void run() {
try {
makeUp();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
结果:死锁
解决:不让一个同步块里面有两个锁
public class DeadLock {
public static void main(String[] args) {
//4、两个女孩互相持有资源并僵持
new MakeUp("白雪公主",0).start();
new MakeUp("灰姑娘",1).start();
}
}
//1、先准备两份资源:镜子和口红
class Mirror{
}
class Lipstick{
}
//2、化妆类
class MakeUp extends Thread{
//使用static保证每个资源只有一份
//记得new一下,因为static必须初始化,否则会NullPointerException
static Mirror mirror = new Mirror();
static Lipstick lipstick = new Lipstick();
private String girlName;
private int ID;
MakeUp(String girlName,int ID){
this.girlName = girlName;
this.ID = ID;
}
//3、准备一个化妆方法
private void makeUp() throws InterruptedException {
if(ID == 0){
synchronized (mirror){//获得镜子的锁
Thread.sleep(1000);
System.out.println(girlName+"获得镜子的锁");
}
synchronized (lipstick){//获得口红的锁
System.out.println(girlName+"获得口红的锁");
}
}else {
synchronized (lipstick){//获得口红的锁
Thread.sleep(1000);
System.out.println(girlName+"获得口红的锁");
}
synchronized (mirror){//获得镜子的锁
System.out.println(girlName+"获得镜子的锁");
}
}
}
@Override
public void run() {
try {
makeUp();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
结果:避免死锁
死锁避免方法
发生死锁的四个必要条件:互斥;请求与保持;不抢占;循环等待
只要破坏其中任意一个或多个条件就可以避免死锁发生
标签:同步,synchronized,accout,int,死锁,线程,new,public From: https://www.cnblogs.com/xiluoluo/p/16879973.html