JAVA多线程学习(二)
一、synchronized关键字
上文的那张图里有个synchronized,现在来讲讲这个关键字。
synchronized是Java中的关键字,是一种同步锁。它修饰的对象有以下几种:
1. 修饰一个代码块,被修饰的代码块称为同步语句块,其作用的范围是大括号{}括起来的代码,作用的对象是调用这个代码块的对象;
2. 修饰一个方法,被修饰的方法称为同步方法,其作用的范围是整个方法,作用的对象是调用这个方法的对象;
3. 修饰一个静态的方法,其作用的范围是整个静态方法,作用的对象是这个类的所有对象;
4. 修饰一个类,其作用的范围是synchronized后面括号括起来的部分,作用主的对象是这个类的所有对象。
修饰一个代码块:一个线程访问一个对象中的synchronized(this)同步代码块时,其他试图访问该对象的线程将被阻塞
public class MyThread extends Thread { public MyThread(String name){ super(name); } @Override public void run() { synchronized(MyThread.class){ for (int i = 0; i <5; i++) { System.out.println(getName()+":"+i); } } } } public class MyThreadDemo { public static void main(String[] args) { MyThread my1=new MyThread("令狐冲"); MyThread my2=new MyThread("任盈盈"); my1.start(); my2.start(); } } 令狐冲:0 令狐冲:1 令狐冲:2 令狐冲:3 令狐冲:4 任盈盈:0 任盈盈:1 任盈盈:2 任盈盈:3 任盈盈:4
两个并发线程访问同一个对象中的同步代码快时,同一时刻只能只有一个线程得到执行,另一个线程被阻塞,必须等到当前线程执行完同步代码块的内容后才会释放锁,第二个线程才能执行并锁定该对象。此外,run()方法假如还有其他非同步代码块,两个线程是可以互相争斗执行权的。
public class MyThread extends Thread { public MyThread(String name){ super(name); } @Override public void run() { synchronized(MyThread.class){ for (int i = 0; i <5; i++) { System.out.println(getName()+":"+i); } } System.out.println(Thread.currentThread().getName()+":这是非同步代码"); } } 令狐冲:0 令狐冲:1 令狐冲:2 令狐冲:3 令狐冲:4 任盈盈:0 令狐冲:这是非同步代码 任盈盈:1 任盈盈:2 任盈盈:3 任盈盈:4 任盈盈:这是非同步代码
修饰一个方法:Synchronized修饰一个方法很简单,就是在方法的前面加synchronized,public synchronized void method(){//todo}; synchronized修饰方法和修饰一个代码块类似,只是作用范围不一样,修饰代码块是大括号括起来的范围,而修饰方法范围是整个函数。
public synchronized void method(){ // todo }
public void method(){ synchronized(this) { // todo } }
修饰一个静态方法:静态方法是属于类的而不属于对象的。同样的,synchronized修饰的静态方法锁定的是这个类的所有对象
public synchronized static void method() { // todo }
修饰一个类:
class ClassName { public void method() { synchronized(ClassName.class) { // todo } } }
总结:
1. 无论synchronized关键字加在方法上还是对象上,如果它作用的对象是非静态的,则它取得的锁是对象;如果synchronized作用的对象是一个静态方法或一个类,则它取得的锁是对类,该类所有的对象同一把锁。
2. 每个对象只有一个锁(lock)与之相关联,谁拿到这个锁谁就可以运行它所控制的那段代码。
3. 实现同步是要很大的系统开销作为代价的,甚至可能造成死锁,所以尽量避免无谓的同步控制。
二、等待线程和唤醒线程
学习了synchronized关键字,我们回过头来看wait(). 让当前线程进入等待状态,同时,wait()会让当前线程释放它所持有的锁。它一般跟notify()或 notifyAll() 方法配合使用。
public class ThreadWait extends Thread { public ThreadWait(String name){ super(name); } @Override public void run() { synchronized(this){ try { //使当前线阻塞 1 s,确保主程序的 t1.wait(); 执行之后再执行 notify() Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName()+":打篮球"); this.notify(); } } public class ThreadWaitDemo { public static void main(String[] args) { ThreadWait t1=new ThreadWait("姚明"); synchronized(t1){ try { //启动线程 System.out.println(Thread.currentThread().getName()+"线程启动。。"); t1.start(); //主线程等待t1通过notify()唤醒。 System.out.println(Thread.currentThread().getName()+"线程等待。。"); t1.wait();//不是使t1线程等待,而是当前执行wait的线程等待 System.out.println(Thread.currentThread().getName()+"线程继续执行。。"); } catch (InterruptedException e) { e.printStackTrace(); } } } } 测试结果: main线程启动。。 main线程等待。。 姚明:打篮球 main线程继续执行。。
sleep()与wait()的区别:
1,sleep方法是Thread类的静态方法,wait()是Object超类的成员方法。
2,sleep()方法导致了程序暂停执行指定的时间,让出cpu该其他线程,但是他的监控状态依然保持者,当指定的时间到了又会自动恢复运行状态。在调用sleep()方法的过程中,线程不会释放对象锁。而当调用wait()方法的时候,线程会放弃对象锁,进入等待此对象的等待锁定池,只有针对此对象调用notify()方法后本线程才进入对象锁定池准备
3,sleep方法需要抛异常,wait方法不需要。
public static void sleep(long millis) throws InterruptedException
参数:millis- 以毫秒为单位的休眠时间。
抛出:InterruptedException- 如果任何线程中断了当前线程。当抛出该异常时,当前线程的中断状态 被清除。
Thread类中sleep方法就已经进行了抛异常处理。
三、Lock锁
Lock锁是JDK5出现的针对线程的锁定操作和释放操作的接口。synchronized同步代码块,同步方法属于隐式锁;而Lock锁需要通过lock()方式上锁,必须通过unlock()方式进行释放锁,所以称之为显式锁。
//电影院买票 public class ThreadTicket implements Runnable{ private int ticket=10; //创建锁对象 private Lock lock=new ReentrantLock(); @Override
public void run() {
while(true){
lock.lock();//加锁
try{
if(ticket>0){
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"正在出售第"+(ticket--)+"张票");
}
}finally{
lock.unlock();//释放锁
}
}
}
} public class TicketDemo { public static void main(String[] args) { ThreadTicket tt=new ThreadTicket(); Thread t1=new Thread(tt,"窗口一"); Thread t2=new Thread(tt,"窗口二"); Thread t3=new Thread(tt,"窗口三"); t1.start(); t2.start(); t3.start(); } } 测试结果: 窗口一正在出售第10张票 窗口一正在出售第9张票 窗口二正在出售第8张票 窗口二正在出售第7张票 窗口二正在出售第6张票 窗口二正在出售第5张票 窗口二正在出售第4张票 窗口二正在出售第3张票 窗口三正在出售第2张票 窗口三正在出售第1张票
四、死锁问题
线程同步解决了线程的安全问题,但是每次访问资源都会去判断锁,降低了效率,同时还会发生死锁问题。
死锁就是两个或两个以上的线程在争夺资源的过程中,发生的一种相互等待的现象。
public class ThreadDied extends Thread { //定义两个锁对象 private static Object A=new Object(); private static Object B=new Object(); private boolean flag; public ThreadDied(boolean flag) { super(); this.flag = flag; } @Override public void run() { if(flag){ synchronized(A){ System.out.println("农夫A要喝酒才有力气种粮食"); synchronized(B){ System.out.println("农夫A有粮食了"); } } }else{ synchronized(B){ System.out.println("农夫B有粮食才能酿酒"); synchronized(A){ System.out.println("农夫B酿酒了"); } } } } } public class ThreadDiedDemo { public static void main(String[] args) { ThreadDied t1=new ThreadDied(true); ThreadDied t2=new ThreadDied(false); t1.start(); t2.start(); } } 测试结果: 农夫A要喝酒才有力气种粮食 农夫B有粮食才能酿酒

浙公网安备 33010602011771号