多线程学习笔记(最全)

基本概念

进程是电脑程序运行的最小单位,而线程则是进程的最小执行单位
多线程呢就是指一个进程运行时,多个线程并行交替执行的过程。可以提高代码的执行效离

实现方式

1.继承thread类
特点:因为是通过继承的方式,所以该类就不能继承其他的类了,有局限性
使用:直接使用该类调用start方法
2.实现Runnable接口
特点:通过实现接口的方式避免了第一种方式的单继承的局限性问题
使用:new Thread(实现了Runnable的类对象).start()
3.实现Callnable接口

解决多线程安全

1.使用synchronized

synchronized的三种应用方式

修饰实例方法,作用于当前实例加锁,进入同步代码前要获得当前实例的锁

修饰静态方法,作用于当前类对象加锁,进入同步代码前要获得当前类对象的锁

修饰代码块,指定加锁对象,对给定对象加锁,进入同步代码库前要获得给定对象的锁。
多线程学习笔记(最全)_第1张图片

多线程学习笔记(最全)_第2张图片

public class a {
     
    //方法1
    public void display1(Thread t) {
     
        synchronized (this){
     
            System.out.println(t.getName());
            for (int i = 0; i < 100; i++) {
     
                System.out.println(i);
            }
        }
    }

    //方法2
    public synchronized void display2(Thread t) {
     
            System.out.println(t.getName());
            for (int i = 0; i < 100; i++) {
     
                System.out.println(i);
        }
    }
    public static void main(String[] args) {
     
        a a = new a();
        Thread thread1 = new Thread(new Runnable() {
     
            @Override
            public void run() {
     
                a.display2(Thread.currentThread());
            }
        },"a1");
        Thread thread2 = new Thread(new Runnable() {
     
            @Override
            public void run() {
     
                a.display2(Thread.currentThread());
            }
        },"a2");
         thread1.start();
         thread2.start();
    }
}
2.使用lock锁

举例:


public class a {
     
    Lock lock = new ReentrantLock(); //用于方法1
    ReentrantReadWriteLock   locks = new ReentrantReadWriteLock  (); //用于方法2
    
    //方法1   一般要写成try catch块的形式,把lock.unclock()方法放到finally里面
    public void display1(Thread t) {
     
        lock.lock();
            System.out.println(t.getName()+":获取到了锁");
            for (int i = 0; i < 100; i++) {
     
                System.out.println(i);
            }
        lock.unlock();
    }

    //方法2
    public void display2(Thread t) {
     
        locks.writeLock().lock();
//        locks.writeLock().lock();
        try{
     
            System.out.println(t.getName()+":获取到了锁");
            for (int i = 0; i < 100; i++) {
     
                System.out.println(i);
            }
        }catch (Exception e){
     
           e.printStackTrace();
        }finally {
     
            locks.writeLock().unlock();
//            locks.writeLock().unlock();
        }
    }


    //方法3   锁降级
    public void display4(Thread t) {
     
        locks.writeLock().lock();
        locks.readLock().lock();
        try{
     
            System.out.println(t.getName()+":获取到了锁");
            for (int i = 0; i < 100; i++) {
     
                System.out.println(i);
            }
        }catch (Exception e){
     
           e.printStackTrace();
        }finally {
     
            locks.readLock().unlock();
            locks.writeLock().unlock();
        }
    }

    //方法4   锁升级  (会产生死锁问题)
    public void display4(Thread t) {
     
    	locks.readLock().lock();
        locks.writeLock().lock();
        try{
     
            System.out.println(t.getName()+":获取到了锁");
            for (int i = 0; i < 100; i++) {
     
                System.out.println(i);
            }
        }catch (Exception e){
     
           e.printStackTrace();
        }finally {
     
        	locks.writeLock().unlock();
            locks.readLock().unlock();
        }
    }

    public static void main(String[] args) {
     
        a a = new a();
        Thread thread1 = new Thread(new Runnable() {
     
            @Override
            public void run() {
     
                a.display1(Thread.currentThread());
            }
        },"a1");
        Thread thread2 = new Thread(new Runnable() {
     
            @Override
            public void run() {
     
                a.display1(Thread.currentThread());
            }
        },"a2");
         thread1.start();
         thread2.start();
    }
}

