Java基础(多线程2)

五、线程同步(5.1两种实现 5.2一种实现)

5.1、 synchronized对象及方法

有的时候需要几行代码作为整体一起执行,实现线程同步,需要用到synchronized代码块

相关源码及其注释:

package day25;

public class Window implements Runnable {

    private Integer no = 100;//表示票的编号 1~100

    @Override
    public void run() {
        while(true) {
            //this即Window对象,唯一的对象也被称之为锁,也可以写no,必须保证同一时刻只有一个线程拿到它
          /* synchronized (this) {
               if(no <= 0) {
                   break;
               }

               //必须让下面两条语句一块运行才能同步
               System.out.println(Thread.currentThread().getName() + "销售第" + no + "张票");
               no--;
           }*/
            if(no <= 0){
                break;
            }
            sale();

            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
    }

    public synchronized void sale(){
        if(no <= 0) {
            return;
        }
        System.out.println(Thread.currentThread().getName() + "销售第" + no + "张票");
        no--;
    }
}
package day25;

/*
* 1、三个窗口销售同一堆票
* 2、每个编号的票只能被销售一次
* 3、票的编号的范围是1~100
*
* 线程同步机制:
* 1、synonronized代码块
*   synchronized(对象) {
*
*   }
*   1) synchronized代码块()中的对象必须是唯一的对象
*   2)唯一的对象 —— 锁,同一时刻只能有一个线程占据这个唯一的对象
*   3)哪个线程占据了这个对象,哪个线程就可以执行synchronized代码块中的代码,
*      此时其他线程就不能执行synchronized代码块中的代码,陷入阻塞状态
*   4)占据这个唯一的对象的线程执行完synchronized代码块后就会释放这个锁
* 2、synonronized方法 即Window里的sale方法
*
*
* */
public class MyTest {
    public static void main(String[] args) {
        Window w = new Window();

        Thread window1 = new Thread(w);
        Thread window2 = new Thread(w);
        Thread window3 = new Thread(w);

        window1.setName("窗口1");
        window2.setName("窗口2");
        window3.setName("窗口3");

        window1.start();
        window2.start();
        window3.start();
    }
}

5.2、Lock锁

从JDK5.0开始,Java提供了更强大的线程同步机制——通过显示定义同步锁对象来实现同步。同步锁使用Lock对象充当。

Lock接口是控制多个线程对共享资源进行访问的工具。锁提供了对共享资源的独占访问,每次只能有一个线程对Lock对象加锁,线程开始访问共享资源之前应先获得Lock对象。

常用ReentrantLock来加锁实现锁

package day25;

import java.util.concurrent.locks.ReentrantLock;

public class Window implements Runnable {

    private Integer no = 100;//表示票的编号 1~100
    private ReentrantLock lock = new ReentrantLock();

    @Override
    public void run() {
        while(true) {
            //this即Window对象,唯一的对象也被称之为锁,也可以写no,必须保证同一时刻只有一个线程拿到它
          /* synchronized (this) {
               if(no <= 0) {
                   break;
               }

               //必须让下面两条语句一块运行才能同步
               System.out.println(Thread.currentThread().getName() + "销售第" + no + "张票");
               no--;
           }*/
           /* if(no <= 0){
                break;
            }
            sale();*/

            try{
                //加锁
                lock.lock();
                if(no <= 0) {
                    break;
                }
                System.out.println(Thread.currentThread().getName() + "销售第" + no + "张票");
                no--;
            }finally {
                lock.unlock();
            }

            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
    }

    public synchronized void sale(){
        if(no <= 0) {
            return;
        }
        System.out.println(Thread.currentThread().getName() + "销售第" + no + "张票");
        no--;
    }
}

5.3、线程安全的单例模式——懒汉式

package day25;

//懒汉式
public class SingleObject {
    private static SingleObject obj;

    private SingleObject() {

    }

    //加了synchronized关键字就是唯一和排他的了,整体运行完,这样就只会创建一次对象,单例
    synchronized public static SingleObject getObject() {
        if (obj == null) {
            obj = new SingleObject();
        }
        return obj;
    }
}
package day25;

public class MyTest2 {
    private static SingleObject obj1;
    private static SingleObject obj2;

    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                obj1 = SingleObject.getObject();
            }
        });

        Thread t2 = new Thread(new Runnable() {
            @Override
            public void run() {
                obj2 = SingleObject.getObject();
            }
        });

        t1.start();
        t2.start();

        Thread.sleep(1000);

        System.out.println(obj1 == obj2);
    }
}

