并发的知识

本文详细介绍了Java中的线程创建、线程属性、共享内存问题、synchronized关键字、volatile修饰符以及线程协作和中断机制。通过实例展示了线程安全问题如竞态条件,并探讨了解决方案,包括使用synchronized和volatile确保内存可见性和线程安全。此外,还讨论了死锁现象及其避免方法。
摘要由CSDN通过智能技术生成

1.线程

线程表示一条单独的执行流,有自己的程序执行计数器,有自己的栈。Java中创建线程的方式有两种:一种是继承Thread,一种是实现Runnable接口。

1.1 创建线程

1.1.1 继承Thread并重写run方法来实现一个线程

代码如下:

package thread_practice.HelloThead;

public class HelloThread extends Thread{

    @Override
    public void run() {
        super.run ();
        System.out.println ("thread name:"+Thread.currentThread ().getName ());
        //currentThread ()是Thread中的静态方法,返回当前执行的线程对象。public static native Thread currentThread();
        System.out.println ("thread ID:"+Thread.currentThread ().getId ());
        System.out.println ("hello");
    }

    public static void main(String[] args) {
        Thread thread = new HelloThread ();
        thread.start ();//调用start(),run()就会执行。
    }
}

输出结果:

thread name:Thread-0
thread ID:16
hello

每一个Thread都有一个ID和name。

public long getId();
public final String getName();

1.1.2 实现Runnable接口来实现线程

优点:Java中只支持单继承,每个类最多只能有一个父类,如果类已经有父类了,就不能继承Thread。相对于Thread,Runnable接口更加实用。

举例

public class HelloRunnable implements Runnable {
    @Override
    public void run() {
        System.out.println ("hello");
    }

    public static void main(String[] args) {
        Thread helloThread = new Thread (new HelloRunnable ());
        //创建Thread对象,但传递一个Runnable对象
        helloThread.start ();
    }
}

输出结果:

hello

1.2 线程属性及方法

包含ID,name,优先级,状态,是否daemo线程,sleep方法,yield方法,join方法,过时方法。

  • 优先级
public final void setPriority(int newPriority);
public final int getPriority();
  • 状态

public State getState();

返回值类型Thread.State

    public enum State{
        NEW,//没有调用start的线程状态为new
        RUUNABLE,//调用start后线程执行run方法且没有阻塞,或者正在执行也可能在等待系统分配时间片
        BLOCKED,//线程阻塞
        WAITING,
        TIMED_WAITING,
        TERMINATED;//线程结束运行后的状态
    }
public final native boolean isAlive();
//检验返回线程是否还活着,线程被启动后,run方法运行结束前,返回值都是true。
  • 是否daemo线程
public final void setDamon(boolean on);
//damon线程一般是其他线程的辅助线程,当主线程退出时,它就没有存在的意义了。
public final boolean isDamon();
  • sleep方法
public static native void sleep(long millis) throws InterruptedException;
//睡眠期间,线程可以被中断,如果被中断,就会抛出InterruptedException
  • yield方法
public static native void yield();
//调用该方法,是告诉调度器我不着急占用CPU,你可以先让其他线程运行,不过也仅仅是建议,具体处理要看调度器
  • join方法
public final void join() throws InterruptedException;
//等待线程结束的过程中可以被中断,如果被中断,就会抛出InterruptedException
public final synchronized void join(long millis) throws InterruptedException;
//可以限定等待的最长时间,单位为毫秒,如果为0,表示无期限等待。
  • 过时方法
public final void stop();
public final void suspend();
public final void resume();

1.3 共享内存

demo

package thread_practice.ShareMemoryDemo;

import java.util.ArrayList;
import java.util.List;

public class ShareMemoryDemo {
    private static int shared  = 0;
    private static void incrShared(){
        shared++;
    }

    static class ChildThread extends Thread{
        List<String> list;
        public ChildThread(List<String> list){
            this.list = list;
        }

