多线程

本文详细介绍了Java中创建线程的四种方法,包括继承Thread类、实现Runnable接口、实现Callable接口及使用线程池。并通过案例分析了线程安全问题,探讨了同步方法、同步代码块和Lock锁三种解决线程同步的策略。

1 创建线程
创建线程的方式总共有四种:
第一种是继承Thread类方式
第二种是实现Runnable接口方式
第三种是实现Callable接口
第四种是线程池:提供了一个线程队列,队列中保存着所有等待状态的线程。避免了创建与销毁额外开销,提高了响应的速度。
第一种:继承Thread类,重写run() 方法,调用start开启线程
注意,线程开启不一定立即执行,由cpu调度执行

public class TestThread1 extends Thread {
    @Override
    public void run() {
        //run 方法线程体
        for(int i=0;i<20;i++){
            System.out.println("我在看代码————"+i);
        }
    }
    public static void main(String[] args) {
        //mian 线程,主线程
        //创建一个线程对象
        TestThread1 testThread1 =new TestThread1();
        //调用start()方法开启线程
        testThread1.start();
        for (int i = 0; i < 20; i++) {
            System.out.println("我在学习线程--"+i);

        }
    }
}

输出:
我在学习线程--0
我在学习线程--1
我在学习线程--2
我在学习线程--3
我在学习线程--4
我在学习线程--5
我在学习线程--6
我在学习线程--7
我在学习线程--8
我在学习线程--9
我在学习线程--10
我在学习线程--11
我在学习线程--12
我在学习线程--13
我在看代码————0
我在学习线程--14
我在学习线程--15
我在看代码————1
我在学习线程--16
我在看代码————2
我在看代码————3
我在看代码————4
我在学习线程--17
我在看代码————5
我在看代码————6
我在看代码————7
我在看代码————8
我在看代码————9
我在看代码————10
我在看代码————11
我在看代码————12
我在看代码————13
我在看代码————14
我在看代码————15
我在看代码————16
我在看代码————17
我在看代码————18
我在看代码————19
我在学习线程--18
我在学习线程--19

Process finished with exit code 0

第二种:实现Runnable接口方式
步骤如下:

  1. 定义Runnable接口的实现类,并重写该接口的run()方法,该run()方法的方法体同样是该线程的线程执行体。
  2. 创建Runnable实现类的实例,并以此实例作为Thread的target来创建Thread对象,该Thread对象才是真正
    的线程对象。
  3. 调用线程对象的start()方法来启动线程。

public class TestThread2 implements Runnable {
    @Override
    public void run() {
        //run 方法线程体
        for(int i=0;i<20;i++){
            System.out.println("我在看代码————"+i);
        }
    }

    public static void main(String[] args) {
        //创建runnable 接口的实现类对象
        TestThread2 testThread2=new TestThread2();
        //创建线程对象,通过线程对象来开启我们的线程,
       // Thread thread = new Thread(testThread2);
       // thread.start();
        //调用start()方法开启线程
      new Thread(testThread2).start();
        for (int i = 0; i < 200; i++) {
            System.out.println("我在学习线程--"+i);

        }
    }
}
输出:
我在看代码————0
我在看代码————1
我在看代码————2
我在看代码————3
我在看代码————4
我在看代码————5
我在看代码————6
我在看代码————7
我在看代码————8
我在学习线程--0
我在学习线程--1
我在学习线程--2
我在学习线程--3
我在学习线程--4
我在学习线程--5
我在学习线程--6
我在看代码————9
我在学习线程--7
我在学习线程--8
我在学习线程--9

Process finished with exit code 0

Thread和Runnable的区别
如果一个类继承Thread,则不适合资源共享。但是如果实现了Runable接口的话,则很容易的实现资源共享。 总结:
实现Runnable接口比继承Thread类所具有的优势:

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

2 线程安全
如果有多个线程在同时运行,而这些线程可能会同时运行这段代码。程序每次运行结果和单线程运行的结果是一样的,而且其他的变量的值也和预期的是一样的,这就是线程安全问题。
我们通过一个案例,演示线程的安全问题:

//多个线程同时操作同一个对象
//买火车票多例子
public class TestThread3 implements Runnable {
    //票数
    private int ticketNums=10;
    @Override
    public void run() {
        while (true){
            if(ticketNums<=0){
                break;
            }
            try{
                Thread.sleep(200);
            }catch (InterruptedException e){
                e.printStackTrace();
            }
         System.out.println(Thread.currentThread().getName()+"-->拿到了第"+ticketNums-- +"票");
        }
    }
    public static void main(String[] args) {
        TestThread3 ticket =new TestThread3();
        new Thread(ticket,"小明").start();
        new Thread(ticket,"小花").start();
        new Thread(ticket,"黄牛党").start();
    }
}

