Java多线程(三)线程同步
Java多线程(三)
1.多线程同步
线程安全
之前售票的例子中,多线程共享tickets可能导致线程安全问题。
举例:
class SaleThread implements Runnable{
private int tickets=10;
public void run(){
while(true){
if(tickets>0){
try{
Thread.sleep(100);
}catch(InterruptedException e){
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"正在发售第"+tickets--+"张票");
}
}
}
}
public class Page369 {
public static void main(String[] args) {
SaleThread saleThread=new SaleThread();
new Thread(saleThread,"窗口1").start();
new Thread(saleThread,"窗口2").start();
new Thread(saleThread,"窗口3").start();
new Thread(saleThread,"窗口4").start();
}
}
错误原因: 在while循环中添加sleep()方法,这样就模拟了售票过程中的延迟。由于线程有延迟,当票号减为1,假设窗口2线程此时出售1号票,对票号进行判断后进入while循环,由于售票前通过sleep()模拟耗时,此时票号任为1,其他线程会进行售票,所以最终会出现0,-1,-2这样的票号。
同步代码块
线程安全问题是由于多个线程同时处理共享资源造成的,想要解决线程安全问题,必须保证处理共享资源的代码在任意时刻只能有一个线程访问。为此Java中提供了线程同步机制。将共享资源代码放置在一个使用synchronized关键字修饰的代码块中,这个代码块称为同步代码块。
语法:
synchronized(lock){
//操作共享资源代码块
}
举例:
class SaleThread2 implements Runnable{
private int tickets=10;
Object lock=new Object(); //定义任意一个对象作为同步代码块的锁
public void run(){
while(true){
//定义同步代码块
synchronized(lock){
if(tickets>0){
try{
Thread.sleep(100);
}catch(InterruptedException e){
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"正在发售第"+tickets--+"张票");
}
}
}
}
}
public class Page371 {
public static void main(String[] args) {
SaleThread2 saleThread2=new SaleThread2();
new Thread(saleThread2,"窗口1").start();
new Thread(saleThread2,"窗口2").start();
new Thread(saleThread2,"窗口3").start();
new Thread(saleThread2,"窗口4").start();
}
}
同步方法
把共享资源放在synchronized定义的区域内,便为这些操作都加了同步锁。在方法前也可以用synchronized修饰,被修饰的方法为同步方法,它能实现和同步代码块同样的功能。
举例:
class SaleThread3 implements Runnable {
private int tickets=10;
public void run(){
while(true){
saleTicket();
}
}
private synchronized void saleTicket(){
if(tickets>0){
try{
Thread.sleep(1000);
}catch(InterruptedException e){
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"正在发售第"+tickets--+"张票");
}
}
}
public class Page372 {
public static void main(String[] args) {
SaleThread3 saleThread=new SaleThread3();
new Thread(saleThread,"窗口1").start();
new Thread(saleThread,"窗口2").start();
new Thread(saleThread,"窗口3").start();
new Thread(saleThread,"窗口4").start();
}
}
同步锁
synchronized同步代码块和同步方法也有一些限制,比如它无法中断一个正在等候获得锁的线程,也无法通过轮询得到锁,如果不想等下去就没办法得到锁。从JDK5开始Java增加了一个功能更强大的Lock锁。Lock锁与synchronized隐式锁在功能上基本相同,但Lock锁可以让某个线程在持续获取同步锁失败后返回,不再继续等待,在使用时也更加灵活。
举例:
import java.util.concurrent.locks.*;
class LockThread implements Runnable{
private int tickets=10;
//定义一个Lock锁对象
private final Lock lock=new ReentrantLock();
public void run(){
while(true){
lock.lock(); //对代码块加锁
if(tickets>0){
try{
Thread.sleep(100);
System.out.println(Thread.currentThread().getName()+"正在发售第"+tickets--+"张票");
}catch(InterruptedException e){
e.printStackTrace();
}finally{
lock.unlock(); //执行完代码块后释放锁
}
}
}
}
}
public class Page374 {
public static void main(String[] args) {
LockThread lockThread=new LockThread();
new Thread(lockThread,"窗口1").start();
new Thread(lockThread,"窗口2").start();
new Thread(lockThread,"窗口3").start();
new Thread(lockThread,"窗口4").start();
}
}
通过Lock接口的实现类ReentrantLock来创建Lock锁对象,并通过Lock锁对象的lock()方法和unlock()方法对核心代码块进行上锁和解锁。
死锁问题
两个线程在运行时都在等待对方的锁,这样就导致程序停滞,称为死锁。
举例:
class DeadLockThread implements Runnable{
//定义两个不同的锁对象
static Object chopsticks=new Object();
static Object knifeAndFork=new Object();
private boolean flag;
DeadLockThread(boolean flag){
this.flag=flag;
}
public void run(){
if(flag){
while(true){
synchronized(chopsticks){
System.out.println(Thread.currentThread().getName()+"---if---chopsticks");
synchronized(knifeAndFork){
System.out.println(Thread.currentThread().getName()+"---if---knifeAndFork");
}
}
}
}else{
while(true){
synchronized(knifeAndFork){
System.out.println(Thread.currentThread().getName()+"---else---knifeAndFork");
synchronized (chopsticks){
System.out.println(Thread.currentThread().getName()+"---else---chopsticks");
}
}
}
}
}
}
public class Page376 {
public static void main(String[] args) {
DeadLockThread thread1=new DeadLockThread(true);
DeadLockThread thread2=new DeadLockThread(false);
new Thread(thread1,"Chinese").start();
new Thread(thread2,"American").start();
}
}
两个线程都需要对方所占用的锁,但是都无法释放自己所拥有锁。