        @Override
        public void run() {
            incrShared ();
            list.add(Thread.currentThread ().getName ());
        }


    }

    public static void main(String[] args) throws InterruptedException{
        List<String> list = new ArrayList<String> ();
        Thread t1 = new ChildThread (list);
        Thread t2 = new ChildThread (list);
        t1.start ();
        t2.start ();
        t1.join ();
        t2.join ();
        System.out.println (shared);
        System.out.println (list);

    }
}

期望值:

2
[Thread-0, Thread-1]

但是实际上每次执行的结果都可能有差异,非期望值。

1.3.1 竞态条件

指当多个线程访问和操作同一个对象时,最终执行结果与执行时序有关。
demo

package thread_practice.ShareMemoryDemo;

public class CounterThread extends Thread {
    private static int counter = 0;

    @Override
    public void run() {
        for (int i = 0; i < 1000; i++) {
            counter++;
        }
    }

    public static void main(String[] args) throws InterruptedException {
        int num = 1000;
        Thread[] threads = new Thread[num];

        for (int i = 0; i < num; i++) {
            threads[i] = new CounterThread ();
            threads[i].start ();
        }

        for (int i = 0; i < num; i++) {
            threads[i].join ();
        }
        
        System.out.println (counter);
    }
}

992493//执行结果的期望值是100万,但是实际输出都是99万多。

这是为何呢?
在这里插入图片描述
原来其操作分为三步:
1.取counter的当前值;2.在当前值基础上加1;3.将新值重新赋给counter。
两个线程可能同时执行第一步,取到了相同的counter值,比如都取到了100,第一个线程执行完后counter变为101,儿第二个线程执行完后还是101,最终的结果就会与期望不符合。

那么如何解决呢?
在这里插入图片描述

1.使用synchronized关键字;
2.使用显式锁;
3.使用原子变量

1.3.2 内存可见性

多个线程可以共享访问和擦欧洲哦相同的变量,但一个线程对一个共享变量的修改,另一个线程不一定马上就能看到,甚至永远也看不到。

public class VisibilityDemo {
    private static boolean shutdown = false;

    static class HelloThread extends Thread{
        @Override
        public void run() {
            while (!shutdown){
                //do nothing
            }
            System.out.println ("exit hello");
        }
    }

    public static void main(String[] args) throws InterruptedException{
        new HelloThread ().start ();
        Thread.sleep (1000);
        shutdown = true;
        System.out.println ("exit main");
    }
}

执行结果;

exit main
//期望结果是两个线程都退出,然而实际上HelloThread永远都不会退出。

那么怎么解决这个问题呢?

1.使用volatile关键字
2.使用synchronized关键字或者显式锁同步;

1.4 synchronized

可以用来修饰实例方法、静态方法、代码块

1.4.1 实例方法demo

public class Counter {
    private int count;
    public synchronized void incr(){
        count++;
    }
    
    public synchronized int getCount(){
        return count;
    }
}

public class CounterThread extends Thread {
    Counter counter;
    public CounterThread(Counter counter){
        this.counter = counter;
    }

    @Override
    public void run() {
        for (int i = 0; i < 1000; i++) {
            counter.incr ();
        }
    }

    public static void main(String[] args) throws InterruptedException {
        int num = 1000;
        Counter counter = new Counter ();
        Thread[] threads = new Thread[num];

        for (int i = 0; i < num; i++) {
            threads[i] = new CounterThread (counter);
            threads[i].start ();
        }

        for (int i = 0; i < num; i++) {
            threads[i].join ();
        }

        System.out.println (counter.getCount ());

    }
}

此时查看结果就是期望值了1000000

synchronized实例方法实际上保护的是同一个对象的方法调用。也即当前实例对象,关键字是this。就像demo里的其实是两个counter在同时执行incr()方法。

1.4.2 静态方法demo

