欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Java线程实现的两种方式及线程安全问题锁机制

程序员文章站 2024-03-19 14:28:16
...

线程的实现

创建多线程的第一种方式:创建Thread类的子类

实现步骤:
    1. 创建一个Thread类的子类
    2. 在Thread类的子类中重写Thread的run方法,设置线程任务(开启线程要做什么?)
    3. 创建Thread类的子类对象
    4. 调用Thread类中的start方法,开启新的线程,执行run方法
java程序属于抢占式调度,那个线程的优先级高,哪个线程先执行;同一个优先级,随机选择一个执行
// 1. 创建一个Thread类的子类
public class MyThread extends Thread{

    // 2. 在Thread类的子类中重写Thread的run方法,设置线程任务(开启线程要做什么?)
    @Override
    public void run(){
        for (int i = 0; i < 20; i++) {
            System.out.println("run"+i);
        }
    }
}

public class Demo01Thread {

    public static void main(String[] args) {
        // 3. 创建Thread类的子类对象
        MyThread mt = new MyThread();
        // 4. 调用Thread类中的start方法,开启新的线程,执行run方法
        mt.start();

        for (int i = 0; i < 20; i++) {
            System.out.println("main"+i);
        }
    }
}

创建多线程程序的第二种方式:实现Runnable接口

实现步骤:
    1. 创建一个Runnable接口的实现类
    2. 在实现类中重写Runnable接口的run方法,设置线程任务
    3. 创建一个Runnable接口的实现类对象
    4. 创建Thread类对象,构造方法中传递Runnable接口的实现类对象
    5. 调用Thread类中的start方法,开启新的线程执行run方法
// 1. 创建一个Runnable接口的实现类
public class RunnableImpl implements Runnable {

    // 2. 在实现类中重写Runnable接口的run方法,设置线程任务
    @Override
    public void run() {
        for (int i = 0; i < 20; i++) {
            System.out.println(Thread.currentThread().getName() + "---->" + i);
        }
    }
}
public class Demo02Runnable {

    public static void main(String[] args) {
        // 3. 创建一个Runnable接口的实现类对象
        RunnableImpl run = new RunnableImpl();
        // 4. 创建Thread类对象,构造方法中传递Runnable接口的实现类对象
        Thread t = new Thread(run, "无情");
        // 5. 调用Thread类中的start方法,开启新的线程执行run方法
        t.start();

        for (int i = 0; i < 20; i++) {
            System.out.println(Thread.currentThread().getName()+"---->"+i);
        }
    }
}

实现Runnable创建多线程程序的好处:

1.避免了单继承的局限
一个类只能继承一个类,类继承了Thread就不能继承其它的类。
实现了Runnable接口,还可以继承其它的类,实现其它的接口
2.增强程序的扩展性,降低程度的耦合性(解耦)
实现Runnable接口的方式,把设置线程任务和开启新线程进行了分离(解耦)
实现类中,重写了run方法:用来设置线程任务
创建Thread类对象,调用start方法:开启新的线程
建议使用runnable接口实现多线程。

通过匿名内部类实现线程的创建

public class Demo03Anonymous {

    public static void main(String[] args) {
        Runnable r = new Runnable(){
            @Override
            public void run() {
                for (int i = 0; i < 200; i++) {
                    System.out.println("张宇" + i);
                }
            }
        };

        new Thread(r).start();

        for (int i = 0; i < 200; i++) {
            System.out.println("费玉清" + i);
        }
    }
}

线程的安全

通过一个案例,讲述线程安全问题:
电影院在卖100张票,三个售票窗口同时卖;

public class TicketImpl implements Runnable{
    private int ticket = 100;

    @Override
    public void run() {
        while (true){
            if (ticket>0){
                try {
                    Thread.sleep(50);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                String name = Thread.currentThread().getName();
                System.out.println(name + "正在卖:" + ticket--);
            }

        }
    }
}
public class Demo04Ticket {

    public static void main(String[] args) {
        // 创建线程任务对象
        TicketImpl ticket = new TicketImpl();
        // 创建三个窗口对象
        Thread t1 = new Thread(ticket, "窗口1");
        Thread t2 = new Thread(ticket, "窗口2");
        Thread t3 = new Thread(ticket, "窗口3");
        // 启动线程,开始卖票
        t1.start();
        t2.start();
        t3.start();

    }
}

输出:

...
窗口3正在卖:17
窗口1正在卖:17
窗口2正在卖:16
窗口1正在卖:15
窗口3正在卖:15
...
窗口1正在卖:0
窗口3正在卖:-1

此时出现:

  1. 相同的票被卖了多次
  2. 不存在的票,0和-1

这样的票数不同步问题,成为线程不安全

解锁线程安全问题有三种方法

  1. 同步代码块
  2. 同步方法
  3. 锁机制

同步代码块

synchronized关键字用于方法中的某个区块中,表示只对这个区块的资源实行互斥访问。格式:

synchronized(同步锁){
    需要同步操作的代码
}

同步锁:
1.锁对象可以是任意类型
2.多个线程对象,要使用同一把锁

public class TicketImpl implements Runnable{
    private int ticket = 100;

    Object lock = new Object();

    @Override
    public void run() {
        while (true){
            synchronized (lock){
                if (ticket>0){
                    try {
                        Thread.sleep(50);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    String name = Thread.currentThread().getName();
                    System.out.println(name + "正在卖:" + ticket--);
                }
            }
        }
    }
}

同步方法

使用synchronized修饰的方法就叫同步方法,保证线程对方法的访问时互斥的。

public synchronized void method(){
    可能会产生线程安全问题的代码
}
public class TicketImpl implements Runnable{
    private int ticket = 100;

    Object lock = new Object();
    
    // 使用同步方法
    public synchronized void sellTicket(){
        if (ticket>0){
            try {
                Thread.sleep(50);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            String name = Thread.currentThread().getName();
            System.out.println(name + "正在卖:" + ticket--);
        }
    }

    @Override
    public void run() {
        while (true){
            sellTicket();
        }
    }
}

Lock锁

java.util.concurrent.locks.Lock机制提供了比synchronized代码块和synchronized方法更广泛的锁定操作,同步代码块/同步方法具有的功能Lock都有,除此之外更强大,更体现面向对象。

Lock锁也称同步锁,加锁与释放锁方法化了,如下:

  • public void lock() :加同步锁。
  • public void unlock() :释放同步锁。
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class TicketImpl implements Runnable{
    private int ticket = 100;

    Lock lock = new ReentrantLock();

    @Override
    public void run() {
        while (true){
            lock.lock();
            if (ticket>0){
                try {
                    Thread.sleep(50);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                String name = Thread.currentThread().getName();
                System.out.println(name + "正在卖:" + ticket--);
            }
            lock.unlock();
        }
    }