注意
使用lock锁时,lock必须定义到类里面,这样线程调用的时候拿到的是同一个lock对象,这样才能锁住

用法

lock实现ReentrantLock

常用方法及用处:
lock() 获取锁,获取不到会一直等待
trylock() 返回值是boolean类型,拿到锁就返回true,没有拿到就返回false,带时间限制的tryLock(),拿不到lock,就等一段时间,超时返回false
unlock() 释放锁

lock实现ReetrantReadWriteLock

读写锁的特性
1.获取锁顺序问题
非公平模式:读锁和写锁的获取的顺序是不确定的,可能会延缓一个或多个读或写线程,但是会比公平锁有更高的吞吐量
公平模式:线程将会以队列的顺序获取锁,等待锁时间越长的先获取
2.可重入:就是说一个线程在获取某个锁后,还可以继续获取该锁。synchronized内置锁就是可重入的,如果不可重入,那么比如一个类的两个方法都被synchronized所修饰,那么方法A就不能够调方法B了。
3.锁降级
首先要知道
锁升级:从读锁变成写锁
锁降级:从写锁变成读锁;
ReetrantReadWriteLock是不支持锁升级的,会产生死锁问题
4.读锁跟写锁是互斥的
对比synchronized使用,测试读操作效率

public class ReadAndWriteLockTest {
     
    public synchronized static void get(Thread thread) {
     
        System.out.println("start time:" + System.currentTimeMillis());
        for (int i = 0; i < 5; i++) {
     
            try {
     
                Thread.sleep(20);
            } catch (InterruptedException e) {
     
                e.printStackTrace();
            }
            System.out.println(thread.getName() + ":正在进行读操作……");
        }
        System.out.println(thread.getName() + ":读操作完毕!");
        System.out.println("end time:" + System.currentTimeMillis());
    }

    public static void main(String[] args) {
     
        new Thread(new Runnable() {
     
            @Override
            public void run() {
     
                get(Thread.currentThread());
            }
        }).start();

        new Thread(new Runnable() {
     
            @Override
            public void run() {
     
                get(Thread.currentThread());
            }
        }).start();
    }
}

多线程学习笔记(最全)_第3张图片

从运行结果可以看出,两个线程的读操作是顺序执行的,整个过程大概耗时200ms。
public class ReadAndWriteLockTest {
     
    ReentrantReadWriteLock lock = new ReentrantReadWriteLock();

    public static void get(Thread thread) {
     
        lock.readLock().lock();
        System.out.println("start time:" + System.currentTimeMillis());
        for (int i = 0; i < 5; i++) {
     
            try {
     
                Thread.sleep(20);
            } catch (InterruptedException e) {
     
                e.printStackTrace();
            }
            System.out.println(thread.getName() + ":正在进行读操作……");
        }
        System.out.println(thread.getName() + ":读操作完毕!");
        System.out.println("end time:" + System.currentTimeMillis());
        lock.readLock().unlock();
    }

    public static void main(String[] args) {
     
        new Thread(new Runnable() {
     
            @Override
            public void run() {
     
                get(Thread.currentThread());
            }
        }).start();

        new Thread(new Runnable() {
     
            @Override
            public void run() {
     
                get(Thread.currentThread());
            }
        }).start();
    }
}

多线程学习笔记(最全)_第4张图片

从运行结果可以看出,两个线程的读操作是同时执行的,整个过程大概耗时100ms。通过两次实验的对比,我们可以看出来,ReetrantReadWriteLock的效率明显高于Synchronized关键字

判断读锁跟写锁互斥

public class ReadAndWriteLockTest {
     

    public static ReentrantReadWriteLock lock = new ReentrantReadWriteLock();

    public static void main(String[] args) {
     
        //同时读、写
        ExecutorService service = Executors.newCachedThreadPool();
        service.execute(new Runnable() {
     
            @Override
            public void run() {
     
                readFile(Thread.currentThread());
            }
        });
        service.execute(new Runnable() {
     
            @Override
            public void run() {
     
                writeFile(Thread.currentThread());
            }
        });
    }

