同步方法解决两种线程创建方式的线程安全问题
接上一节!
线程的安全问题与线程的同步机制
1. 多线程卖票出现的问题:出现了重票和错票
2.什么原因导致的? 线程1操作ticket的过程中,尚未结束的情况下,其他线程也参与进来,对ticket进行操作。
3.如何解决? 必须保证一个线程a在操作ticket的过程中,其他线程必须等待,
直到线程a操作ticket结束以后,其它线程才可以进来继续操作ticket。(ticket是共享数据)
比如上厕所几个坑位,坑位就是共享数据,排队进去后,上完,后面的人才能进来,不能一次进去好几个人,这样才安全。
4.Java是如何解决线程的安全问题? 使用线程的同步机制。
方式1:同步代码块
synchronized(同步监视器){
//需要被同步的代码
}
说明:
> 需要被同步的代码,及为操作共享数据的代码
> 共享数据:即多个线程都需要操作的数据。比如:ticket
> 需要被同步的代码,在被synchronized包裹以后,就使得一个线程在操作这些代码的过程中,其他线程必须等待。
> 同步监视器,俗称锁。哪个线程获取了锁,哪个线程就能执行需要被同步的代码。
比如一个坑位,有个门锁,你进去把门锁上别人就进不来了,上完厕所把锁打开其他人才能进来。
> 同步监视器,可以使用任何一个类的对象充当。但是,多个线程必须共用同一个同步监视器。
注意:在实现Runnable接口的方式中,同步监视器可以考虑使用:this。
在继承Thread类的方式中,同步监视器要慎用this,可以考虑使用:当前类.class。
方式2:同步方法
说明:
> 如果操作共享数据的代码完整的声明在了一个方法中,那么我们就可以将此方法声明为同步方法即可。
> 非静态的同步方法,默认同步监视器是this,this还是要考虑是不是唯一的。
静态的同步方法,默认的同步监视器是当前类本身。当前类名.class
5. synchronized的好处:解决了线程的安全问题
弊端:在操作共享数据时,多线程其实是串行执行的,意味着性能底。
使用 同步方法 解决两种线程创建方式的线程安全问题
解决实现Runnable接口方式出现的线程安全问题:
package thread.demo03_threadsafe.runnablesafe;
//使用同步方法解决实现Runnable接口的线程安全问题。
public class WindowTest1 {
public static void main(String[] args) {
SaleTicket1 s = new SaleTicket1();
Thread t1 = new Thread(s);
Thread t2 = new Thread(s);
Thread t3 = new Thread(s);
t1.setName("窗口一");
t2.setName("窗口二");
t3.setName("窗口三");
t1.start();
t2.start();
t3.start();
}
}
class SaleTicket1 implements Runnable{
int ticket = 100;
boolean isFlag = true;//之前while(true)没法跳出循环,就加了这一行。
@Override
public void run() {
while (isFlag){
show();
}
}
//同步方法是非静态的,默认的同步监视器就是this。
public synchronized void show(){ //此时的同步监视器是:this。此题目中即为s,是唯一的。
if (ticket > 0){
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "售票,票号为: " + ticket);
ticket--;
}else {
isFlag = false;
}
}
}
解决继承Thread方式出现的线程安全问题:
package thread.demo03_threadsafe.threadsafe;
//使用同步方法解决继承Thread类的线程安全问题
public class WindowTest1 {
public static void main(String[] args) {
SaleTicket2 s1 = new SaleTicket2("窗口1");
SaleTicket2 s2 = new SaleTicket2("窗口2");
SaleTicket2 s3 = new SaleTicket2("窗口3");
s1.start();
s2.start();
s3.start();
}
}
class SaleTicket2 extends Thread{
public SaleTicket2() {
}
public SaleTicket2(String name) {
super(name);
}
static int ticket = 100;//没有static直接300张票,每个窗口都卖1——100
//一个对象一份的是实例变量。
//所有对象一份的是静态变量。
static boolean isFlag = true;
@Override
public void run() {
while (true){
show();
}
}
// public synchronized void show(){ //此时同步监视器:this。此题目中this有好几个,s1,s2,s3,仍然是线程不安全的
public static synchronized void show(){ //此时同步监视器:当前类本身,即为SaleTicket2.class,是唯一的,所以线程是安全的。
//具体还要看适不适合加成静态,不要为了改成线程安全就加静态static。
if (ticket > 0){
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "票号为: " + ticket);
ticket--;
}else {
isFlag = false;
}
}
}
Thread类的常用方法和生命周期练习:
题目需求:
新年倒计时
模拟新年倒计时,每隔一秒输出一个数字,依次输出10,9,8......1,最后输出,新年快乐!
package thread.demo02_lifecycle.exer;
//题目:模拟新年倒计时,每隔一秒输出一个数字,依次输出10,9,8......1,最后输出,新年快乐!
//题目没有其它要求,就直接在主线程里面写出来
public class HappyNewYear {
public static void main(String[] args) {
for (int i = 10; i >= 0; i--) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (i > 0){
System.out.println("新年倒计时:" +i + "天");
}else {
System.out.println("新年快乐!!!");
}
}
}
}
输出:
Thread类的常用方法和生命周期面试:
package thread.demo02_lifecycle.interview;
/*
关于Thread.sleep()方法的一个面试题:如下的代码中sleep()执行后,到底哪个线程进入阻塞状态了呢?
*/
public class ThreadTest {
public static void main(String[] args) {
//创建线程对象
MyThread t = new MyThread("线程1");
t.start();
//调用sleep方法
try {
t.sleep(1000 * 5);//t还是在主线程里面调sleep方法,这里可以理解为对象调sleep方法。
} catch (InterruptedException e) {
e.printStackTrace();
}
//5秒后这里才会执行。
System.out.println("Hello,World!");//阻塞的是主线程
}
}
class MyThread extends Thread{
public MyThread() {
}
public MyThread(String name){
}
@Override
public void run() {
for (int i = 0; i < 10000; i++) {
System.out.println(Thread.currentThread().getName() + "--->" + i);
}
}
}
解决线程安全问题的练习:
银行有一个账户。
有两个储户分别向同一个账户存3000元,每次存1000,存3次。每次存完打印账户余额。
问题:该程序是否有安全问题,如果有,如何解决?
【提示】
1. 明确哪些代码是多线程运行代码,须写入run()方法。
2. 明确什么是共享数据。
3. 明确多线程运行代码中哪些语句是操作共享数据的。
package thread.demo03_threadsafe.exer;
public class AccountTest {
public static void main(String[] args) {
Account acct = new Account();
Customer customer1 = new Customer(acct,"储户甲");
Customer customer2 = new Customer(acct,"储户乙");
customer1.start();
customer2.start();
}
}
class Account{ //账户
private double balance; //余额
public synchronized void deposit(double amt){
if(amt > 0){
balance += amt;
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "存钱1000元,账户余额为: " + balance);
}
}
class Customer extends Thread{ //Customer客户
Account account;
public Customer(Account acct){
this.account = acct;
}
public Customer(Account acct,String name){
super(name);
this.account = acct;
}
@Override
public void run() {
for (int i = 0; i < 3; i++) {
//有了这一段就能看出来交互了,甲存一次,乙存一次。
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
account.deposit(1000);
}
}
}
输出: