目录
线程通信的例子:使用两个线程打印1-100。线程1,线程2交替打印
一、解决线程安全的方式一:同步代码块
synchronized(同步监视器){ //需要被同步的代码 }
说明:1.操作共享数据的代码即为需要被同步的代码 2.共享数据:多个线程共同操作的变量。 3.同步监视器:俗称:锁。任何一个类的对象都可以充当锁。 要求:多个线程必须公用同一把锁 注意:在实现Runnable接口的方式中,我们可以考虑用this来充当同步监视器 在继承Thread类的方式中,慎用this来充当同步监视器,可以考虑用当前类来充当同步监视器
class WIns implements Runnable{
private int tacket = 100;
// Object obj = new Object();
@Override
public void run() {
while (true){
synchronized (this){//此时的this就是唯一的对象wIns
if(tacket > 0){
// sleep()是为了更大概率出现线程安全问题
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"卖出的票号是:" + tacket );
tacket--;
}
}
}
}
}
public class WindowsTest1 {
public static void main(String[] args) {
WIns wIns = new WIns();
Thread t1 = new Thread(wIns);
Thread t2 = new Thread(wIns);
t1.setName("窗口一");
t2.setName("窗口二");
t1.start();
t2.start();
}
}
class Window2 extends Thread {
private static int ticket = 100;//需要加static使得三个线程共用一个数据,但是存在线程安全问题,待解决!!!
static Object obj = new Object();
public void run() {
while (true) {
synchronized (this) {//类也是对象,真正的面向对象
if (ticket > 0) {
System.out.println(getName() + "卖出的票,票号为:" + ticket);
ticket--;
}
}
}
}
}
public class WindowsTest2 {
public static void main(String[] args) {
Window2 w1 = new Window2();
Window2 w2 = new Window2();
Window2 w3 = new Window2();
w1.setName("窗口1:");
w2.setName("窗口2:");
w3.setName("窗口3:");
w1.start();
w2.start();
w3.start();
}
}
二、解决线程安全的方式二:同步方法
如果操作共享数据的代码完整的声明在一个方法中,我们不妨将这个方法声明为同步的
同步方法的 好处:解决了线程安全问题 坏处:操作同步的代码时,只能一个来操作,相当于单线
/**
* 使用同步方法来解决实现Runnable接口的线程安全问题
*
* @author haiyu
* @create 2022/1/1-20:36
*/
class Windows5 implements Runnable {
private int tacket = 100;
// Object obj = new Object();
@Override
public void run() {
while (true) {
show();
}
}
private synchronized void show(){//默认的同步监视器:this
if (tacket > 0) {
System.out.println(Thread.currentThread().getName() + "卖出的票号是:" + tacket);
tacket--;
}
}
}
public class WindowsTest3 {
public static void main(String[] args) {
Windows5 windows3 = new Windows5();
Thread t1 = new Thread(windows3);
Thread t2 = new Thread(windows3);
t1.setName("窗口一");
t2.setName("窗口二");
t1.start();
t2.start();
}
}
class Window4 extends Thread {
private static int ticket = 100;//需要加static使得三个线程共用一个数据,但是存在线程安全问题,待解决!!!
public void run() {
while (true) {
show();
}
}
// private synchronized void show(){ 同步监视器为:this 当前类的对象:w1,w2,w3 有三个。这是不对的
private static synchronized void show(){//同步监视器:当前类本身:Windows4.class
if (ticket > 0) {
System.out.println(Thread.currentThread().getName() + "卖出的票,票号为:" + ticket);
ticket--;
}
}
}
public class WindowsTest4 {
public static void main(String[] args) {
Window4 w1 = new Window4();
Window4 w2 = new Window4();
Window4 w3 = new Window4();
w1.setName("窗口1:");
w2.setName("窗口2:");
w3.setName("窗口3:");
w1.start();
w2.start();
w3.start();
}
}
总结:
1.同步方法仍然涉及到同步监视器,只是不用显式的去声明
2.非静态的同步方法,同步监视器是:this
3.静态的同步方法,同步监视器是:当前类本身
三、使用同步机制将单例模式中的懒汉式改为线程安全的
public class BankTest {
public static void main(String[] args) {
Bank bank = Bank.getBank();
}
}
class Bank {
private Bank() {
}
private static Bank bank = null;
public static Bank getBank() {//或者直接public static synchronized Bank getBank()
// synchronized (Bank.class) {//但是这样效率不好
if (bank == null) {
bank = new Bank();
}
return bank;
// }
//这样就好多了,后面来的就直接可以return了,不用判断
if(bank == null){
synchronized (Bank.class){
bank = new Bank();
}
}
return bank;
}
}
四、解决线程安全的方式三:Lock锁
面试题:synchronized 和 lock的异同
相同:二者都可以解决线程问题
不同:synchronized机制在执行完相应的同步代码块以后,自动的释放同步监视器
lock需要手动的lock() 和 unlock()
class WIn implements Runnable {
private int ticket = 100;
//1.实例化一个ReentrantLock
private ReentrantLock lock = new ReentrantLock();
@Override
public void run() {
while (true) {
try {
// 2. 调用lock方法
lock.lock();
if (ticket > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "卖出的票号为:" + ticket);
ticket--;
}else{
break;
}
}finally{
// 3.调用解锁的方法
lock.unlock();
}
}
}
}
public class LockTest {
public static void main(String[] args) {
WIn w1 = new WIn();
Thread t1 = new Thread(w1);
Thread t2 = new Thread(w1);
t1.start();
t2.start();
}
}
死锁的理解:不同的线程分别占用对方需要的同步资源不放弃,都在等待对方放弃自己需要的同步资源,就形成了线程的死锁
2.说明:
1)出现死锁后,不会出现异常,不会出现提示,只是所有的线程都处于阻塞状态,无法继续
2)我们使用同步时,要避免出现死锁。
解决方法
>专门的算法、原则
>尽量减少同步资源的定义>尽量避免嵌套同步
五、线程通信的例子:使用两个线程打印1-100。线程1,线程2交替打印
涉及到的三个方法: wait():一旦执行此方法,当前线程就进入阻塞状态,并释放同步监视器。 notify():一旦执行此方法,就会唤醒被wait的一个线程。如果有多个线程被wait, 就唤醒优先级高的那个线程。 notifyALl():一旦执行此方法,就会唤醒所有被wait的线程。 说明: 1.wait(),notify(),notifyALl()三个方法必须使用在同步代码块或同步方法中。 2.wait(),notify(),notifyALL()三个方法的调用者必须是同步代码块或同步方法中的同步监视器。 否则,会出现ILLegalMonitorstateException异常 3.wait(),notify(),notifyALL()三个方法是定义在java.lang.object类中。 面试题:sleep()和wait()的异同点 1.相同点:一旦执行方法,都会让调用的线程进入阻塞状态 2.不同点: ①两个方法声明的位置不同,Thread中声明的是sleep()方法,Object中声明的是wait()方法 ②调用的要求不同:sleep()可以在任何情况下调用,wait()必须在同步代码块中或者在同步方法中 ③关于是否释放同步监视器:如果两个方法都声明在同步代码块或同步方法中,slee()不会释放锁,wait()可以释放锁
class Number implements Runnable {
private int num = 0;
@Override
public void run() {
while (true) {
synchronized (this) {
notify();
if (num < 100) {
num++;
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "打印了" + num);
try {
wait();//作用:①释放锁 ②使调用wait()的线程进入阻塞状态
} catch (InterruptedException e) {
e.printStackTrace();
}
} else {
break;
}
}
}
}
}
public class communicationTest {
public static void main(String[] args) {
Number n = new Number();
Thread t1 = new Thread(n);
Thread t2 = new Thread(n);
t1.setName("打印机1");
t2.setName("打印机2");
t1.start();
t2.start();
}
}
六、线程通信的应用:经典例题:生产者和消费者的问题
生产者(Productor)将产品交给店员(CLerk),而消费者(Customer)从店员处取走产品,店员一次 只能持有固定数量的产品(比如:20),如果生产者试图生产更多的产品,店员会叫生产者停一下, 如果店中有空位放产品了再通知生产者继续生产;如果店中没有产品了,店员会告诉消费者等一下, 如果店中有产品了再通知消费者来取走产品。
lass Clerk {
private int number = 0;
public int getNumber() {
return number;
}
public synchronized void shengchan() {
if (number < 20) {
number++;
System.out.println("产品数量为:" + getNumber());
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
notify();
} else {
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public synchronized void xiaofei() {
if (number > 0) {
number--;
System.out.println("产品数量为:" + getNumber());
try {
Thread.sleep(20);
} catch (InterruptedException e) {
e.printStackTrace();
}
notify();
} else {
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
class Productor extends Thread {
private Clerk clerk;
public Productor(Clerk clerk) {
this.clerk = clerk;
}
@Override
public void run() {
while (true) {
clerk.shengchan();
}
}
}
class Cosumer extends Thread {
private Clerk clerk;
public Cosumer(Clerk clerk) {
this.clerk = clerk;
}
@Override
public void run() {
while (true) {
clerk.xiaofei();
}
}
}
public class ProductTest {
public static void main(String[] args) {
Clerk c = new Clerk();
Productor p1 = new Productor(c);
Cosumer c1 = new Cosumer(c);
Cosumer c2 = new Cosumer(c);
p1.setName("提供者");
c1.setName("客户1");
c2.setName("客户2");
p1.start();
c1.start();
c2.start();
}
}