结果输出:小明–>拿到了第9票
黄牛党–>拿到了第9票
小花–>拿到了第10票
小花–>拿到了第8票
小明–>拿到了第8票
黄牛党–>拿到了第8票
小花–>拿到了第7票
小明–>拿到了第6票
黄牛党–>拿到了第5票
黄牛党–>拿到了第4票
小明–>拿到了第3票
小花–>拿到了第4票
黄牛党–>拿到了第2票
小明–>拿到了第1票
小花–>拿到了第0票
黄牛党–>拿到了第-1票

发现程序出现了两个问题:

  1. 相同的票数
  2. 不存在的票,比如0票与-1票,是不存在的。 这种问题,几个窗口(线程)票数不同步了,这种问题称为线程不安全。

2.2线程同步
当我们使用多个线程访问同一资源的时候,且多个线程中对资源有写的操作,就容易出现线程安全问题。
要解决上述多线程并发访问一个资源的安全性问题:也就是解决重复票与不存在票问题,Java中提供了同步机制 (synchronized)来解决。
为了保证每个线程都能正常执行原子操作,Java引入了线程同步机制。 那么怎么去使用呢?有三种方式完成同步操作:

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

例子:1.同步方法
使用synchronized修饰的方法,就叫做同步方法,保证A线程执行该方法的时候,其他线程只能在方法外等着。

格式: public synchronized void method(){
可能会产生线程安全问题的代码
}

public class UnsafeTicket {

        public static void main(String[] args) {
            TestLock2 testLock2 =new TestLock2();
            new Thread(testLock2).start();
            new Thread(testLock2).start();
            new Thread(testLock2).start();
        }
    }
    class TestLock2 implements Runnable{
        int ticketNums=10;
        @Override
        public  synchronized void run() {
            while (true){
                    if(ticketNums>0){
                        try{
                            Thread.sleep(1000);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        System.out.println(ticketNums--);
                    }else {
                        break;
                    }

结果:
10
9
8
7
6
5
4
3
2
1

2.同步代码块
同步代码块: synchronized 关键字可以用于方法中的某个区块中,表示只对这个区块的资源实行互斥访问。
格式:synchronized (同步锁){
需要同步操作的代码块
}
同步锁: 对象的同步锁只是一个概念,可以想象为在对象上标记了一个锁.

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

代码:

public class Testlock3 {

    public static void main(String[] args) {
        TestLock1 testLock2 =new TestLock1();
        new Thread(testLock2).start();
        new Thread(testLock2).start();
        new Thread(testLock2).start();
    }
}
class TestLock1 implements Runnable {
    int ticketNums = 10;
    Object lock = new Object();

    @Override
    public void run() {
        while (true) {
            synchronized (lock) {
                if (ticketNums > 0) {
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(ticketNums--);
                } else {
                    break;
                }
            }

        }
    }
}

3.lock锁
lock锁机制提供了比synchronized代码块和synchronized方法更广泛的锁定操作,同步代码块/同步方法具有的功能Lock都有,除此之外更强大,更体现面向对象。 Lock锁也称同步锁,加锁与释放锁方法化了,如下:
public void lock() :加同步锁。 public void unlock() :释放同步锁。
代码如下:

import java.util.concurrent.locks.ReentrantLock;
// 测试锁
public class TestLock {
    public static void main(String[] args) {


        TestLock2 testLock2 = new TestLock2();
        new Thread(testLock2).start();
        new Thread(testLock2).start();
        new Thread(testLock2).start();
    }
}
class TestLock2 implements Runnable{
    int ticketNums=10;
    //定义lock 锁
    private final ReentrantLock lock =new ReentrantLock();
    @Override
    public void run() {
        while (true){
            try{
                lock.lock();//加锁
                if(ticketNums>0){
                    try{
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(ticketNums--);
                }else {
                    break;
                }
            }finally {
                lock.unlock();//解锁
            }
        }
    }
}

synchronized 与lock 的对比

  1. lock是显示锁(手动开启和关闭锁)synchronized 是隐式锁,出了作用域自动释放。
  2. Lock 只有代码块锁,synchronized 有代码块锁和方式锁
  3. 使用lock锁,JVM将花费较少的时间来调整线程,性能更好,并且具有更好的扩展性
  4. 优先使用顺序:
    Lock> 同步代码块>同步方法
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值