并发:一个对象被多个线程同时操作
如
处理并发问题:
线程同步=队列+锁
最天然的处理:让线程排队,一个一个来(队列)
线程同步是一个等待机制,多个线程要同时操作一个对象时,让线程进入对象等待池,形成队列,一个一个来
锁:在一个线程正在操作该对象时,要上锁,防止其他线程抢占操作(每个对象都有一把锁)
synchronized(锁机制)
当一个线程获得对象的排它锁,独占资源,其他资源必须等待,使用后释放锁即可
会存在的问题:
1.会引发性能问题
2.如果一个优先级高的线程在等待一个优先级低的线程释放锁,会引发优先级倒置,发生性能问题。
每个线程在自己的工作内存交互,内存控制不当会造成数据不一致。
线程是不安全的,必须加以控制(队列+锁)
线程未加锁:
import java.util.ArrayList; import java.util.List; //线程的不安全集合 public class UnsafeList { public static void main(String[] args) { List<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()); } }
运行结果为:
注意:结果是变化的,可能有多个线程抢占同一位置(并发问题)
线程加锁后:
package syn; import java.util.ArrayList; import java.util.List; public class SafeList { public static void main(String[] args) throws InterruptedException { List<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()); } }
运行结果为:
线程同步的解决方法:
1.隐式定义同步锁
synchronized 关键字加在 方法上(加在public与返回值之间)---》默认锁this对象,也就是该方法所在的类的对象
或synchronized(obj){}代码块(用{}将整个代码块包含进去)--》锁的是(obj)中obj对象
未加锁:
//不安全的银行 public class UnsafeBank { public static void main(String[] args) { Account account = new Account(100,"结婚基金"); Drawing man = new Drawing(account,50,"男人"); Drawing woman = new Drawing(account,100,"女人"); man.start(); woman.start(); } } //个人账户 class Account{ private int money;//账户余额 private String name;//账号名称 public Account(int money, String name) { this.money = money; this.name = name; } public int getMoney() { return money; } public void setMoney(int money) { this.money = money; } public String getName() { return name; } public void setName(String name) { this.name = name; } } //银行,模拟取票 class Drawing extends Thread{ private Account account;//账户 private int drawMoney;//取了多少钱 private int nowMoney;//现在手里多少钱 public Drawing(Account account,int drawMoney,String name){ super(name); this.account = account; this.drawMoney = drawMoney; } @Override public void run() { //判断有没有钱 if(account.getMoney() - drawMoney <0){ System.out.println(account.getName()+"卡里的钱不够,取不了这么多!"); return; } try { Thread.sleep(1000);//模拟一个延时 } catch (InterruptedException e) { e.printStackTrace(); } account.setMoney(account.getMoney()-drawMoney); nowMoney = nowMoney + drawMoney; System.out.println(account.getName()+"卡里的余额为"+account.getMoney()); System.out.println(Thread.currentThread().getName()+"现在手上有"+nowMoney+"钱!"); } }
加锁后
public class SafeBank { public static void main(String[] args) { Account1 account1 = new Account1(100,"结婚基金"); Drawing1 man = new Drawing1(account1,50,"男人"); Drawing1 woman = new Drawing1(account1,100,"女人"); man.start(); woman.start(); } } //个人账户 class Account1{ private int money;//账户余额 private String name;//账号名称 public Account1(int money, String name) { this.money = money; this.name = name; } public int getMoney() { return money; } public void setMoney(int money) { this.money = money; } public String getName() { return name; } public void setName(String name) { this.name = name; } } //银行,模拟取票 class Drawing1 extends Thread{ private Account1 account1;//账户 private int drawMoney;//取了多少钱 private int nowMoney;//现在手里多少钱 public Drawing1(Account1 account1,int drawMoney,String name){ super(name); this.account1 = account1; this.drawMoney = drawMoney; } @Override public void run() { synchronized (account1){//加对象锁代码块,解决线程同步 //判断有没有钱 if(account1.getMoney() - drawMoney <0){ System.out.println(account1.getName()+"卡里的钱不够,取不了这么多!"); return; } try { Thread.sleep(1000);//模拟一个延时 } catch (InterruptedException e) { e.printStackTrace(); } account1.setMoney(account1.getMoney()-drawMoney); nowMoney = nowMoney + drawMoney; System.out.println(account1.getName()+"卡里的余额为"+account1.getMoney()); System.out.println(Thread.currentThread().getName()+"现在手上有"+nowMoney+"钱!"); } } }
注意:
1.锁的对象是公共资源,是变化的量,是需要增删改查的对象
2.synchronized十分消耗资源,运行缓慢(可能主线程运行完了,用的锁资源的线程还没运行完,得sleep等待一下才行),{ }中包含的代码
越少,程序运行起来就越快。(在例子中加sleep是为了模拟一个延时,让所有线程都可以抢占同一资源)
死锁:多个线程抱着对方需要的资源不放,形成僵持
避免方法:synchronized{}中不要包含有另一个synchronized{}公共资源,要让资源等待相互独立。
造成死锁:
package syn; public class DeadLock { public static void main(String[] args) { MakeUp g1 = new MakeUp(0,"灰姑娘"); MakeUp g2 = new MakeUp(1,"白雪公主"); g1.start(); g2.start(); } } //口红 class Lipstick{ } //镜子 class Mirror{ } class MakeUp extends Thread{ // 需要的资源只有一份,用static static Lipstick lipstick = new Lipstick(); static Mirror mirror = new Mirror(); // 选择 int choice; String name;//化妆人的名字 MakeUp(int choice,String name){ this.choice = choice; this.name = name; } @Override public void run() { // 化妆 try { makeup(); } catch (InterruptedException e) { e.printStackTrace(); } } // 化妆方法 public void makeup() throws InterruptedException { if (choice == 0){ // 先获得口红,再获得镜子 synchronized (lipstick){ System.out.println(this.name+"获得了口红"); Thread.sleep(1000); // 等一秒钟,获得镜子 synchronized (mirror){ System.out.println(this.name+"获得了镜子"); } } }else { synchronized (mirror){ // 先获得镜子,再获得口红 System.out.println(this.name+"获得了镜子"); Thread.sleep(1000); synchronized (lipstick){ System.out.println(this.name+"获得了口红"); } } } } }
运行结果为:
程序还在运行,没有停止
解决死锁:
package syn; public class DeadLock { public static void main(String[] args) { MakeUp g1 = new MakeUp(0,"灰姑娘"); MakeUp g2 = new MakeUp(1,"白雪公主"); g1.start(); g2.start(); } } //口红 class Lipstick{ } //镜子 class Mirror{ } class MakeUp extends Thread{ // 需要的资源只有一份,用static static Lipstick lipstick = new Lipstick(); static Mirror mirror = new Mirror(); // 选择 int choice; String name;//化妆人的名字 MakeUp(int choice,String name){ this.choice = choice; this.name = name; } @Override public void run() { // 化妆 try { makeup(); } catch (InterruptedException e) { e.printStackTrace(); } } // 化妆方法 public void makeup() throws InterruptedException { if (choice == 0){ // 先获得口红,再获得镜子 synchronized (lipstick){ System.out.println(this.name+"获得了口红"); Thread.sleep(1000); } // 等一秒钟,获得镜子 synchronized (mirror){ System.out.println(this.name+"获得了镜子"); } }else { synchronized (mirror){ // 先获得镜子,再获得口红 System.out.println(this.name+"获得了镜子"); Thread.sleep(1000); } synchronized (lipstick){ System.out.println(this.name+"获得了口红"); } } } }
运行结果为:
运行完成
2.显示定义同步锁(Lock接口)
jdk5.0后,可通过显示定义同步锁对象Lock对象,来实现线程同步
ReentrantLock实现了Lock接口,可用于解决线程并发问题
class A{
// 定义lock锁
private final ReentrantLock lock = new ReentrantLock();
try {
lock.lock();//加锁
//要加锁的同步代码块
}finally {
lock.unlock();//解锁,如果同步代码块有异常要抛出,要将unlock()写在finally{}内
}
}
package syn; import java.util.concurrent.locks.ReentrantLock; //可重入锁的测试 public class TestReentrantLock { public static void main(String[] args) { Ticket t = new Ticket(); new Thread(t,"A").start(); new Thread(t,"B").start(); new Thread(t,"C").start(); } } class Ticket implements Runnable{ private int num = 10; // 定义lock锁 private final ReentrantLock lock = new ReentrantLock(); @Override public void run() { while (true){ try { lock.lock();//加锁 if(num >0){ try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName()+"买了票----"+num--); }else { break; } }finally { lock.unlock();//解锁 } } } }
synchronized与Lock的比较
1.Lock是显式锁,要手动开启,关闭;synchronized是隐式锁,出了作用域会自动关闭
2.Lock只有代码块锁,synchronized有代码块锁和方法锁
3.Lock锁,jvm调用时间更少,性能更好,有更好的扩展性
4.优先使用顺序
Lock>同步代码块(已经进入了方法体,分配相应的资源)>同步方法(在方法体外部)