Java 多线程、线程安全实例

创建线程

Java使用 java.lang.Thread 类代表线程,所有的线程对象都必须是Thread类或其子类的实例。

Thread类

public class MyThread extends Thread{
    public MyThread(String name) {
        //调用父类的String参数的构造方法,指定线程的名称
        super(name);
    }

    /**
     * 重写run方法,完成该线程执行的逻辑
     */
    @Override
    public void run() {
        try {
            sleep(10);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        for(int i = 0; i < 100; i++) {
            System.out.println(getName() + i);
        }
    }
}
public class ThreadTest {
    public static void main(String[] args) {
        //创建自定义线程对象
        MyThread mt1 = new MyThread("a");
        MyThread mt2 = new MyThread("b");
        MyThread mt3 = new MyThread("c");
        //开启新线程
        mt1.start();
        mt2.start();
        mt3.start();
        //主线程中的for循环
        for (int i = 0; i < 20; i++) { 
       		System.out.println("主线程:"+i); 
        }
    }
}

程序启动运行main时候,java虚拟机启动一个进程,主线程main在main()调用时候被创建。随着调用mt的对象的 start方法,另外三个新的线程也启动了,这样,整个应用就在多线程下运行。

Runnable接口

public class MyRunnable implements Runnable{
    @Override
    public void run() {
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        for(int i = 0; i < 10; i++) {
            System.out.println(Thread.currentThread().getName() + i);
        }
    }
}
public class RunableTest {
    public static void main(String[] args) {
        MyRunnable mr = new MyRunnable();

        new Thread(mr, "a").start();
        new Thread(mr, "b").start();
        new Thread(mr, "c").start();
    }
}

通过实现Runnable接口,使得该类有了多线程类的特征。run()方法是多线程程序的一个执行目标。所有的多线程 代码都在run方法里面。Thread类实际上也是实现了Runnable接口的类。

在启动的多线程的时候,需要先通过Thread类的构造方法Thread(Runnable target) 构造出对象,然后调用Thread 对象的start()方法来运行多线程代码。

实际上所有的多线程代码都是通过运行Thread的start()方法来运行的。因此,不管是继承Thread类还是实现 Runnable接口来实现多线程,最终还是通过Thread的对象的API来控制线程的,熟悉Thread类的API是进行多线程 编程的基础。

实现Runnable接口比继承Thread类所具有的优势

  • 适合多个相同的程序代码的线程去共享同一个资源
  • 可以避免java中的单继承的局限性
  • 增加程序的健壮性,实现解耦操作,代码可以被多个线程共享,代码和线程独立
  • 线程池只能放入实现Runable或Callable类线程,不能直接放入继承Thread的类。

在java中,每次程序运行至少启动2个线程。一个是main线程,一个是垃圾收集线程。因为每当使用 java命令执行一个类的时候,实际上都会启动一个JVM,每一个JVM其实在就是在操作系统中启动了一个进程。

线程安全

如果有多个线程在同时运行,而这些线程可能会同时运行这段代码。程序每次运行结果和单线程运行的结果是一样 的,而且其他的变量的值也和预期的是一样的,就是线程安全的。
下面模拟售票演示线程不安全:

public class Ticket implements Runnable {
    private int ticket = 100;
    @Override
    public void run() {
        while (true) {
            if (ticket > 0) {
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                String name = Thread.currentThread().getName();
                System.out.println(name + "正在卖:" + ticket);
                ticket--;
            }
        }
    }
}
public class Test1 {
    public static void main(String[] args) {
        Ticket ticket = new Ticket();

        Thread t1 = new Thread(ticket, "窗口1");
        Thread t2 = new Thread(ticket, "窗口2");
        Thread t3 = new Thread(ticket, "窗口3");

        t1.start();
        t2.start();
        t3.start();
    }
}

部分结果:
在这里插入图片描述

线程同步

当我们使用多个线程访问同一资源的时候,且多个线程中对资源有写操作,就容易出现线程安全问题。
为了保证每个线程都能正常执行原子操作,Java引入了线程同步机制。有三种方式完成同步操作:

  • 同步代码块
  • 同步方法
  • 锁机制

同步代码块

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

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

同步锁是一个抽象的概念,锁对象可以是任意类型,比如new Object()也可以。

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

    Object obj = new Object();

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

同步方法

使用synchronized修饰的方法叫做同步方法,保证一个线程在执行方法的时候,其他线程阻塞等待。

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

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

    public synchronized void payTicket() {
        if (ticket > 0) {
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            String name = Thread.currentThread().getName();
            System.out.println(name + "正在卖:" + ticket);
            ticket--;
        }
    }
}

同步方法payTicket()也可以设置为静态的,对于非静态方法,同步锁是this,对于静态方法,使用当前方法所在类的字节码对象(类名.class)。

Lock锁

Lock锁也称同步锁

  • public void lock() :加同步锁
  • public void unlock() :释放同步锁
public class Ticket implements Runnable {
    private int ticket = 100;

    Lock l = new ReentrantLock();

    @Override
    public void run() {
        while (true) {
           l.lock();
            if (ticket > 0) {
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }finally{
                    String name = Thread.currentThread().getName();
                    System.out.println(name + "正在卖:" + ticket);
                    ticket--;
                    l.unlock();
                }
            }
        }
    }
}
发布了243 篇原创文章 · 获赞 87 · 访问量 7万+

猜你喜欢

转载自blog.csdn.net/IT_10/article/details/104070077