多线程
进程:系统分配资源的单位;
线程:处理器任务调度和执行的单位,线程直接共享进程资源。
安全隐患问题:
线程不同步问题:
问题:会出现多个窗口买一张票的情况/卖错票(票号为0/-1)
问题出现原因:一个线程已经运行了,但是还没有执行ticket--,后续线程也进来了,导致他们操作了同一张票号...
解决:如果一个线程已经运行了,但是在运行的过程中阻塞了,也不会给后续线程分配CPU资源,直至该线程运行完毕。
线程安全的两种方法:
1.方式一:同步代码块(synchronized);
2.方式二:同步方法。
注意:同步代码块和同步方法统一的规则:不能包含过多的代码,也不能包含过少的代码,只包含存在安全隐患的代码。
1.方式一:同步代码块;
书写格式:
synchronized(同步监视器){
//需要同步的代码块
}
/**
* 参数说明:
* 1.需要被同步的代码:操作共享数据的代码(注意不能多包含代码,也不能少包含代码);
* 共享数据:多个线程共同操作的变量(例如,ticket票号)
* 2.同步监视器,俗称:锁。 任何类型的对象都能充当锁
* 要求:所有的线程必须保证共享一把锁。
* 注意类也是对象(在学习反射的时候会详讲),可以使用(类名.class)来同步监视器;
* 类只会被加载一次。
* 注意使用继承Thread类时 慎用(this)来充当同步监视器;
* 解释:(卫生间的锁不能从外面决定关锁/解锁,只能被进卫生间的人从里面关锁/解锁,也就是一个卫生间在同一时间的关锁/解锁只能被一个已经进入卫生间的人操作)。
*/
满足条件:
1.多个线程共用一把锁;
2.任何类型的对象都能充当同步监视器(锁);
3.需要同步的代码,不能多包也不能少包。
1.1.继承Thread类
public class PraSaleTicket {
public static void main(String[] args) {
Sale sale01 = new Sale();
Sale sale02 = new Sale();
Sale sale03 = new Sale();
sale01.setName("窗口01");
sale02.setName("窗口02");
sale03.setName("窗口03");
sale01.start();
sale02.start();
sale03.start();
}
}
class Sale extends Thread{
private static int ticket = 100;
Object myLock = new Object();
@Override
public void run() {
// synchronized (myLock) { // 不能多包含代码,也不能少包含代码
while (true){
synchronized (Sale.class) {//当前类为静态类,只能加载一次,所以可以让当前类作为锁
if (ticket > 0) {
try {
Thread.currentThread().sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(getName() + "_票号为:" + ticket);
ticket--;
} else {
break;
}
}
}
}
}
1.2.实现Runnable接口
public class PraSaleTicket_Runnable {
public static void main(String[] args) {
SaleTickets sale = new SaleTickets();
Thread thread = new Thread(sale);
Thread thread2 = new Thread(sale);
Thread thread3 = new Thread(sale);
thread.setName("窗口01");
thread2.setName("窗口02");
thread3.setName("窗口03");
thread.start();
thread2.start();
thread3.start();
}
}
class SaleTickets implements Runnable{
private int ticket = 100;
// Object olock = new Object(); // 任何类型的对象都能充当锁。
MyLock myLock = new MyLock();
@Override
public void run() {
// 错误的存放位置:Object olock = new Object(); // 任何类型的对象都能充当锁。
while (true) {
// 错误的存放位置:Object olock = new Object(); // 任何类型的对象都能充当锁。
synchronized(myLock) {
if (ticket > 0) {
//如果票号大于0,假设现在票号为1,线程1抢到了CPU,但是还没往后面走,线程2页签到了资源,线程3也抢到了...导致输出的票号出现了:1 0 -1 的现象。
//如果此处没有调用sleep(),也会出现重复票号的问题...
try {
Thread.currentThread().sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "_票号为:" + ticket);
ticket--;
} else {
break;
}
}
}
}
}
//任何类型的对象都能作为同步监视器。
class MyLock{
}
2.方式二:同步方法;
将存在线程安全隐患的代码形成一个方法,即为同步方法。
同步方法写法:
修饰符 (static) synchronized 返回值类型 方法名(){}
注意事项:
1.多个线程共用一把锁;
2.需要同步的代码,不能多包也不能少包。
3.静态的同步方法:默认使用的同步监视器是,当前类.class;
4.非静态的同步方法:默认使用的同步监视器是,this,指代当前类。
注意此时将需要同步的方法,使用了同步方法来保证线程安全,针对买100张票的话,我们没办法终止循环,所以在循环结束后需要手动终止循环!!!
2.1.继承Thread类
public class PraSaleTicket02 {
public static void main(String[] args) {
Sales sale01 = new Sales();
Sales sale02 = new Sales();
Sales sale03 = new Sales();
sale01.setName("窗口01");
sale02.setName("窗口02");
sale03.setName("窗口03");
sale01.start();
sale02.start();
sale03.start();
}
}
class Sales extends Thread{
private static int ticket = 100;
@Override
public void run() {
while (true){
showTicket();
}
}
private static synchronized void showTicket(){//默认的同步监视器是当前类(Sales.class)
// private synchronized void showTicket(){
//使用该方法是错的,因为这样是默认传入的同步监视器是this(也就是sale01,sale02,sale03)
//不能保证多个线程共用一把锁,所以线程是不安全的。
if (ticket > 0) {
try {
Thread.currentThread().sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "_票号为:" + ticket);
ticket--;
}
}
}
2.2.实现Runnable接口
public class PraSaleTicket_Runnable02 {
public static void main(String[] args) {
SaleTicket sale = new SaleTicket();
Thread thread = new Thread(sale);
Thread thread2 = new Thread(sale);
Thread thread3 = new Thread(sale);
thread.setName("窗口01");
thread2.setName("窗口02");
thread3.setName("窗口03");
thread.start();
thread2.start();
thread3.start();
}
}
class SaleTicket implements Runnable{
private int ticket = 100;
@Override
public void run() {
while (true) {
showTickets();
}
}
private synchronized void showTickets(){//默认的同步监视器是SaleTicket.class
if (ticket > 0) {
try {
Thread.currentThread().sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "_票号为:" + ticket);
ticket--;
}
}
线程安全的好处/局限:
好处:解决线程安全问题;
局限:让线程效率低,相当于把多线程问题变成了单线程的问题。
欢迎关注微信公众号:小红的成长日记,一起学Java!