一.多线程安全问题
承接上篇博客的内容多线程(一)线程创建,使用Runnable接口创建线程
此时,int类型的数据num是一个共享数据,被多个线程一起调用
//毕向东经典买票程序
class Ticket implements Runnable{
//1.num是多个线程共同使用的共享数据
private int num = 100;
public void run(){
//2.在run()方法内,有条执行语句
while(true){
if(num>0){
try{Thread.sleep(10);}catch (InterruptedException e){}
System.out.println(Thread.currentThread().getName()+".....sale...."+num--);
}
}
}
}
class TicketDemo{
public static void main(String[] args) {
Ticket t = new Ticket();
Thread t1 = new Thread(t);
Thread t2 = new Thread(t);
Thread t3 = new Thread(t);
Thread t4 = new Thread(t);
t1.start();
t2.start();
t3.start();
t4.start();
}
}
上述程序可能出现数据出错问题,即多线程安全问题
多线程操作导致数据出错的场景:如上图所示程序运行情况,主线程开启4个线程执行买票操作,tickets从100递减,当tickets的值为1时,可能会出现一种错误的数据处理:若线程二执行到tickets–(还未执行—操作)时进入wait( )等待状态,CPU切换到线程一,线程一此时执行if判断,通过,然后执行tickets–,tickets的值变为0,然后线程一进入wait( )状态,CPU切换到线程二执行tickets–,执行完后tickets的值为-1,发生数据异常(tickets的值应当大于等于0)
虽然这种情况发生的几率很小,但是一旦发生,就是非常严重的错误,而且这种错误不容易被发现
1.多线程安全问题产生的原因
一个线程在执行多条语句并运算同一个共享数据时,在执行过程中(未执行完所有操作语句)其他线程参与进来操作了这个共享数据。导致到了错误数据的产生。
###涉及到两个因素:
1,多个线程在操作共享数据
2,有多条语句对共享数据进行运算
2.解决多线程安全问题
将操作共享数据的语句在某一时段让一个线程执行直到所有语句执行完毕,在执行过程中,其他线程不能进来执行
可以使用同步代码块的方式将多条操作共享数据的代码进行同步封装
格式:
synchronized(对象) { //这个对象可以是任意对象,是同步的锁。
需要被同步的代码;
}
二.同步
优势:解决了线程安全问题。
弊端:降低部分性能(因为判断锁需要消耗资源),同时带来了死锁问题
1.定义同步的前提
1,必须要有两个或者两个以上的线程,才需要同步。
2,多个线程必须保证使用的是同一个锁。
2.同步的第二种表现形式—同步函数
//毕向东经典银行存钱程序
/**
*两个客户向银行存钱,没人每次存100,各存3次
*sum是共享数据,多个线程对sum操作可能使sum的值出错
*Bank类的add()方法中,有两步操作,1,sum累和。2,sum输出,正常情况下程序运行,应该输出sum=100至600,但是可能会出现输出重复值,比如输出两个300的情况
*错误分析:若线程一执行sum累和后sum值为200,该执行输出sum,但是CPU切换到线程二执行sum累和,执行完后sum值为300,然后CPU再次切换到线程一,输出sum=300(正常情况线程一应该输出sum=200),程序出错
*/
class Bank{
private int sum;
public void add(int num){
sum = sum + num;
try{Thread.sleep(10);}catch(InterruptedException e){}
System.out.println("sum="+sum);
}
}
class Cus implements Runnable{
private Bank b = new Bank();
public void run(){
for(int x=0; x<3; x++){
b.add(100);
}
}
}
class BankDemo {
public static void main(String[] args){
Cus c = new Cus();
Thread t1 = new Thread(c);
Thread t2 = new Thread(c);
t1.start();
t2.start();
}
}
###同步函数:
其实就是将同步关键字定义在函数上,让函数具备了同步性
使用同步函数后:
class Bank{
private int sum;
//同步函数
public synchronized void add(int num){
sum = sum + num;
try{Thread.sleep(10);}catch(InterruptedException e){}
System.out.println("sum="+sum);
}
}
class Cus implements Runnable{
private Bank b = new Bank();
public void run(){
for(int x=0; x<3; x++){
b.add(100);
}
}
}
class BankDemo {
public static void main(String[] args){
Cus c = new Cus();
Thread t1 = new Thread(c);
Thread t2 = new Thread(c);
t1.start();
t2.start();
}
}
###同步函数的锁:
验证同步函数锁的程序:
//毕向东经典测试同步函数锁程序
class Ticket implements Runnable{
private int num = 100;
boolean flag = true;
//实现run()方法
public void run(){
if(flag)
while(true){
//定义一个同步代码块,当flag=true时执行
synchronized(this){
if(num>0){
try{Thread.sleep(10);}catch (InterruptedException e){}
System.out.println(Thread.currentThread().getName()+".....obj...."+num--);
}
}
}
else
while(true)
//当flag=false时执行show()函数(同步函数)
this.show();
}
//定义一个同步函数,show()函数
public synchronized void show(){
if(num>0){
try{Thread.sleep(10);}catch (InterruptedException e){}
System.out.println(Thread.currentThread().getName()+".....function...."+num--);
}
}
}
class SynFunctionLockDemo
{
public static void main(String[] args)
{
Ticket t = new Ticket();
Thread t1 = new Thread(t);
Thread t2 = new Thread(t);
//定义两个线程,t1线程开启时flag=true,t1线程执行同步代码块中的内容
t1.start();
/**
*设置主线程休息10毫秒,让t1线程带着参数flag=true执行,如果不设置主线程休息,
*则主线程会一直执行下去,设置flag的值为false,如果t1线程没有及时开启,则t1线程执行时flag的值就会被被设置为false,
*也去执行同步函数中的内容了,达不到预期的两个线程分别执行同步函数和同步代码块的效果
*/
try{Thread.sleep(10);}catch(InterruptedException e){}
t.flag = false;
//t2线程开启时flag=false,t2线程执行同步函数中的内容
t2.start();
}
}
上述代码执行,如果多次执行后没有出现多线程安全问题,说明同步函数和同步代码块使用的是同一把锁,实现了买票代码的同步
通过验证,同步函数使用的锁就是this锁,即使用自己所在的对象this对象作为同步的锁
####当同步函数被static修饰时
静态函数在加载时所属于类,这时有可能还没有该类产生的对象,但是该类的字节码文件加载进内存就已经被封装成了字节码文件对象。而静态同步函数就是使用的这个字节码文件对象(类名.class)
3.同步代码块和同步函数的区别
###1.锁对象不同
同步代码块使用的锁可以是任意对象。
同步函数使用的锁是this,静态同步函数的锁是该类的字节码文件对象。
###2.适用场景不同
如果一个类中只有一个同步,可以使用同步函数
如果有多同步,必须使用同步代码块,来确定不同的锁。同步代码块相对灵活一些
引申:多线程在单例设计模式中的使用
###饿汉式:
class Single{
privatestatic Single s = new Single();
privateSingle(){}
publicstatic Single getInstance()
{
return s;
}
}
###懒汉式:
class Single {
privatestatic static Single s = null;
privateSingle(){}
publicstatic static Single getInstance() {
if(s==null)
s= new Single();
return s;
}
}
当多线程访问懒汉式时,因为懒汉式的方法内对共享数据(s)进行多条语句的操作(if判断和创建对象),所以容易出现线程安全问题。为了解决线程安全问题,加入同步机制
###懒汉式:(使用同步函数同步)
class Single {
privatestatic static Single s = null;
privateSingle(){}
publicstatic static synchronized Single getInstance() {
if(s==null)
s= new Single();
return s;
}
}
使用同步函数后程序的效率又降低了,因为当Single对象创建之后,如果其他对象调用单例程序,则需要两次判断(先判断锁,再进行一次if判断)
为解决效率降低问题,使用双重判断
###懒汉式:(使用同步代码块加双重判断)
class Single{
private static Single s = null;
private Single(){}
public static Single getInstance(){
if(s == null){
//锁是字节码文件对象
synchronized(Single.class){
if(s == null)
s = new Single();
}
}
return s;
}
}
三.死锁
发生在同步的嵌套中,同步函数中有同步代码块,同步代码块中有同步函数
/**
*死锁:常见情景之一:同步的嵌套。
*使用验证同步函数锁的程序稍加调整改为一个死锁程序
*/
class Ticket implements Runnable{
private int num = 100;
Object obj = new Object();
boolean flag = true;
public void run(){
//flag=ture时执行
if(flag)
while(true){
//先执行同步代码块,锁对象是obj
synchronized(obj){
//再执行同步函数,锁对象是一个Ticket类的对象(即show函数所在的对象)
show();
}
}
//flag=false时执行
else
while(true)
//先执行同步函数(this锁),再执行同步代码块(obj锁)
this.show();
}
//同步函数
public synchronized void show(){
//同步代码块
synchronized(obj){
if(num>0){
try{Thread.sleep(10);}catch (InterruptedException e){}
System.out.println(Thread.currentThread().getName()+".....sale...."+num--);
}
}
}
}
class DeadLockDemo
{
public static void main(String[] args)
{
Ticket t = new Ticket();
Thread t1 = new Thread(t);
Thread t2 = new Thread(t);
t1.start();
try{Thread.sleep(10);}catch(InterruptedException e){}
t.flag = false;
t2.start();
}
}
一个更为简单直接的死锁程序:
class Test implements Runnable{
private boolean flag;
Test(boolean flag){
this.flag = flag;
}
public void run(){
if(flag){
while(true)
synchronized(MyLock.locka){
System.out.println(Thread.currentThread().getName()+"..if locka....");
synchronized(MyLock.lockb) {
System.out.println(Thread.currentThread().getName()+"..if lockb....");
}
}
}
else{
while(true)
synchronized(MyLock.lockb){
System.out.println(Thread.currentThread().getName()+"..else lockb....");
synchronized(MyLock.locka){
System.out.println(Thread.currentThread().getName()+"..else locka....");
}
}
}
}
}
class MyLock{
public static final Object locka = new Object();
public static final Object lockb = new Object();
}
class DeadLockTest {
public static void main(String[] args) {
Test a = new Test(true);
Test b = new Test(false);
Thread t1 = new Thread(a);
Thread t2 = new Thread(b);
t1.start();
t2.start();
}
}