多线程安全及线程池

1、线程安全与非线程安全:

线程安全:多线程访问时,采用了加锁机制,当有某个线程操作类时,其他对象不能对此类进行操作,直到该线程使用完成其他线程才能对此类进行操作,不会出现数据不一致,或者数据污染问题;

线程不安全:就是不提供数据访问的保护,任何线程任何时候都能进行操作访问,导致多个线程可以同时操作同一个对象,会出现数据污染与数据不一致问题;

例如:我们去银行取钱,你有存折,你老婆有银行卡,同时在不同地方取钱,现在账户里有100块钱:

线程不安全:
两个人可以同时操作账户,你操作账户取了10元,还剩90;
你媳妇获得账户操作的时候账户上也是100元,同时也取了10元,最后账
户还剩90元,你媳妇操作慢点,最后出来账户还剩90元;
线程安全:
你操作账户时100元,媳妇操作时也是100元,系统随机先处理你请求,将账
户 被锁了,媳妇的请求不能操作,账户变成了90,当媳妇操作时获取账户上钱数是90,又取了10元,账户上就剩了80元;

2、synchronized

模拟两个账户同时取钱:

public class Account {
    //账号
	private String accountNo;
	
	//账户余额
	private double balance;
	//construnct function
	public Account(String accountNo, double balance) {
		super();
		this.accountNo = accountNo;
		this.balance = balance;
	}

	//---------setter和getter的方法------
	public String getAccountNo() {
		return accountNo;
	}

	

	public void setAccountNo(String accountNo) {
		this.accountNo = accountNo;
	}

	public double getBalance() {
		return balance;
	}

	public void setBalance(double balance) {
		this.balance = balance;
	}
	//---------setter和getter的方法------
}

public class DrawThread extends Thread {

	 private Account account;
	 
	 private double drawAmount;

	public DrawThread(Account account, double drawAmount) {
		super();
		this.account = account;
		this.drawAmount = drawAmount;
	}
	
	@Override
	public void run() {
		/*if there are many threads operate this account ,these threads
		should share this account's resource ,we use synchronized to lock
		the account resorce only one thread can operate this account,when
		it finishes operation ,it should quit and release this account 
		*/
		synchronized (account) {
			if(account.getBalance()>drawAmount) {
		    	   System.out.println("you have draw "+drawAmount+" money!");
		    	   try {
					Thread.sleep(1);
				} catch (Exception e) {
					e.printStackTrace();
				}
		    	   //modify balance
		    	   account.setBalance(account.getBalance()-drawAmount);
		    	   System.out.println("now you account still has :"+account.getBalance());
		       }else {
		    	   System.out.println("this account has no enough money!");
		       }	
		}
	}
}
public class DrawTest {

	public static void main(String[] args) {
		Account account=new Account("123456",1000);
		new DrawThread(account, 800).start();
		new DrawThread(account, 800).start();
		
	}
}
you have draw 800.0 money!
now you account still has :200.0
this account has no enough money!

3、Lock

public class AccountLock {
	//定义锁对象
	private final ReentrantLock lock=new ReentrantLock();
	
    //账号
	private String accountNo;
	
	//账户余额
	private double balance;
	//construnct function
	public AccountLock(String accountNo, double balance) {
		super();
		this.accountNo = accountNo;
		this.balance = balance;
	}

	//---------setter和getter的方法------
	public String getAccountNo() {
		return accountNo;
	}

	

	public void setAccountNo(String accountNo) {
		this.accountNo = accountNo;
	}

	public double getBalance() {
		return balance;
	}

	public void setBalance(double balance) {
		this.balance = balance;
	}
	//---------setter和getter的方法------
	
	public void draw(double drawAmount) {
		 lock.lock();
		 System.out.println("add lock to this account and other threads cannot modify this account!");
		 try {
			 if(balance>drawAmount) {
		    	   System.out.println("you have draw "+drawAmount+" money!");
		    	   try {
					Thread.sleep(1);
				} catch (Exception e) {
					e.printStackTrace();
				}
		    	   //modify balance
		    	  setBalance(balance-drawAmount);
		    	   System.out.println("now you account still has :"+balance);
		       }else {
		    	   System.out.println("this account has no enough money!");
		       }
		} catch (Exception e) {
			e.printStackTrace();
		}finally {
			lock.unlock();
			System.out.println("unlock this account!");
		}
	}
	
}