5.4、死锁

不同的线程分别占用对方需要的同步资源不放弃,都在等待对方放弃自己需要的同步资源,就形成了线程的死锁。

出现死锁后,不会出现异常,不会出现提示,只是所有的线程都处于阻塞状态,无法继续。

相关源码现象展示,运行时会卡住:

package day27;
//发生嵌套同步了
public class A extends Thread{
    @Override
    public void run() {
        while(true){
            synchronized (Lock.m){
                synchronized (Lock.n){
                    System.out.println("A............");
                }
            }
        }
    }
}
package day27;

public class B extends Thread{
    @Override
    public void run() {
        while(true){
            synchronized (Lock.n){
                synchronized (Lock.m){
                    System.out.println("B............");
                }
            }
        }
    }
}
package day27;

public class Lock {
    public static Object m = new Object();
    public static Object n = new Object();
}
package day27;

public class MyTest {
    public static void main(String[] args) {
        A a = new A();
        B b = new B();

        a.start();
        b.start();

    }
}

解决方法:

专门的算法、原则

尽量减少同步资源的定义

尽量避免嵌套同步

六、线程通信

相关方法:

wait():令当前线程挂起并放弃CPU,同步资源并等待,使别的线程可访问并修改共享资源,而当前线程排队等候其他线程调用notify()或notifyAll()方法唤醒,唤醒后等待重新获得对监视器的所有权后才能继续执行。

notify():唤醒正在排队等待同步资源的线程中优先级最高者结束等待;

notifyAll():唤醒正在排队等待资源的所有线程结束等待。

以上三个方法只有在synchronized方法或代码块中才能使用。

6.1、打印数字:

package day28;

//1~20
/*
* wait()
* notify()
* notifyAll()
*
* */
public class PrintNum implements Runnable {

    private Integer i = 1;

    @Override
    public void run() {
        while (true) {
            synchronized (Object.class){
                if(i > 20){
                    break;
                }

                Object.class.notifyAll();//唤醒其他线程,通知别人我要睡了,wait方法

                System.out.println(Thread.currentThread().getName() + ": " + i);
                i++;

                try {
                    Object.class.wait();
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }

            try {
                Thread.sleep(5);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }

        }
    }
}
package day28;

public class MyTest {
    public static void main(String[] args) {
        PrintNum printNum = new PrintNum();
        Thread th1 = new Thread(printNum);
        Thread th2 = new Thread(printNum);

        th1.setName("线程1");
        th2.setName("线程2");

        th1.start();
        th2.start();
    }
}

6.2、生产者消费者问题

此问题也称有限缓冲问题,是一个多线程同步问题的经典案例。

这里需要让生产者在缓冲区满时休眠(要么干脆放弃数据),等到下次消费者消耗缓冲区中的数据的时候,生产者才能被唤醒,开始往缓冲区添加数据。同样,也可以让消费者在缓冲区空时进入休眠,等到生产者往缓冲区添加数据之后,在唤醒消费者。

见源码及其注释:

package day28;

import java.util.ArrayList;
import java.util.Objects;

public class Store {
    public static ArrayList<Object> list= new ArrayList<>(100);
    public static final Integer MAX_NUM = 100;

}
package day28;

/*
* 生产者
* */
public class Producer extends Thread {
    @Override
    public void run() {
        while (true) {
           synchronized (Store.list) {
               if (Store.list.size() < Store.MAX_NUM){
                   Store.list.notifyAll();
                   //生产
                   Object item = new Object();
                   Store.list.add(item);
                   System.out.println(Thread.currentThread().getName() + " - 生产商品,仓库目前数量:" + Store.list.size());
               }else{
                   //停止生产
                   System.out.println(Thread.currentThread().getName() + " - 仓库已满,停止生产" );
                   try {
                       Store.list.wait();
                   } catch (InterruptedException e) {
                       throw new RuntimeException(e);
                   }
               }
           }
        }
    }
}
package day28;

/*
* 消费者
* */
public class Consumer extends Thread {
    @Override
    public void run() {
        while (true) {
            synchronized (Store.list) {
                if (Store.list.size() > 0){
                    Store.list.notifyAll();
                    //消费
                    Store.list.remove(0);
                    System.out.println(Thread.currentThread().getName() + " - 消费商品,仓库目前数量:" + Store.list.size());
                }else{
                    //停止消费
                    System.out.println(Thread.currentThread().getName() + " - 仓库已空,停止消费" );
                    try {
                        Store.list.wait();
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                }
            }
        }
    }
}
package day28;

public class MyTest2 {
    public static void main(String[] args) {
        Producer p = new Producer();
        Consumer c = new Consumer();

        p.start();
        c.start();
    }
}

6.3、wait和sleep的区别

共同点:

wait(),wait(long)和sleep(long)的效果都是让当前线程暂时放弃CPU的使用权,进入阻塞状态。

不同点:

方法归属不同:

sleep(long)是Thread的静态方法

而wait(),wait(long)都是Object的成员方法,每个对象都有

醒来时机不同:

执行sleep(long)和wait(long)的线程都会在等待相应毫秒后醒来

wait(long)和wait()还可以被notify唤醒,wait()如果不唤醒就一直等下去。

锁特性不同(重点)

wait方法的调用必须先获取wait对象的锁,而sleep则无此限制

wait方法执行后会释放对象锁,允许其他线程获得该对象锁(我放弃CPU,但你们还可以用)

而sleep方法如果在synchronized代码块中执行,并不会释放对象锁(我放弃CPU,但你们也不能用)

七、JDK5.0新增线程创建方式

7.1、实现Callable接口

与Runnable相比,Callable功能更强大些:

相比run()方法,可以有返回值;

方法可以抛出异常;

支持泛型的返回值;

需要借助FutureTask类,比如获取返回结果。

package day29;

import java.util.concurrent.Callable;

/*
* 实现Callable接口实现线程
* 1、实现Callable接口 - 泛型是返回值的类型
* 2、重写call方法
* */
public class SumThread implements Callable<Integer> {

