一、线程的安全性问题
以下拿三个窗口卖100张票的例子讲述线程安全的问题,代码如下:
package security;
import java.util.concurrent.locks.ReentrantLock;
class Number implements Runnable{
//设置票的总数
int ticket = 100;
//1.Lock的实例化
private ReentrantLock lock = new ReentrantLock();
@Override
public void run() {
//使用同步代码块实现的线程安全问题
// while (true){
// synchronized(this){
// if (ticket > 0){
// try {
// Thread.sleep(10);
// } catch (InterruptedException e) {
// e.printStackTrace();
// }
// System.out.println(Thread.currentThread().getName()+":"+ticket);
// ticket--;
// }else{
// break;
// }
// }
// }
while (true){
try {
// 上锁;
lock.lock();
if (ticket > 0){
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+":"+ticket);
ticket--;
}else{
break;
}
}finally {
//解锁;
lock.unlock();
}
}
}
}
public class WindowTest1 {
public static void main(String[] args) {
Number ticket = new Number();
Thread t1 = new Thread(ticket);
Thread t2 = new Thread(ticket);
Thread t3 = new Thread(ticket);
t1.setName("窗口一");
t2.setName("窗口二");
t3.setName("窗口三");
t1.start();
t2.start();
t3.start();
}
}
方法一:同步代码块:
synchronized(同步监视器){
要被同步的代码块
}
说明:
1.共享数据:多个线程共同操作的数据
2.需要被同步的代码块:即为操作共享数据的代码;
注意:实际操作时,既不能包少了,也不能包少了。
3.同步监视器,俗称锁。任何一个类的对像,都可以纯当同步监视器。
但是要求同步监视器必须是多个线程共用的对象。
方法二:同步方法:
如果操作的共享数据的代码,完整的声明在相应的方法中。则我们可以考虑使用同步方法。
非静态的同步方法默认的是this(这里需要注意的就是一定要注意锁是不是线程之间所共有的)
静态的同步方法默认的是当前类本身,也是不能修改
比较synchronized和 Lock的异同?
相同点:都解决了线程的安全问题
不同点:synchronized需要指定同步监视器,保证此同步监视器的唯一性。
synchronized不管是同步方法还是同步代码块,在出了对应的大括号后,都会自动释放锁。
Lock的实例必须保证唯一性。
Lock不会自动释放锁,需要实例调用unLock()方法,才会释放锁
总结:Lock优于同步代码块 同步代码块优于同步方法。 开发的时候用的同步代码块多一些,学习Lock是为了面试
二、线程间的通信
线程间的通信说白了就是三个方法的应用:
- 1.wait():一旦调用此方法后,当前线程进入阻塞状态,需要被唤醒
- 2.notify():程序一旦执行此方法后,就会唤醒一个被阻塞的线程。
- 3.notifyAll():程序一旦执行此方法后,就会唤醒所有被阻塞的线程。
说明:
1.wait()/notify()/notifyAll()这三个方法必须用在同步代码块或者同步方法中。 2.此wait()/notify()/notifyAll()的调用者必须是同步监视器,否则爆出异常java.lang.IllegalMonitorStateException。
sleep()和wait()方法的异同:
相同点:都能使得当前线程进入阻塞状态。
不同点:
1.结束阻塞的方式不同:wait需要进行唤醒,而sleep自动结束阻塞。
2. 它俩定义的类不同:sleep()在Thread中,而wait()在Object中;
3. 会不会释放同步监视器:wait()会,而sleep()不会;
4. wait():使用在同步代码块或者同步方法中,sleep():没有特殊的限定范围;
以下是线程间的通信的例子,代码如下:
//实现A、B两个线程交替打印100个数
public class CommunicationTest {
public static void main(String[] args) {
NumPPrint numPPrint = new NumPPrint();
Thread A = new Thread(numPPrint);
Thread B = new Thread(numPPrint);
A.setName("线程A");
B.setName("线程B");
A.start();
B.start();
}
}
class NumPPrint implements Runnable{
Object object = new Object();
int number = 1;
@Override
public void run() {
while (true){
synchronized (object){
object.notify(); //将被wait的线程唤醒
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (number < 101 ){
System.out.println(Thread.currentThread().getName()+":"+number);
number++;
}else{
break;
}
try {
object.wait();//使得当前线程进入阻塞状态,并且释放同步监视器
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
三、死锁的问题
死锁:不同的线程分别占用对方所需要的资源不放弃,都在等待对方的同步资源,就形成了死锁。
解决的办法:
专门的算法、原则;
尽量减少同步资源的定义;
尽量避免嵌套同步。
以下是形成死锁的实例,代码如下:
package security2;
public class DeadLockTest {
public static void main(String[] args) {
StringBuilder s1 = new StringBuilder();
StringBuilder s2 = new StringBuilder();
new Thread(){
@Override
public void run() {
synchronized (s1){
s1.append("a");
s2.append("1");
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (s2){
s1.append("b");
s2.append("2");
System.out.println(s1);
System.out.println(s2);
}
}
}
}.start();
new Thread(){
@Override
public void run() {
synchronized (s2){
s1.append("c");
s2.append("3");
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (s1){
s1.append("d");
s2.append("4");
System.out.println(s1);
System.out.println(s2);
}
}
}
}.start();
}
}