public class Counter {
    private static int count;
    public static synchronized void incr(){
        count++;
    }

    public static synchronized int getCount(){
        return count;
    }
}

1.4.3 代码块demo

public class Counter {
    private int count;

    public void incr() {
        synchronized (this) {
            count++;
        }
    }

    public int getCount() {
        synchronized (this) {
            return count;
        }
    }
}

synchronized 同步的对象可以是任意对象,任意对象都有一个锁和等待队列。或者说,任何对象那个都可以作为锁对象。

public class Counter {
    private int count;
    private Object lock = new Object ();

    public void incr() {
        synchronized (lock) {
            count++;
        }
    }

    public int getCount() {
        synchronized (lock) {
            return count;
        }
    }
}
public class StaticCounter {
    private static int count;
   // private Object lock = new Object ();

    public void incr() {
        synchronized (staticCounter.class) {
            count++;
        }
    }

    public int getCount() {
        synchronized (StaticCounter.class) {
            return count;
        }
    }
}

1.4.4 特性

1.可重入性—通过记录锁的持有线程和持有数量来实现的
指对同一个执行线程,他在获得了锁之后,在调用其他需要同样锁的代码是,可以直接调用。
2.内存可见性—对于复杂一些的操作,synchronized 可以实现原子操作,避免出现竞态条件。在释放锁时,所有写入都会协会内存,而获得锁后,都会在内存中读取最新的数据
3.死锁—有a,b两个线程,a持有锁A,b持有锁B,在等待锁A,a和b陷入了相互等待,最后谁都执行不下去。

死锁demo

public class DeadLockDemo {
    private static Object lockA = new Object ();
    private static Object lockB = new Object ();

    private static void startThreadA(){
        Thread aThread = new Thread (){
            @Override
            public void run() {
                synchronized (lockA){
                    try{
                        Thread.sleep (1000);
                    }catch (InterruptedException e){
                        synchronized (lockB){

                        }
                    }
                }
            }
        };
        aThread.start ();
    }

    private static void startThreadB(){
        Thread bThread = new Thread (){
            @Override
            public void run() {
                synchronized (lockB){
                    try {
                        Thread.sleep (1000);
                    }catch (InterruptedException e){

                    }synchronized (lockA){

                    }
                }
            }
        };
        bThread.start ();
    }

    public static void main(String[] args) {
        startThreadA ();
        startThreadB ();
    }
}

1.5 修饰符volatile

public class Swither {
    private volatile boolean on;

    public boolean isOn() {
        return on;
    }
    
    public void setOn(boolean on){
        this.on = on;
    }
}

可以保证内存可见性。使用synchronized 的成本更高,volatile显得更加轻量级。

1.6 协作机制

场景:生产者消费者协作模式、同时开始协作模式、等待结束(主从协作模式)、异步结果、集合点。

demo

public class WaitThread extends Thread {
    private volatile boolean fire = false;

    @Override
    public void run() {
        try {
            synchronized (this) {
                while (!fire) {
                    wait ();
                }
            }
            System.out.println ("fired");
        } catch (InterruptedException e) {

        }
    }

    public synchronized void fire() {
        this.fire = true;
        notify ();
    }

    public static void main(String[] args) throws InterruptedException {
        WaitThread waitThread = new WaitThread ();
        waitThread.start ();
        Thread.sleep (1000);
        System.out.println ("fire");
        waitThread.fire ();

    }
}

fire
fired

这里有两个线程,一个是主线程,一个是WaitThread,协作的条件变量是fire,WaitThread等待变量变为true,在不为true的时候调用wait,主线程设置该变量并调用notify。

1.7 中断机制

停止一个线程的主要机制就是中断,不是强迫终止,而是一种协作机制,给线程传递一个取消的信号,但是由线程来决定如何以及何时退出。

1.7.1 中断的方法

public boolean isInterrupted();
public void interrupt();
public static boolean interrupted();

2. 原子变量

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值