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

线程的实现

创建多线程的第一种方式:创建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();
        }
    }
发布了56 篇原创文章 · 获赞 8 · 访问量 2万+

猜你喜欢

转载自blog.csdn.net/Dawn510/article/details/102677514