    // 读操作
    public static void readFile(Thread thread) {
     
        lock.readLock().lock();
        boolean readLock = lock.isWriteLocked();
        if (!readLock) {
     
            System.out.println("当前为读锁!");
        }
        try {
     
            for (int i = 0; i < 5; i++) {
     
                try {
     
                    Thread.sleep(20);
                } catch (InterruptedException e) {
     
                    e.printStackTrace();
                }
                System.out.println(thread.getName() + ":正在进行读操作……");
            }
            System.out.println(thread.getName() + ":读操作完毕!");
        } finally {
     
            System.out.println("释放读锁!");
            lock.readLock().unlock();
        }
    }

    // 写操作
    public static void writeFile(Thread thread) {
     
        lock.writeLock().lock();
        boolean writeLock = lock.isWriteLocked();
        if (writeLock) {
     
            System.out.println("当前为写锁!");
        }
        try {
     
            for (int i = 0; i < 5; i++) {
     
                try {
     
                    Thread.sleep(20);
                } catch (InterruptedException e) {
     
                    e.printStackTrace();
                }
                System.out.println(thread.getName() + ":正在进行写操作……");
            }
            System.out.println(thread.getName() + ":写操作完毕!");
        } finally {
     
            System.out.println("释放写锁!");
            lock.writeLock().unlock();
        }
    }
}

多线程学习笔记(最全)_第5张图片
总结
1.Java并发库中ReetrantReadWriteLock实现了ReadWriteLock接口并添加了可重入的特性
2.ReetrantReadWriteLock读写锁的效率明显高于synchronized关键字
3.ReetrantReadWriteLock读写锁的实现中,读锁使用共享模式;写锁使用独占模式,换句话说,读锁可以在没有写锁的时候被多个线程同时持有,写锁是独占的
4.ReetrantReadWriteLock读写锁的实现中,需要注意的,当有读锁时,写锁就不能获得;而当有写锁时,除了获得写锁的这个线程可以获得读锁外,其他线程不能获得读锁


Condition

经典用例:可以实现生产者跟消费者的实例

lock可以使用Condition,Condition是一个多线程间协调通信的工具类,要与一个lock绑定使用

3.volatile关键字的使用

①volatile变量是Java语言提供了一种稍弱的同步机制
②volatile能够保证字段的可见性
②volatile能够禁止指令重排
原理:volatile变量不会被缓存在寄存器或者对其他处理器不可见的地方,因此在读取volatile类型的变量时总会返回最新写入的值
究竟能否保证线程安全?
答案是在一定条件下能,1.写入值时不依赖于当前值,不包含其他变量
经典用法
做状态标记量

 volatile boolean flag ;
    ……
    while!flag ){
     
        ……
    }
    public void setFlag() {
     
       flag = true;
    }

synchronized 跟lock的区别

synchronized是java的一个关键字,是内置的语言实现的,是用来解决多线程安全问题的,他不能去获取到锁状态,当遇到异常的时候会自动的释放掉锁,synchronized使用Object对象本身的wait 、notify、notifyAll调度机制,而Lock可以使用Condition进行线程之间的调度,Synchronized存在明显的一个性能问题就是读与读之间互斥

lock是一个接口,
lock等待锁过程中可以用interrupt来中断等待,而synchronized只能等待锁的释放,不能响应中断;

Lock可以通过trylock来知道有没有获取锁,而synchronized不能;

Lock可以提高多个线程进行读操作的效率。(可以通过readwritelock实现读写分离)

在性能上来说,如果竞争资源不激烈,两者的性能是差不多的,而当竞争资源非常激烈时(即有大量线程同时竞争),此时Lock的性能要远远优于synchronized。所以说,在具体使用时要根据适当情况选择。

思考

为什么在分布式情况下synchronized和lock会失效?
因为他们的前提条件必须是同一进程内,而分布式服务里面,多个服务属于不同进程,所以用普通的同步锁会失效,这里就要用到分布式锁去处理

分布式锁

你可能感兴趣的:(多线程,java,并发编程)