4、比较:
1、Synchronized是关键字,而Lock是一个类;
2、Synchronized在执行完毕或者出现异常是才会释放锁,Lock是不会主动的释放锁,只有在finally中调用方法unlock才能释放锁;
3、Synchronized不能判断是否获得锁,Lock可以调用可以判断是否获得锁;
4、Synchronized如果有两个线程
线程1、线程2 线程1获得锁的话线程2会一直等待直到获得锁
lock 如果线程2尝试获得锁,如果得不到的话就会放弃
5、Lock锁适合大量同步的代码的同步问题,synchronized锁适合代码少量的同步问题。

5、线程池

public class ThreadPoolTest {

	public static void main(String[] args) {
		ExecutorService pool = Executors.newFixedThreadPool(6);
		Runnable target=()->{
			for(int i=0;i<10;i++) {
				System.out.println(Thread.currentThread().getName()+" 的i的值是:"+i);
			}
		};
		pool.submit(target);
		pool.submit(target);
		pool.shutdown();
	}
}
使用步骤:
1、调用Executors的静态工厂方法创建一个ExecutorService 对象,该对象表示一个线程池;
2、创建一个Runnable或者Callable实例,作为线程的执行任务;
3、使用ExecutorService 的submit的提交方法提交Runnable或者Callable实例;
4、使用shutdown()关闭线程池,使用完毕后;

线程池有哪些:
newFixedThreadPool
newCachedThreadPool
newSingleThreadPool
newScheduledThreadPool
newSingleThreadScheduledExecutor

6、ThreadLocal和Collections的包装线程不安全方法

Collections

public class CollectionsTest {
    public static void main(String[] args) {
    	
		List list =Arrays.asList("E","F","G");
		List synchronizedList = Collections.synchronizedList(list);
		Collection collection = Collections.synchronizedCollection(list);
		Map map =new HashMap();
		map.put("1", "111");
		map.put("2", "222");
		Map synchronizedMap = Collections.synchronizedMap(map);
		Set set=new HashSet();
		set.add("333");
		Set synchronizedSet = Collections.synchronizedSet(set);
	}
	
}
 包装线程不安全Collcetions提供了多个方法:
     synchronizedCollection()
     synchronizedList
     synchronizedSet
     synchronizedMap

ThreadLocal 的使用方法:
ThreadLocal 是指如果有多个线程每个线程都会有线程变量的副本,且线程能对本线程的变量进行修改


public class ThreadLocalTest {

	private ThreadLocal<Integer> codeNum=new ThreadLocal<Integer>() {
		//给每个线程初始化一个值0
		  protected Integer initialValue() {
			  return 0;
		  };
	};
	
	public void creaseNum() {
		codeNum.set(codeNum.get()+1);
	}
	
	public Integer getNum() {
		creaseNum();
		return codeNum.get();
	}
	
	public static void main(String[] args) {
		ThreadLocalTest test =new ThreadLocalTest();
		test.new TestThread(test).start();
		test.new TestThread(test).start();
		
	}
	
	
	class TestThread extends Thread{
		private  ThreadLocalTest test;
		
		public TestThread(ThreadLocalTest test) {
			this.test = test;
		}

		@Override
		public void run() {
			for(int i=0;i<3;i++) {
				System.out.println(Thread.currentThread().getName()+" ----->"+"now num is "+test.getNum());	
			}
		}
	}
}
我们发现每个线程都共享同一个codeNum实例,但它们并没有发生相互干扰的情况,
而是各自产生独立的codeNum,这是因为我们通过ThreadLocal为每一个线程提供了单独的副本。
发布了15 篇原创文章 · 获赞 7 · 访问量 2853

猜你喜欢

转载自blog.csdn.net/xiaocaodeshengri/article/details/100879745