    @Override
    public Integer call() throws Exception {
        Integer sum = 0;
        for (int i = 0; i <=100 ; i++) {
            sum+=i;
        }
        return sum;
    }
}
package day29;

import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

public class MyTest {
    public static void main(String[] args)  {
        SumThread sumThread = new SumThread();
        FutureTask<Integer> futureTask = new FutureTask<Integer>(sumThread);
        Thread thread = new Thread(futureTask);

        thread.start();

        try {
            Integer sum = futureTask.get();
            System.out.println(sum);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        } catch (ExecutionException e) {
            throw new RuntimeException(e);
        }
    }
}

7.2、面试题:

Runnable和Callable的区别?

1)Runnable接口run方法没有返回值,Callable接口call方法有返回值,是个泛型,和Future、FutureTask配合可以用来获取异步执行的结果;

2)Callable接口支持返回执行结果,有返回值,需要调用FutureTask.get()得到,此方法会阻塞主进程的继续往下执行,如果不调用不会阻塞;

3)Callable接口的call()方法允许抛出异常,而Runnable接口的run()方法的异常只能在内部消化,不能继续上抛。

7.2、线程池_创建线程,使用过程

池化技术:

线程池

连接池 - 网络连接

内存池

目的:减少对性能消耗比较大的操作(创建),提升系统的工作效率

如何解决:提前创建好,直接拿来用

相关源码:

package day30;

public class Num1Thread implements Runnable{

    @Override
    public void run() {
        for (int i = 0; i <= 100; i++) {
            if (i % 2 == 0) {
                System.out.println(Thread.currentThread().getName() + " " + i);
            }
        }
    }
}
package day30;

public class Num2Thread implements Runnable{

    @Override
    public void run() {
        for (int i = 0; i <= 100; i++) {
            if (i % 2 == 0) {
                System.out.println(Thread.currentThread().getName() + " " + i);
            }
        }
    }
}
package day30;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class MyTest1 {
    public static void main(String[] args) {
        //创建自定义线程类的对象
        Num1Thread num1 = new Num1Thread();
        Num2Thread num2 = new Num2Thread();

        //创建有固定数量线程的线程池
        ExecutorService pool = Executors.newFixedThreadPool(100);
        //通过线程池执行线程
        pool.execute(num1);
        pool.execute(num2);
        //关闭线程池
        pool.shutdown();
    }
}

7.3、Collections_创建线程安全的集合

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值