【Java学习笔记】 线程

线程的创建:

不带返回值:

方式一:

MyThread thread = new MyThread();

通过这种方式创建, MyThread 必须 extend Thread 类并且完成里面的 void run()方法

线程开启后,run方法将会被执行

 

方式二:

Thread thread = new Thread(task);

task 为 implement 了 Runnable 的类的对象,与方式一一样,类里面的void run()方法必须被完成

线程开启后,run方法会被执行

带返回值:

先建立一个implement Callable<T> 的类,完成里面的 T call()方法

先新建这个Callable对象(返回值为 Integer 为例):

class MyCallable implements Callable<Integer>{
    @Override
    int call(){
        return 1;
    }
}
Callable<Integer> callable=new MyCallable<Integer>();

然后再将这个callable对象传入task

FutureTask<Integer> task=new FutureTask<>(callable);

再将此task传入线程

Thread thread = new Thread(task);

这样线程开启后,当调用task.get();,程序会等待线程完成并且返回1

 

用户线程与守护线程

用户线程的运行是独立的,而守护线程在所有用户线程终止后就会自行终止,无论是否执行完任务

如何创建守护线程?

在构造Thread对象时,将第二个参数传入true可将线程设置为守护线程,否则,将会被设为用户线程

多线程下的数据安全问题

在使用多线程操作同一个数据时,会导致数据不安全

比如:

class ExposeProblem implements Runnable {

    private int testNum = 100;
    
    public void run() {
        while(testNum > 0){
            System.out.println(testNum--);
        }
    }

}

假设用这个类创建一个task对象,并用10个线程来运行这一个task

在这段代码中,按照代码逻辑,控制台本应该输出100到1,testNum最小值应该为0

但由于多线程,可能会发生这样的情况:

  1. testNum为1
  2. Thread1正在执行while条件判断,结果为true
  3. Thread0在循环内,并且正在执行testNum--
  4. Thread0输出testNum,为1
  5. testNum为0
  6. Thread1在循环内,并且正在执行testNum
  7. Thread1输出testNum,为0
  8. testNum为-1

这只是一种数据不安全的示例,在实际运行中,可能会发生更严重的情况,例如输出-1,-2

因此,在多线程中,数据保护非常重要

 

解决数据安全问题

常用的解决方法有两种

第一种: synchronized

以上面的代码为例,我们在run方法的签名加上synchronized

class ExposeProblem implements Runnable {

    private int testNum = 100;
    
    public synchronized void run() {
        while(testNum > 0){
            System.out.println(testNum--);
        }
    }

}

此时,当一个线程执行run方法,会把任务对象作为key,并且 hold 住这个 key,此时,别的线程便无法进入run方法,当这个线程执行结束,会自动释放key,其他线程便可以进入

第二种:Lock

还是以前面的代码为例:

class ExposeProblem implements Runnable {

    private int testNum = 100;
    private Lock lock = new ReentrantLock();
    
    public synchronized void run() {
        lock.lock();
        while(testNum > 0){
            System.out.println(testNum--);
        }
        lock.unlock();
    }

}

用lock()方法来锁,unlock()方法来开锁

这样,当一个线程进入lock.lock();之后,会拿走锁的钥匙,别的进程就无法进入,unlock以后将钥匙归还,别的线程就可以进入,这也能保住testNum不被多条线程同时操作

 

死锁

使用锁来确保数据安全性,但是使用不当可能会造成死锁的情况,程序堵在一个地方无法运行

例如:


    public static void main(String[] args) throws InterruptedException {
        Student s = new Student();
        Teacher t = new Teacher();
        TestThread testThread = new TestThread(s, t);
        testThread.start();
        t.enter(s);

    }

    static class Student {
        public synchronized void enter(Teacher t) throws InterruptedException {
            System.out.println("学生拿着教室的钥匙尝试进入老师办公室");
            Thread.sleep(200);
            t.leave();
        }

        public synchronized void leave() {
            System.out.println("教室外有钥匙");
            System.out.println("老师离开了教室并且归还了钥匙");
        }
    }

    static class Teacher {
        public synchronized void enter(Student s) throws InterruptedException {
            System.out.println("老师拿着办公室的钥匙尝试进入教室");
            Thread.sleep(200);
            s.leave();

        }

        public synchronized void leave() {
            System.out.println("办公室外有钥匙");
            System.out.println("学生离开了办公室并且归还了钥匙");
        }
    }

    static class TestThread extends Thread {
        Student s;
        Teacher t;

        public TestThread(Student s, Teacher t) {
            this.s = s;
            this.t = t;
        }

        @Override
        public void run() {
            try {
                s.enter(t);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

这段代码会造成死锁

老师拿着办公室的钥匙进入教室,但教室的钥匙被学生拿走了,所以老师在教室外面等

学生拿着教室的钥匙进入办公室,但办公室的钥匙被老师拿走了,所以学生在办公室外面等

他们永远也等不到钥匙,这就是死锁

Wait和Notify

每个对象都有wait和notify方法用来暂停线程和恢复其它线程,

例如,Thread1和Thread0都有obj对象,Thread1调用obj.wait(),会暂停,Thread0可以随时调用notify()或notifyAll()来恢复Thread1的线程

这个通常用在线程间通信

 

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值