线程同步
线程安全问题
三大不安全案例
购票问题
代码:
package thread;
//线程不安全,出现负数
public class UnsafebuyTicket {
public static void main(String[] args) {
BuyTicket station = new BuyTicket();
new Thread(station,"老师").start();
new Thread(station,"学生").start();
new Thread(station,"黄牛").start();
}
}
class BuyTicket implements Runnable{
private int ticketNums = 10;//票数
boolean flag = true; //外部停止方式
@Override
public void run() {
//买票
while (flag){
try {
buy();
} catch (Exception e) {
e.printStackTrace();
}
}
}
private void buy() throws Exception {
//判断是否有票
if (ticketNums<=0){
flag = false;
return;
}
//模拟延迟
Thread.sleep(100);
//买票
System.out.println(Thread.currentThread().getName()+"拿到"+ticketNums--);
}
}
每个线程在自己工作内存交互,内存控制不当会造成数据不一致,在购票中就会发送每个人都认为有票,会产生负数
两个人去银行取钱
代码
package thread;
public class UnsanfeBank {
public static void main(String[] args) {
//定义账户,100万
Account account = new Account(100, "购房基金");
Drawing you = new Drawing(account,50,"你");
Drawing wife = new Drawing(account,100,"妻子");//你和妻子一共要取出150万,但是账户里面只有100万
you.start();
wife.start();
}
}
//账户
class Account {
int money;//余额
String name;//银行卡名字
public Account(int money, String name) {
this.money = money;
this.name = name;
}
}
//银行
class Drawing extends Thread{
Account account;//账户
//取了多少钱
int drawingMoney;
//还剩多少钱
int nowMoney;
public Drawing(Account accountint, int drawingMoney,String name){
super(name);
this.account = accountint;
this.drawingMoney = drawingMoney;
}
//取钱
@Override
public void run() {
//判断是否够取
if (account.money-drawingMoney<0){
System.out.println(Thread.currentThread().getName()+"钱不够,取不了");
return;
}
//模拟延迟
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
account.money = account.money-drawingMoney; //卡内的余额
nowMoney = nowMoney+drawingMoney; //当前手中的钱
System.out.println(account.name+"的余额为:"+account.money);
//在这里 this.getName 等价于 Thread.currentThread().getName() 都是获取线程名字
System.out.println(this.getName()+"手里还有:"+nowMoney);
}
}
这样的线程是不安全的
不安全集合
代码
package thread;
import java.util.ArrayList;
import java.util.List;
public class UnsafeList {
public static void main(String[] args) throws InterruptedException {
List<String> list = new ArrayList<>();
for (int i = 0; i < 1000; i++) {
//lambda表达式,简易代码
new Thread(()->{
list.add(Thread.currentThread().getName());
}).start();
}
//放大效果
Thread.sleep(10);
System.out.println(list.size());
}
}
本来要产生1000个,但是实际上会减少一下,减少的就是两个线程同一时间将数组添加到了同一个位置
Synchronized(同步)
对比上文的购票问题,对代码进行优化,也就是加上synchronized 锁
//加了synchroinzed 同步方法,锁的是this
private synchronized void buy() throws Exception {
//判断是否有票
if (ticketNums<=0){
flag = false;
return;
}
//模拟延迟
Thread.sleep(100);
//买票
System.out.println(Thread.currentThread().getName()+"拿到"+ticketNums--);
}
}
不会有负数产生,加上了锁后,第一个人拿到了票才能放开锁,后一个人才能继续领票。
synchronized 默认监视this,需要监视其他的(例如run()方法)需要使用synchronized,可以锁任何对象(锁的对象是需要变化的,增删改操作)
synchronized () {
//监视对象
}
package thread;
import java.util.ArrayList;
import java.util.List;
public class UnsafeList {
public static void main(String[] args) throws InterruptedException {
List<String> list = new ArrayList<>();
for (int i = 0; i < 10000; i++) {
//lambda表达式,简易代码
new Thread(()->{
//给他加上锁
synchronized (list) {
list.add(Thread.currentThread().getName());
}
}).start();
}
//放大效果
Thread.sleep(3000);
System.out.println(list.size());
}
}
线程死锁
多个线程各自占有一些共享资源,并且互相等待其他线程占有的资源才能运行,而导致两个或者多个线程都在等待对方释放资源,都停止执行的情形,某一个同步块同时拥有两个以上对象的锁,就可能发生死锁问题。
package thread;
//死锁:多个线程互相抱着对方需要的资源,然后形成僵持
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 girName;
Makeup(int choice,String girName){
this.choice = choice;
this.girName = girName;
}
@Override
public void run() {
//化妆
try {
makeup();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//互相持有对方的锁,就是需要拿到对方的资源
private void makeup() throws InterruptedException {
if (choice==0){
synchronized (lipstick){//获得口红的锁
System.out.println(this.girName+"获得口红的锁");
Thread.sleep(1000);
synchronized (mirror){//一秒钟后想获得镜子
System.out.println(this.girName+"获得镜子的锁");
}
}
}else {
synchronized (mirror) {//获得镜子的锁
System.out.println(this.girName + "获得镜子的锁");
Thread.sleep(2000);
synchronized (lipstick) {//二秒钟后想获得口红
System.out.println(this.girName + "获得口红的锁");
}
}
}
}
}
把第二个需要等待后的代码提出前一个锁,即可正常运行
//互相持有对方的锁,就是需要拿到对方的资源
private void makeup() throws InterruptedException {
if (choice==0){
synchronized (lipstick){//获得口红的锁
System.out.println(this.girName+"获得口红的锁");
Thread.sleep(1000);
}
synchronized (mirror){//一秒钟后想获得镜子
System.out.println(this.girName+"获得镜子的锁");
}
}else {
synchronized (mirror) {//获得镜子的锁
System.out.println(this.girName + "获得镜子的锁");
Thread.sleep(2000);
}synchronized (lipstick) {//二秒钟后想获得口红
System.out.println(this.girName + "获得口红的锁");
}
}
}
Lock
ReentrantLock:可重复锁
class A{
private final ReentrantLock lock = new ReentrantLock();//1.定义ReentrantLock
public void m(){
lock.lock();//2.加锁
try{
//需要保证线程安全的代码;
}
finally{
lock.unlock();//3.释放锁
}
}
}
synchronized与Lock对比
- Lock是显示锁(需要手动开启和关闭),synchronized是隐式锁,跳出作用域自动释放
- Lock只有代码块,synchronized有代码块和方法锁
- 使用Lock锁,JVM将花费较少的时间来调度线程(这一点比synchronized好一些)
- 优先使用顺序:
- Lock->同步代码块->同步方法
线程协作
生产者和消费者模式
只是一个问题,不是23种设计模式之一。
线程池
lock.unlock();//3.释放锁
}
}
}
==synchronized与Lock对比==
- Lock是显示锁(需要手动开启和关闭),synchronized是隐式锁,跳出作用域自动释放
- Lock只有代码块,synchronized有代码块和方法锁
- 使用Lock锁,JVM将花费较少的时间来调度线程(这一点比synchronized好一些)
- 优先使用顺序:
- Lock->同步代码块->同步方法