Java多线程学习06
21/04/07
线程同步
- 并发:同一个对象被多个线程同时操作
- 处理多线程问题时,多个线程访问同一个对象,并且某些线程还想修改这个对象,这时候我们就需要线程同步。线程同步其实是一种等待机制,多个需要同时访问此对象的线程进入这个对象的等待池形成队列,等待前边线程使用完毕,下一个线程再使用。
队列和锁
- 由于同一个进程的多个线程共享同一块存储空间,在带来方便的同时,也带来了访问冲突问题,为了保证数据在方法中被访问时的正确性,在访问时加入了 锁机制 synchronized ,当一个线程获得对象的排它锁,独占资源,其他线程必须等待,使用后释放锁即可,存在以下问题:
- 一个线程持有锁会导致其他所有需要此锁的线程挂起;
- 在多线程竞争下,加锁,释放锁会导致比较多的上下文切换和调度延时,引起性能问题;
- 如果一个优先级高的线程等待一个优先级低的线程释放锁,会导致优先级倒置,引起性能问题。
同步方法
-
由于我们可以通过private关键字来保证数据对象只被方法访问,所以我们指需要针对方法提出一套机制,这套机制就是synchronized关键字,它包括两种方法:
synchronized方法和synchronized块
同步方法:public syncronized void methor(int args){}
-
synchronized方法控制对"对象"的访问,每个对象对应一把锁,每个synchronized方法都必须获得调用该方法的对象的锁才能执行,否则线程会阻塞,方法一旦执行,就独占该锁,直到该方法返回才释放锁,后面被阻塞的线程才能获得这个锁,继续执行
缺陷:若将一个大的方法申明为要syncronized将会影响效率
购票:
package com.syn;
//不安全购票
public class UnsafeBuyTickets {
public static void main(String[] args) {
BuyTickets station = new BuyTickets();
new Thread(station,"小红").start();
new Thread(station,"小明").start();
new Thread(station,"小王").start();
}
}
class BuyTickets implements Runnable{
private int ticketNums = 10;
boolean flag = true;//外部停止方式
@Override
public void run() {
//买票
while (flag){
try {
buy();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
//synchronized 同步方法
private synchronized void buy() throws InterruptedException {
//判断是否有票
if(ticketNums<=0){
flag = false;
return;
}else {
//买票
System.out.println(Thread.currentThread().getName()+"拿到了第"+ticketNums--+"张票");
}
//延时
Thread.sleep(100);
}
}
输出结果:
小红拿到了第10张票
小红拿到了第9张票
小红拿到了第8张票
小红拿到了第7张票
小红拿到了第6张票
小红拿到了第5张票
小强拿到了第4张票
小强拿到了第3张票
小强拿到了第2张票
小强拿到了第1张票
进程完成,退出码 0
取钱:
package com.syn;
/*
* 问题:两个线程同时访问临界资源时,出现的问题
* 实例问题:妻子、丈夫携主、副卡在同一个银行账户中取钱(两个线程访问临界资源对象)
*
* 妻子正在读卡...
* 验证成功!
* 丈夫正在读卡...
* 验证成功!
* 妻子取款成功!卡内余额为:0.0元
* 丈夫取款成功!卡内余额为:-200.0元
*
* 解决:为临界资源的原子操作加同步代码块(锁)synchronized
* 1.为取款方法加锁,因为取款就是原子操作,从插卡验证,到取款成功的一系列步骤,不可缺少或打乱
* 2.在取款方法内部加同步代码块,锁住的this即是当前账户的实例对象
* 3.为所有取出钞票的那一步原子操作加锁,包括妻子和丈夫
*
* 思路:如果丈夫先拿到锁,他就会先取钱,后台将取出钱后的余额修改数据,而妻子再取钱的时候,余额不足
* 抢到锁之后妻子在等锁,即是阻塞状态
*
* 注意:锁是随机的被线程抢到的!
*
* //什么场景下加锁?什么场景下不加锁?
* //写(增、删、改) 操作---> 加锁!
* //读操作 不加锁
*/
public class TestSynchronized {
public static void main(String[] args) {
//临界资源,只有一张银行卡
//临界资源对象只有一把锁
Account account = new Account("0001","123456",2000);
Thread husband = new Thread(new Husband(account),"丈夫");
Thread wife = new Thread(new Wife(account),"妻子");
wife.start();
husband.start();
}
}
class Account { //银行账户
String cardNo;
String passWord;
double balance;
public Account(String cardNo,String passWord,double balance){
this.cardNo = cardNo;
this.passWord = passWord;
this.balance = balance;
}
//取款(原子操作,从插卡验证,到取款成功的一系列步骤,不可缺少或打乱)
public synchronized void withdrawal(String cardNo,String passWord,double money){
synchronized (this){
System.out.println(Thread.currentThread().getName()+"正在读卡...");
//(原子操作,从插卡验证,到取款成功的一系列步骤,不可缺少或打乱)
if (this.cardNo.equals(cardNo)&&this.passWord.equals(passWord)){
System.out.println("验证成功!");
if (money<=this.balance){
//模拟现实世界,正在数钞
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
this.balance -= money;//取出
System.out.println(Thread.currentThread().getName()+"取款成功,卡内余额:"+this.balance+"元");
}else {
System.out.println(Thread.currentThread().getName()+"卡内余额不足!余额:"+balance+"元");
}
}else {
System.out.println("卡号或密码不正确!");
}
}
}
}
class Husband implements Runnable{
Account account;
public Husband(Account account){
this.account = account;
}
//线程任务
@Override
public void run() {
synchronized (account){
this.account.withdrawal("0001","123456",500);//原子操作
}
}
}
class Wife implements Runnable{
Account account;
public Wife(Account account){
this.account = account;
}
//线程任务
@Override
public void run() {
synchronized (account){
this.account.withdrawal("0001","123456",2000);//原子操作
}
}
}
输出结果:
妻子正在读卡...
验证成功!
妻子取款成功,卡内余额:0.0元
丈夫正在读卡...
验证成功!
丈夫卡内余额不足!余额:0.0元
进程完成,退出码 0
JUC:
package com.syn;
import java.util.concurrent.CopyOnWriteArrayList;
public class TestJUC {
public static void main(String[] args) {
CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<String>();
for (int i = 0; i < 10000; i++) {
new Thread(()->{
list.add(Thread.currentThread().getName());
}).start();
}
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(list.size());
}
}
输出结果:
10000
进程完成,退出码 0
死锁问题:
- 多个线程各自占有一些资源,并且互相等待其他线程占有的资源才能运行,而导致两个或者多个线程都在等待对方释放资源,都停止执行的情形。某一同步块同时拥有”两个以上的锁“时,就可能发生”死锁“问题。
package com.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 girlName;
Makeup(int choice,String girlName){
this.choice = choice;
this.girlName = girlName;
}
@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.girlName+"获得了口红的锁");
Thread.sleep(1000);
synchronized (mirror){
System.out.println(this.girlName+"获得了镜子的锁");
}
}
}else {
synchronized (mirror){
System.out.println(this.girlName+"获得了镜子的锁");
Thread.sleep(2000);
synchronized (lipstick){
System.out.println(this.girlName+"获得了口红的锁");
}
}
}
}
}
输出结果:
灰姑娘获得了口红的锁
白雪公主获得了镜子的锁
//程序卡住,两个线程僵持住
更改为:
private void makeup() throws InterruptedException {
if (choice==0){
synchronized (lipstick){
System.out.println(this.girlName+"获得了口红的锁");
Thread.sleep(1000);
}synchronized (mirror){
System.out.println(this.girlName+"获得了镜子的锁");
}
}else {
synchronized (mirror){
System.out.println(this.girlName+"获得了镜子的锁");
Thread.sleep(2000);
}synchronized (lipstick){
System.out.println(this.girlName+"获得了口红的锁");
}
}
}
输出结果:
灰姑娘获得了口红的锁
白雪公主获得了镜子的锁
白雪公主获得了口红的锁
灰姑娘获得了镜子的锁
进程完成,退出码 0
-
产生死锁的四个必要条件:
- 互斥条件:一个资源每次只能被一个进程使用
- 请求与保持条件:一个进程因请求资源而阻塞时,对以获得的资源保持不放。
- 不剥夺条件:进程已获得的资源,在未使用完之前,不能强行剥夺。
- 循环条件:若干进程之间形成一种头尾相接的循环等待资源关系。
上面列出了死锁的四个必要条件,我们只要想办法破其中的任意一个或多个条件就可以避免死锁发生
Lock锁
package com.syn;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class TestLock {
public static void main(String[] args) {
Test test = new Test();
new Thread(test).start();
new Thread(test).start();
new Thread(test).start();
}
}
class Test implements Runnable{
int ticketNums = 10;
//定义一个Lock锁
private final ReentrantLock lock = new ReentrantLock();
@Override
public void run() {
while (true){
try {
lock.lock(); //加锁
if (ticketNums<=0){
break;
}else {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(ticketNums--);
}
}finally {
lock.unlock(); //解锁
}
}
}
}
输出结果:
10
9
8
7
6
5
4
3
2
1
进程完成,退出码 0