标签:win 必须 集合 一个 rup pac raw 异常 锁定
并发
并发是指同一个对象被多个线程同时操作。
上面的两个例子会导致线程不安全
线程不安全测试
买票
public class Test {
public static void main(String[] args) {
//一份资源
MyThread myThread=new MyThread();
//多个代理
new Thread(myThread,"李斯").start();
new Thread(myThread,"张良").start();
new Thread(myThread,"韩非").start();
}
}
class MyThread implements Runnable{
private int num=10;//剩余的火车票数
boolean flag=true;
public void test(){
if(num<=0){
flag=false;
return;
}
//模拟网络延时
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"-->"+num--);
}
@Override
public void run() {
while (flag){
test();
}
}
}
结果
韩非-->10
张良-->9
李斯-->8
韩非-->7
张良-->6
李斯-->5
韩非-->4
李斯-->3
张良-->2
韩非-->1
李斯-->0
张良-->-1
结果分析
上面的结果出现了负数:
Thread.sleep(200);
假设出现abc三个人,当只有一张票的时候,a最先获得时间片,抢到了1,b刚进入的时候1还没又来得及修改,因此继续使用资源,但经过sleep()后获得的资源已被修改变成了0,以此类推c只能抢到-1
第二次运行
李斯-->10
韩非-->10
张良-->9
张良-->8
李斯-->7
韩非-->6
韩非-->5
李斯-->4
张良-->3
韩非-->2
李斯-->1
张良-->1
韩非-->0
张良-->-1
结果分析:
上面的结果出现了相同的数10
开辟多线程后多线程都有自己的工作空间,ABC都有自己的工作空间,这些空间都与主内存进行交换。
A从主内存将10拷贝过来后,10还没来得及修改(-1的操作)就被B从主内存拷贝到了自己的内存空间,所以就导致了两个人抢到了同一张票。
上面两种情况就导致了线程不安全。
线程不安全二
集合示例:
public class Test {
public static void main(String[] args) {
List<String> list=new ArrayList<>();
for (int i = 0; i < 1000; i++) {
new Thread(()->{
list.add(Thread.currentThread().getName());
}).start();
}
System.out.println(list.size());
}
}
结果
554
分析:实际长度本应该是1000,但是这次运行的运行结果显示的实际长度却是554,
这是因为线程在运行的时候发生了覆盖,如当线程的名相同的时候,就可以覆盖前面那个同名的线程。
所以此处的测试也导致了线程不安全。
提示
1. 不是所有的线程都需要线程安全
2. 通常情况下,读不需要线程安全,发生改的操作就需要线程安全,又读又改需要线程安全。
线程不安全测试三
模拟父亲和儿子同时取一张卡的钱
public class Test {
public static void main(String[] args) {
//账户
Account account=new Account(100,"生活费");
Drawing you=new Drawing(account,80,"儿子");
Drawing rent=new Drawing(account,90,"父亲");
you.start();
rent.start();
}
}
//模拟取款
class Drawing extends Thread {
Account account;//取钱的账户
int drawingMoney;//取钱数
int packetTotal;//身上还有多少钱
public Drawing(Account account,int drawingMoney,String name){
super(name);// thread线程名字
this.account=account;
this.drawingMoney=drawingMoney;
}
@Override
public void run() {
if(account.money-drawingMoney<0){
return;
}
//模拟取钱花费的时间
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
account.money-=drawingMoney;
packetTotal+=drawingMoney;
System.out.println("取钱后账户余额为:"+account.money);
System.out.println(this.getName()+"取了多少钱:"+packetTotal);
}
}
class Account{
int money;//金额
String name;//名称
public Account(int money, String name) {
this.money = money;
this.name = name;
}
}
结果
取钱后账户余额为:-70
父亲取了多少钱:90
取钱后账户余额为:-70
儿子取了多少钱:80
分析:经过Thread.sleep(1000);后我们发现出现了线程不安全,当然睡眠不是造成线程不安全的原因,当多启动几个线程同时共享资源就算不使用睡眠也可能出现线程不安全。此处使用睡眠只是方便测试。在一个线程睡眠时,账户余额还没来得及变化,另一个线程访问没有来得及变化的余额满足了条件,最后执行完毕却使用了变化后的账户,使得最后账户变为了负数,造成了线程不安全。
锁
现时生活中,我们会面临一个资源,多个人都想使用的问题。解决方法当然就是排队。
处理多线程的时候,多个线程访问同一个对象,并且还需要修改这个对象,这时我们就需要用到线程同步
线程同步起始就是一种等待机制,多个需要访问同一个对象的线程进入对象的等待池形成队列。等待前面的线程使用完毕,下一个线程再使用。
使用资源的线程将资源锁定,此时只有它自己能够使用,直到结束,下一个再进行锁定,使用资源。
由于同一个进程的多个线程共享同一块存储空间,会造成访问冲突的问题,因此为了保护访问时的正确性,我们在访问的时候加入了锁机制
当线程获得了锁机制就能独占资源,其它线程必须等待。
锁机制带来的问题
锁一个具体的对象
成员方法锁this
静态方法锁class对象
锁会在两个地方出现:
a. 方法(同步方法):synchronized 方法
b. 块(同步块):synchronized 块
声明synchronized后会大大的影响效率。
synchronized方法
package com.dongjixue.test;
public class Test {
public static void main(String[] args) {
//一份资源
MyThread myThread=new MyThread();
//多个代理
new Thread(myThread,"李斯").start();
new Thread(myThread,"张良").start();
new Thread(myThread,"韩非").start();
}
}
class MyThread implements Runnable{
private int num=10;//剩余的火车票数
boolean flag=true;
//线程安全(同步)
//下面的同步方法可以改为同步块,参数传递this,这样做可以提升性能。
//(可以直接锁,因为num属于this.class)
public synchronized void test(){
if(num<=0){
flag=false;
return;
}
//模拟网络延时
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"-->"+num--);
}
@Override
public void run() {
while (flag){
test();
}
}
}
结果
李斯-->10
李斯-->9
韩非-->8
张良-->7
韩非-->6
李斯-->5
韩非-->4
张良-->3
韩非-->2
李斯-->1
分析:和线程不安全测试进行对比,加入锁和保证了数据的准确性,同时将使用的资源封装到test方法中,而不是将资源直接写到run方法里面(锁对地方),这样做的目的是尽可能的提高性能。
测试二 取钱
public class Test {
public static void main(String[] args) {
//账户
Account account=new Account(100,"生活费");
Drawing you=new Drawing(account,80,"儿子");
Drawing rent=new Drawing(account,90,"父亲");
you.start();
rent.start();
}
}
//模拟取款
class Drawing extends Thread {
Account account;//取钱的账户
int drawingMoney;//取钱数
int packetTotal;//身上还有多少钱
public Drawing(Account account,int drawingMoney,String name){
super(name);
this.account=account;
this.drawingMoney=drawingMoney;
}
@Override
public void run() {
test();
}
//此处的this是指Drawing.class
//而我们修改的是Account中的account,所以没有锁对对象。还是会出错
public synchronized void test(){
if(account.money-drawingMoney<0){
return;
}
//模拟取钱花费的时间
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
account.money-=drawingMoney;
packetTotal+=drawingMoney;
System.out.println("取钱后账户余额为:"+account.money);
System.out.println(this.getName()+"取了多少钱:"+packetTotal);
}
}
class Account{
int money;//金额
String name;//名称
public Account(int money, String name) {
this.money = money;
this.name = name;
}
}
结果
取钱后账户余额为:-70
取钱后账户余额为:-70
儿子取了多少钱:80
父亲取了多少钱:90
分析:结果还是出现了异常,这是因为没有锁对资源造成的。我们应该锁定的是Account的资源,而不是test这个方法。
synchronized块
取钱示例:
package com.dongjixue.test;
public class Test {
public static void main(String[] args) {
//账户
Account account=new Account(100,"生活费");
Drawing you=new Drawing(account,80,"儿子");
Drawing father=new Drawing(account,10,"父亲");
Drawing mother=new Drawing(account,100,"母亲");
you.start();
father.start();
mother.start();
}
}
//模拟取款
class Drawing extends Thread {
Account account;//取钱的账户
int drawingMoney;//取钱数
int packetTotal;//身上还有多少钱
public Drawing(Account account,int drawingMoney,String name){
super(name);
this.account=account;
this.drawingMoney=drawingMoney;
}
@Override
public void run() {
test();
}
public void test(){
//当账户没有钱就不进入同步块,直接结束方法,节约了资源,提升了性能
if (account.money<=0){
return;
}
//指定要锁的对象,这样就不会锁错对象
synchronized (account){
if(account.money-drawingMoney<0){
return;
}
//模拟取钱花费的时间
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
account.money-=drawingMoney;
packetTotal+=drawingMoney;
System.out.println("取钱后账户余额为:"+account.money);
System.out.println(this.getName()+"取了多少钱:"+packetTotal);
}
}
}
class Account{
int money;//金额
String name;//名称
public Account(int money, String name) {
this.money = money;
this.name = name;
}
}
结果
取钱后账户余额为:20
儿子取了多少钱:80
取钱后账户余额为:10
父亲取了多少钱:10
结果正确
提示:要锁对资源
测试二 集合
package com.dongjixue.test;
import java.util.ArrayList;
import java.util.List;
public class Test {
public static void main(String[] args) throws InterruptedException {
List<String> list=new ArrayList<>();
for (int i = 0; i < 1000; i++) {
new Thread(()->{
synchronized (list){
list.add(Thread.currentThread().getName());
}
}).start();
}
//延时5秒才执行下面的输出语句,不然当上面的线程还没执行完毕
//就执行到了下面的输出语句,也会造成结果不正确。
Thread.sleep(5000);
System.out.println(list.size());
}
}
结果:正确
1000
容器的自带锁(juc编程)
public class Test {
public static void main(String[] args) throws InterruptedException {
CopyOnWriteArrayList <String> list=new CopyOnWriteArrayList<>();
for (int i = 0; i < 1000; i++) {
new Thread(()->{
list.add(Thread.currentThread().getName());
}).start();
}
Thread.sleep(5000);
System.out.println(list.size());
}
}
结果
1000
死锁
示例:两个人同时吃一碗饭和喝一碗酒
public class Test {
public static void main(String[] args) {
People people=new People(0,"小红");
people.start();
People people2=new People(1,"小明");
people2.start();
}
}
//喝酒
class Drink{
}
//吃饭
class Eat{
}
class People extends Thread{
int choice; //选择喝酒还是吃饭
String name;
//static表示饭和酒只有一份,不写表示多份,有多份资源下面就不会产生死锁
static Eat eat=new Eat();
static Drink drink=new Drink();
public People(int choice, String name) {
this.choice = choice;
this.name = name;
}
@Override
public void run() {
//吃饭和喝酒
doWhat();
}
//先后持有对方的对象锁->可能造成死锁
private void doWhat(){
if(choice==0){
//获得喝酒的锁
synchronized (drink){
System.out.println(this.name+"喝酒");
//1秒后想获得吃饭的锁
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (eat){
System.out.println(this.name+"吃饭");
}
}
}else {
//获得吃饭的锁
synchronized (eat){
System.out.println(this.name+"吃饭");
//2秒后想获得喝酒的锁
try {
Thread.sleep(4000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (drink){
System.out.println(this.name+"喝酒");
}
}
}
}
}
结果
小红喝酒
小明吃饭
//分析结果:
小明吃饭后该喝酒,小红喝酒后该吃饭。但小明占据了饭这一份资源,小红占据了酒这一份资源。它们都想要等待对方释放资源,结果就造成了死锁。
修改示例
public class Test {
public static void main(String[] args) {
People people=new People(0,"小红");
people.start();
People people2=new People(1,"小明");
people2.start();
}
}
//喝酒
class Drink{
}
//吃饭
class Eat{
}
class People extends Thread{
//选择喝酒还是吃饭
int choice;
String name;
//static表示饭和酒只有一份
static Eat eat=new Eat();
static Drink drink=new Drink();
public People(int choice, String name) {
this.choice = choice;
this.name = name;
}
@Override
public void run() {
//吃饭和喝酒
doWhat();
}
//先后持有对方的对象锁->可能造成死锁
private void doWhat(){
if(choice==0){
//获得喝酒的锁
synchronized (drink) {
System.out.println(this.name + "喝酒");
//1秒后想获得吃饭的锁
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//喝完酒后,释放酒的锁再持有饭的锁就不会造成死锁
synchronized (eat){
System.out.println(this.name+"吃饭");
}
}else {
//获得吃饭的锁
synchronized (eat){
System.out.println(this.name+"吃饭");
//2秒后想获得喝酒的锁
try {
Thread.sleep(4000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
synchronized (drink){
System.out.println(this.name+"喝酒");
}
}
}
}
结果
小红喝酒
小明吃饭
小明喝酒
小红吃饭
分析:当我们将锁移除另一个锁,不要锁套索。结果就正确了,当它们用完资源就能够释放,让另一个线程(小红/小明)去使用它们各自释放的资源。
参考教材:尚学堂Java300集
标签:win 必须 集合 一个 rup pac raw 异常 锁定
原文地址:https://www.cnblogs.com/lanxinren/p/14678316.html