多线程学习笔记

多线程编程

  • 线程就是独立的执行路径;
  • 在程序运行时,即使没有自己创建线程,后台也会有多个线程,如主线程,gc线程;
    main() 称之为主线程,为系统的入口,用于执行整个程序;
  • 在一个进程中,如果开辟了多个线程,线程的运行由调度器安排调度,调度器是与
  • 操作系统紧密相关的,先后顺序是不能认为的干预的。
  • 对同一份资源操作时, 会存在资源抢夺的问题, 需要加入并发控制;
  • 线程会带来额外的开销,如cpu调度时间,并发控制开销。
  • 每个线程在自己的工作内存交互,内存控制不当会造成数据不一致

如果有错,各位可以指出

进程和线程

进程

是一个正在执行中的程序就是一个进程,系统会为这个进程发配独立的内存资源。

​ 是系统进行资源分配的基本单位

例如:正在运行的 QQ、IDE、浏览器就是进程

线程

又称轻量级进程

进程中的一条执行路径,也是CPU调度和执行的基本单位,一个进程由一个或多个线程组成,

彼此之间完成不同的工作,同时执行,称为多线程。

注意:很多 多线程是模拟出来的,真正的多线程是指有多个cpu,即多核,如服务
器。如果是模拟出来的多线程,即在一个cpu的情况下, 在同一个时间点,cpu只能
执行一个代码,因为切换的很快,所以就有同时执行的错局。

进程与线程的联系

序号进程与线程的联系
(1)一个进程最少拥有一个线程–主线程— 运行起来就执行的线程
(2)线程之间是共享内存资源的(这个内存资源是由进程申请的)
(3)线程之间可以通信(进行数据传递:多数为主线程和子线程)

创建子线程的原因

如果在主线程中存在有比较耗时的操作(例如:下载视频、上传文件 数据处理) 这些操作会阻塞主线程,后面的任务必须等这些任务执行完毕之后才能执行,用户体验比较差, 为了不阻塞主线程,需要将耗时的任务放在子线程去处理。

线程的组成

任何一个线程都具有基本的组成部分:

  • CPU时间片:操作系统(OS)会为每个线程分配执行时间。
  • 运行数据:
    • 堆空间:存储线程需使用的对象,多个线程可以共享堆中的对象。
    • 栈空间:存储线程需使用的局部变量,每个线程都拥有独立的栈。

线程的特点

线程的特点

1、线程抢占式执行

  • 效率高
  • 可防正单一线程长时间独占CPU

2、在单核CPU中,宏观上同时执行,微观上顺序执行。

线程的创建

创建线程的三种方式:

1.继承Thread类

2.实现Runnable接口

3.实现Callable接口

继承Thread类

  • 自定义线程类继承Thread类
  • 重写run() 方法,编写线程的执行体
  • 创建线程对象,调用start()方法启线程
/**
 * 创建线程方式:继承Thread类,重写run() 方法,调用start开启线程
 * 线程开启不一定立即执行,由cpu调度执行
 */
public class MyThread1 extends Thread{

    @Override
    public void run() {
        for(int i=1;i<1000;i++) {
            System.out.println("我是run()方法------"+i);
        }
    }
}
public class Test {

    public static void main(String[] args) {
        //main线程,主线程

        //创建一个线程对象
        MyThread1 thread = new MyThread1();

        //调用start()的方法,开启线程
        thread.start();

        for(int i=1;i<1000;i++) {
            System.out.println("我在学习多线程-----"+i);
        }
    }
}

网图下载

添加依赖

        <dependency>
            <groupId>commons-io</groupId>
            <artifactId>commons-io</artifactId>
            <version>2.6</version>
        </dependency>
import org.apache.commons.io.FileUtils;

import java.io.File;
import java.io.IOException;
import java.net.URL;

//下载器
public class webDownloader {
    //下载方法
    public void downloader(String url,String name){
        try {
            //文件工具类,把网页地址变成一个文件
            FileUtils.copyURLToFile(new URL(url),new File(name));
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

}
public class MyThread2 extends Thread{
    private String name;
    private String url;

    public MyThread2(String url,String name){
        this.url = url;
        this.name = name;
    }

    @Override
    public void run() {
        webDownloader webDownloader = new webDownloader();
        webDownloader.downloader(url,name);
        System.out.println("下载的文件名为"+name);
    }
}
public class Test {

    public static void main(String[] args) {

        //创建线程对象
       MyThread2 myThread1 = new MyThread2("https://dss1.baidu.com/6ONXsjip0QIZ8tyhnq/it/u=2038406380,317017081&fm=5","1.jpg");
       MyThread2 myThread2 = new MyThread2("https://dss1.baidu.com/6ONXsjip0QIZ8tyhnq/it/u=2300875363,445064071&fm=5","2.jpg");
       MyThread2 myThread3 = new MyThread2("https://blog.csdn.net/qian4517?spm=1000.2115.3001.5113","3.txt");

       MyThread2 myThread4 = new MyThread2("https://blog.csdn.net/backbug/article/details/99572931","4.html");
       myThread1.start();
       myThread2.start();
       myThread3.start();
       myThread4.start();
    }
}

实现Runnable接口

  • 自定义线程类实现Runnable接口
  • 实现run() 方法,编写线程的执行体
  • 创建线程对象,调用start()方法启线程
public class MyRunnable implements Runnable{

    @Override
    public void run() {
        for(int i=1;i<1000;i++) {
            System.out.println("我是run()方法------"+i);
        }
    }
}
public class Test {

    public static void main(String[] args) {

        //创建runnable接口的实现类对象
        Runnable runnable = new MyRunnable();

        //创建线程对象,通过线程对象来开启我们的线程,代理
//        Thread t1 = new Thread(runnable);
//        t1.start();

        new Thread(runnable).start();

        for(int i=1;i<1000;i++) {
            System.out.println("我在学习多线程-----"+i);
        }
    }
}

继承Thread类

  • 子类继承Thread类具备多线程能力
  • 启动线程:子类对象. start()
  • 不建议使用:避免0OP单继承局限性

实现Runnable接口

  • 实现接口Runnable具有多线程能力
  • 启动线程:传入目标对象+ Thread对象.start()
  • 推荐使用:避免单继承局限性,灵活方便,方便同- -个对象被多个线程使用

龟兔赛跑

public class Race implements Runnable{

    private static String winner;

    @Override
    public void run() {
        String name = Thread.currentThread().getName();
        for (int i = 0; i <= 100 ; i++) {

            //判断比赛是否结束
            boolean flag  = gameOver(i);
            if(flag){ //比赛结束就停止程序
                break;
            }

            System.out.println(name+"跑了"+i+"米");

            //模拟兔子睡觉
            if(name.equals("兔子") && i>= 100/2){
                try {
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    //判断是否完成了比赛
    private boolean gameOver(int distance){

        //判断是否有胜利者,
        if(winner != null){//存在胜利者
            return true;
        }

        if(distance >= 100){
            winner = Thread.currentThread().getName();;
            System.out.println("胜利者:"+ winner);
            return true;
        }
        return false;
    }

    public static void main(String[] args) {
        //创建runnable接口的实现类对象
        Runnable race = new Race();

        new Thread(race,"乌龟").start();
        new Thread(race,"兔子").start();

        //匿名内部内创建线程
        
        //new 的是Runnable的子类,但是我们没有命名;
//        Runnable runnable = new Runnable() {
//            @Override
//            public void run() {
//
//            }
//        };
//        new Thread(runnable,"线程1");

        //匿名内部类创建一个线程对象,
//        new Thread(new Runnable() {
//            @Override
//            public void run() {
//
//            }
//        },"").start();

    }
}

实现Callable接口

1.实现Callable接口, 需要返回值类型

2.重写call方法,需要抛出异常

3.创建目标对象

4.把Callable对象转成可执行任务:FutureTask task = new FutureTask<>(callable);

5.创建线程对象,调用start()方法启动线程

import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;

public class Mycallable {
    public static void main(String[] args) throws Exception{

        //创建Callable对象,通过匿名内部类
        Callable callable = new Callable<Integer>() {
            @Override
            public Integer call() throws Exception {
                int sum = 0;
                for (int i = 0; i <= 100 ; i++) {
                     sum+=i;
                }
                return sum;
            }
        };

        //把Callable对象转成可执行任务
        FutureTask<Integer> task = new FutureTask<>(callable);

        //创建线程,启动线程
        new Thread(task,"线程1").start();

        //获取返回值,需要捕获或抛出异常
        Integer sum1 =  task.get();
        System.out.println("结果为:"+sum1);
    }
}

1.实现Callable接口, 需要返回值类型
2.重写call方法,需要抛出异常
3.创建目标对象
4.创建执行服务: ExecutorService ser = Executors.newFixedThreadPool(1);
5.提交执行: Future result1 = ser.submit(t1);
6.获取结果: boolean r1 = result1.get()
7.关闭服务: ser.shutdownNow();

静态代理

在实现Runnable接口,创建线程使用了静态代理

/*
 * 使用Runnable创建线程
 * 1.类实现Runnable接口+重写run() --->真实角色类
 * 2.启动多线程 使用静态代理
 * 	1)创建真实角色
 * 	2)创建代理角色+真实角色引用
 * 	3)调用start()方法启动线程
 */
 public class MyRunnable implements Runnable{
    @Override
    public void run() {
    }
}
 
public class Test {

    public static void main(String[] args) {

        //创建runnable接口的实现类对象,真实对象
        Runnable runnable = new MyRunnable();

        //创建线程对象,通过线程对象来开启我们的线程,代理对象
        Thread t1 = new Thread(runnable);
        t1.start();
    }
}

静态代理案例:

//真实对象,出售商品
public class Factory implements Product{

    @Override
    public void sale() {
        System.out.println("卖出生产的产品");
    }
}
//真实对象要干的事
public interface Product {
    //出售
    void sale();
}

//Proxy 代理角色,商品代理商
public class Agent implements Product{

    //目标对象
    private Factory target;

    public Agent(Factory target) {
        this.target = target;
    }

    //代理厂家出售商品
    @Override
    public void sale() {
        before();//出售商品之前
        this.target.sale();//目标对象
        after();//出售商品之后
    }

    private void before() {
        System.out.println("出售商品之前,代理商为商品做广告");
    }

    private void after() {
        System.out.println("出售商品之后,代理商向厂家收取代理费用");
    }
}

/**
 * 静态代理总结
 * 真实对象和代理对象要实现同一接口
 * 代理对象要代理真实角色
 *
 *好处:
 *  代理对象可以做很多真是对象做不了的事
 *  真是对象专注做自己的事情
 */
public class StaticProxy {

    public static void main(String[] args) {
        //创建真实对象
        Factory factory = new Factory();
        //创建代理对象,把真是对象交给代理对象
        Agent agent = new Agent(factory);
        //代理对象为真实对象做事
        agent.sale();
    }

}

线程状态

线程的六种状态

参考:https://blog.csdn.net/pange1991/article/details/53860651

  1. 初始(NEW):新创建了一个线程对象,但还没有调用start()方法。

  2. 运行(RUNNABLE):Java线程中将就绪(ready)和运行中(running)两种状态笼统的称为“运行”。
    线程对象创建后,其他线程(比如main线程)调用了该对象的start()方法。该状态的线程位于可运行线程池中,等待被线程调度选中,获取CPU的使用权,此时处于就绪状态(ready)。就绪状态的线程在获得CPU时间片后变为运行中状态(running)。

  3. 阻塞(BLOCKED):表示线程阻塞于锁。

  4. 等待(WAITING):进入该状态的线程需要等待其他线程做出一些特定动作(通知或中断)。

  5. 超时等待(TIMED_WAITING):该状态不同于WAITING,它可以在指定的时间后自行返回。

  6. 终止(TERMINATED):表示该线程已经执行完毕。

在这里插入图片描述

源码

public enum State {
        /**
         * Thread state for a thread which has not yet started.
         */
        NEW,

        /**
         * Thread state for a runnable thread.  A thread in the runnable
         * state is executing in the Java virtual machine but it may
         * be waiting for other resources from the operating system
         * such as processor.
         */
        RUNNABLE,

        /**
         * Thread state for a thread blocked waiting for a monitor lock.
         * A thread in the blocked state is waiting for a monitor lock
         * to enter a synchronized block/method or
         * reenter a synchronized block/method after calling
         * {@link Object#wait() Object.wait}.
         */
        BLOCKED,

        /**
         * Thread state for a waiting thread.
         * A thread is in the waiting state due to calling one of the
         * following methods:
         * <ul>
         *   <li>{@link Object#wait() Object.wait} with no timeout</li>
         *   <li>{@link #join() Thread.join} with no timeout</li>
         *   <li>{@link LockSupport#park() LockSupport.park}</li>
         * </ul>
         *
         * <p>A thread in the waiting state is waiting for another thread to
         * perform a particular action.
         *
         * For example, a thread that has called <tt>Object.wait()</tt>
         * on an object is waiting for another thread to call
         * <tt>Object.notify()</tt> or <tt>Object.notifyAll()</tt> on
         * that object. A thread that has called <tt>Thread.join()</tt>
         * is waiting for a specified thread to terminate.
         */
        WAITING,

        /**
         * Thread state for a waiting thread with a specified waiting time.
         * A thread is in the timed waiting state due to calling one of
         * the following methods with a specified positive waiting time:
         * <ul>
         *   <li>{@link #sleep Thread.sleep}</li>
         *   <li>{@link Object#wait(long) Object.wait} with timeout</li>
         *   <li>{@link #join(long) Thread.join} with timeout</li>
         *   <li>{@link LockSupport#parkNanos LockSupport.parkNanos}</li>
         *   <li>{@link LockSupport#parkUntil LockSupport.parkUntil}</li>
         * </ul>
         */
        TIMED_WAITING,

        /**
         * Thread state for a terminated thread.
         * The thread has completed execution.
         */
        TERMINATED;
    }

线程的一些方法

方法说明
setPriority(int newPiority)更改线程的优先级
static void sleep(long millis)在指定的毫秒数内让当前正在执行的线程休眠(暂停执行)
void join()强占CPU资源,执行该线程,其他线程等待该线程执行完
static void yield()暂停当前正在执行的线程,释放CPU资源,重新竞争时间片
void interrupt()中断线程,别使用这个方法
boolean isAlive()测试线程是否处于活动状态

线程停止


/**
 * 停止线程
 * 1.建议线程正常停止---> 利用次数,不建议死循环
 * 2.建议使用标志位--->设置一个标志位
 * 3.不要使用stop或者destroy等过时或者jdk不建议使用的方法
 */
public class MyThread implements Runnable{

    //1.设置一个标志位
    private boolean flag = true;

    @Override
    public void run() {
        int i = 0;
        while (flag){
            System.out.println("run.....Thread"+i);
        }
    }

    public void stop(){
        this.flag = false;
    }

    public static void main(String[] args) {
        //创建Runnable实现类的对象
        MyThread myThread = new MyThread();
        //启动线程
        new Thread(myThread).start();

        for (int i = 0; i < 1000; i++) {
            System.out.println("main"+i);
            if( i==900 ){
                //调用stop方法,切换标志位
                myThread.stop();
                System.out.println("线程停止");
            }
        }
    }
}

线程休眠_sleep

sleep (时间)指定当前线程阻塞的毫秒数;"
sleep存在异 常InterruptedException;
sleep时间达到后线程进入就绪状态;
sleep可以模拟网络延时,倒计时等。’
每一个对象都有一 个锁,sleep不会释放锁;

import java.text.SimpleDateFormat;
import java.util.Date;

public class TestSleep {

    public static void main(String[] args){
        timeDown();
        startTime();
    }
    
    //模拟倒计时
    public static void timeDown()  {
        int sumTime = 10;
        while(true){
            System.out.println(sumTime--);
            try {
                Thread.sleep(1000);//休眠1秒
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            if(sumTime == 0){
                break;
            }
        }
    }

    //每隔一秒,获取系统的时间
    public static void startTime(){
        //参数缺省,startTime会获取当前系统时间
       // Date startTime = new Date();
        Date startTime = new Date(System.currentTimeMillis());


        while(true){
            try {
                Thread.sleep(1000);
                System.out.println(new SimpleDateFormat("HH:mm:ss").format(startTime));
                startTime = new Date();//重新获取系统当前时间
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

线程让步_yield

礼让线程,让当前正在执行的线程暂停,但不阻塞
将线程从运行状态转为就绪状态
让cpu重新调度,礼让不一定成功!看CPU心情

/**
 * 测试线程让步方法
 * 让步不一定成功,看CPU调度
 */
public class TestYield implements Runnable{

    public static void main(String[] args) {
        //创建Runnable接口的实现类对象
        TestYield testYield = new TestYield();

        new Thread(testYield,"A").start();
        new Thread(testYield,"B").start();
    }

    @Override
    public void run() {
        //可以多跑几遍
        String name = Thread.currentThread().getName();
        System.out.println(name+"线程开始执行");
        Thread.yield();//线程让步
        System.out.println(name+"线程停止执行");
    }
}

线程强制执行_join

join合并线程,待此线程执行完成后,再执行其他线程,其他线程阻塞

可以想象成插队

/**
 * 测试join方法,想象成插队
 */
public class TestJoin implements Runnable{

    @Override
    public void run() {
        for (int i = 0; i <1000 ; i++) {
            System.out.println("join 线程"+ i);
        }
    }

    public static void main(String[] args) throws InterruptedException {

        TestJoin testJoin = new TestJoin();
        Thread thread = new Thread(testJoin);
        thread.start();//启动

        //主线程执行
        for (int i = 0; i < 100; i++) {
            if(i==50){
                //抢占CPU资源,优先执行
                thread.join(); //插队
            }
            System.out.println("主线程执行"+i);
        }
    }
}

线程优先级

Java提供一个线程调度器来监控程序中启动后进入就绪状态的所有线程,线程调度器按照优先级决定应该调度哪个线程来执行。
线程的优先级用数字表示,范围从1~10.
Thread.MIN_ PRIORITY = 1;
Thread.MAX_ PRIORITY = 10;
Thread.NORM_ PRIORITY = 5;
使用以下方式改变或获取优先级
getPriority() . setPriority(int xxx)

优先级的设定建议再start() 调度之前

优先级低只是意味着获得调度的概率低,并不是优先级低就不会被调用了,这要看CPU的调度,也有可能被调度在优先级高的线程之前。

public class TestPriority implements Runnable{

    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName()+"--->"+Thread.currentThread().getPriority());
    }
}
public class Test {

    public static void main(String[] args) {
        //主线程默认优先级
        System.out.println(Thread.currentThread().getName()+"--->"+Thread.currentThread().getPriority());
        TestPriority testPriority = new TestPriority();
        Thread thread1 = new Thread(testPriority);
        Thread thread2 = new Thread(testPriority);
        Thread thread3 = new Thread(testPriority);
        Thread thread4 = new Thread(testPriority);
        Thread thread5 = new Thread(testPriority);
        Thread thread6 = new Thread(testPriority);

        //设置线程优先级
        thread2.setPriority(4);
        thread3.setPriority(Thread.MIN_PRIORITY);
        thread4.setPriority(Thread.MAX_PRIORITY);
        thread5.setPriority(6);
        thread6.setPriority(7);

        //设置优先级,再启动线程
        thread1.start();
        thread2.start();
        thread3.start();
        thread4.start();
        thread5.start();
        thread6.start();
    }
}

守护线程(daemon)

线程分为用户线程和守护线程

虚拟机必须确保用户线程执行完毕

虚拟机不用等待守护线程执行完毕

如:后台记录操作日志,监控内存,垃圾回收等…

//用户线程
public class UserThread implements Runnable{

    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println("我是用户线程,在活动"+i);
        }
        System.out.println("我结束了");
    }
}
//守护线程
public class DaemonThread implements Runnable{

    @Override
    public void run() {
        while(true){
            System.out.println("我是守护线程,一直在");
        }
    }
}
public class Test {

    public static void main(String[] args) {
        //创建守护线程对象
        DaemonThread daemonThread = new DaemonThread();
        //创建用户线程对象
        UserThread userThread = new UserThread();


        Thread daemon = new Thread(daemonThread);
        //默认为false,表示用户线程,一般线程都是用户线程
        daemon.setDaemon(true);//定义该线程为守线程
        daemon.start();

        Thread user = new Thread(userThread);
        user.start();
    }
}

线程同步

同步方法

由于我们可以通过private关键字来保证数据对象只能被方法访问,所以我们只需
要针对方法提出一套机制 ,这套机制就是synchronized关键字,它包括两种用法:
synchronized方法和synchronized块.

同步方法: public synchronized void method(int args) {}

synchronized方法控制对“对象”的访问,每个对象对应一把锁,每个
synchronized方法都必须获得调用该方法的对象的锁才能执行,否则线程会阻塞,
方法一旦执行,就独占该锁,直到该方法返回才释放锁,后面被阻塞的线程才能获
得这个锁,继续执行

缺陷:若将一个大的方法申明为synchronized将会影响效率

对于一个方法中,如果有只读代码和修改数据代码,但是修改的内容才需要加锁,

锁的太多,就浪费了资源

同步块

同步块: synchronized (Obj ){}
Obj 称之为同步监视器
Obj 可以是任何对象,但是推荐使用共享资源作为同步监视器
同步方法中无需指定同步监视器,因为同步方法的同步监视器就是this ,就是
这个对象本身,或者是class [反射中讲解]
同步监视器的执行过程

  1. 第一个线程访问,锁定同步监视器,执行其中代码.
  2. 第二个线程访问 ,发现同步监视器被锁定,无法访问.
  3. 第一个线程访问完毕,解锁同步监视器.
  4. 第二个线程访问,发现同步监视器没有锁,然后锁定并访问

案例:1. 网上购票

//线程不安全,购买车票,需要使用线程同步
public class BuyTickets implements Runnable{

    private int ticketNums = 10;

    private boolean flag = true;//标志位

    @Override
    public void run() {
        while (flag) {
            buy();
        }
    }
    //synchronized 同步方法,锁定的this
    private synchronized void buy(){

            //判断是否有票
            if(ticketNums<=0){
                System.out.println("余票不足");
                flag = false;
                return ; //结束当前程序块
            }

            //模拟延时
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            System.out.println(Thread.currentThread().getName()+"购买了第"+ticketNums-- +"票");
    }
}
public class Test {

    public static void main(String[] args) {

        Runnable buyTickets = new BuyTickets();
        //模拟三个人同时购票
        new Thread(buyTickets,"A---").start();
        new Thread(buyTickets,"B---").start();
        new Thread(buyTickets,"C---").start();
    }
}

2.银行取钱

//账户
public class Account {

    private String name; //账户名
    private double RemainMoney;//余额

    public Account(String name, double remainMoney) {
        this.name = name;
        RemainMoney = remainMoney;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public double getRemainMoney() {
        return RemainMoney;
    }

    public void setRemainMoney(double remainMoney) {
        RemainMoney = remainMoney;
    }
}
//不安全的取钱
//两个人同时取同一个账户的钱
//原因:两个线程把账户拿到各自的内存中,不共享,同时操作了原数据
public class UnsafeBank extends Thread{

    Account account;//账户
    //取多少钱
    double drawMoney;
    //现在手里有多少钱
    double nowMoney;

    public UnsafeBank(Account account,double drawMoney,String name){
        super(name);
        this.account = account;
        this.drawMoney = drawMoney;
    }



    //取钱
    @Override
    public void run() {

        Drawing();

    }

    public Account Drawing(){

        //锁的对象是变化的量
        synchronized (account) {
            if (account.getRemainMoney() - drawMoney <= 0) {
                System.out.println(Thread.currentThread().getName() + "取钱,不好意思,余额不足");
                return account;
            }

            //确保两人都看到未取钱前的余额
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            // 取完后,账户剩下的余额
            account.setRemainMoney(account.getRemainMoney() - drawMoney);
            //手里的钱
            nowMoney = nowMoney + drawMoney;

            System.out.println(Thread.currentThread().getName() + "手里的钱为" + nowMoney);

            System.out.println(account.getName() + "的账户余额为" + account.getRemainMoney());


            return account;
        }
    }
}
public class Test {

    public static void main(String[] args) {
        //创建一个账户对象
        Account account = new Account("小明",200);

        //新建两个取钱对象
        UnsafeBank you = new UnsafeBank(account,50,"你");
        UnsafeBank girlFriend = new UnsafeBank(account,100,"girlFriend");

        you.start();
        girlFriend.start();
    }

}

3.不安全的集合

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

//线程不安全的集合
//两个线程操作了同一个位置
public class unsafeList {

    public static void main(String[] args) throws InterruptedException {
        List<String> list = new ArrayList<>();
            for (int i = 0; i < 10000; i++) {
                //使用线程向集合中加入数据
                //使用lambda表达式
                new Thread(() -> {
                    synchronized (list) {
                        list.add(Thread.currentThread().getName());
                    }
                }).start();
            }
		//等待数据添加完全
        Thread.sleep(3000);
        //会出现小于10000的情况;
        System.out.println(list.size());
    }
}

死锁

多个线程各自占有一-些共享资源,并且互相等待其他线程占有的资源才能运行,而
导致两个或者多个线程都在等待对方释放资源,都停止执行的情形.某一个同步块
同时拥有“两个以上对象的锁”时,就可能会发生“死锁”的问题.

public class DeadLock {

    public static void main(String[] args) {
        //两把锁
        String A = "a";
        String B = "b";

        new Thread(()->{
            String name = Thread.currentThread().getName();
            //先获得A锁
            synchronized (A){
                System.out.println(name+"获得了锁"+A+",等待获得锁"+B);

                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                //再获得B锁
                synchronized (B){
                    System.out.println(name+"获得了锁"+B+",运行结束");
                }
            }
           
        },"线程1").start();


        new Thread(()->{
            String name = Thread.currentThread().getName();
            //先获得B锁
            synchronized (B){
                System.out.println(name+"获得了锁"+B+",等待获得锁"+A);

                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                //再获得A锁
                synchronized (A){
                    System.out.println(name+"获得了锁"+A+",运行结束");
                }
            }
           
        },"线程2").start();

    }
}

死锁避免方法

产生死锁的四个必要条件:

  1. 互斥条件: 一个资源每次只能被一个进程使用。
  2. 请求与保持条件: 一个进程因请求资源而阻塞时,对已获得的资源保持不放。
  3. 不剥夺条件 :进程已获得的资源,在未使用完之前,不能强行剥夺。
  4. 循环等待条件:若干进程之间形成-种头尾相接的循环等待资源关系。

上面列出了死锁的四个必要条件,我们只要想办法破其中的任意一个或多 个条件
就可以避免死锁发生

Lock(锁)

  • 从JDK 5.0开始,Java提供了更强大的线程同步机制一通过 显式定义同步锁对象来实现同步。同步锁使用Lock对象充当

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

  • ReentrantLock(可重入锁) 类实现了Lock,它拥有与synchronized相同的并发性和内存语义,在实现线程安全的控制中,比较常用的是ReentrantLock,可以显式加锁、释放锁。

public class ReentrantLockTest {

    public static void main(String[] args) {
        Ticket ticket = new Ticket();

        new Thread(ticket).start();
        new Thread(ticket).start();
        new Thread(ticket).start();
    }


}

class Ticket implements Runnable{

    int num = 10;
    //定义lock锁
    private final ReentrantLock lock = new ReentrantLock();

    @Override
    public void run() {

        while(true) {

            try {//推荐使用
                lock.lock();//加锁
                if (num > 0) {
                    try {
                        //增加问题发生的概率
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(num--);
                } else {
                    break;
                }
            } finally {
                lock.unlock();//释放锁
            }

        }
    }
}

synchronized和lock区别

Lock是显式锁(手动开启和关闭锁,别忘记关闭锁) synchronized是隐式锁, 出了
作用域自动释放
Lock只有代码块锁,synchronized有代码块锁和方法锁
使用Lock锁,JVM将花费较少的时间来调度线程,性能更好。并且具有更好的扩展
性(提供更多的子类)
优先使用顺序:
Lock >同步代码块(已经进入了方法体,分配了相应资源) >同步方法(在方法体之外)

线程的交互

这三个方法是 Object 对象的不是线程的方法

在开始讲解等待唤醒机制之前,有必要搞清一个概念 - -线程之间的通信:多个线程在处理同
一个资源,但是处理的动作(线程的任务)却不相同。通过一定的手段使各个线程能有效的利用资
源。而这种手段即-----等待唤醒机制 。
I
等待唤醒机制所涉及到的方法:
wait () :等待, 将正在执行的线程释放其执行资格和执行权,并存储到线程池中。
notify () :唤醒,唤醒线程池中被wait ()的线程,-一次唤醒一个,而且是任意的。
notifyAll () :唤醒全部: 可以将线程池中的所有wait()线程都唤醒。
其实,所谓唤醒的意思就是让线程池中的线程具备执行资格。必须注意的是,这些方法都是在
同步中才有效。同时这些方法在使用时必须标明所属锁,这样才可以明确出这些方法操作的到底是
哪个锁上的线程。

交替打印AB

public class Test {
        public static void main(String[] args) {

            String lock = "lock";

            //lambda表达式
            new Thread(() -> {
                //同步代码块
                synchronized (lock) {
                    try {
                        for (int i = 1; i <= 5; i++) {
                            System.out.println(Thread.currentThread().getName() + "-" + "A");
                            if(i !=6) lock.notify();//唤醒
                            if(i !=5) lock.wait();//等待,释放锁

                        }
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
            }, "线程A").start();

            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            new Thread(() -> {
                //同步代码块
                synchronized (lock) {
                    try {
                        for (int i = 1; i <= 5; i++) {
                            System.out.println(Thread.currentThread().getName() + "-" + "B");
                            if(i != 5) lock.notify();//唤醒
                            if(i !=5) lock.wait();//等待
                        }
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }

            }, "线程B").start();
        }
}

等待与唤醒,案例2

/**
 * 定义一个资源类,有两个成员变量
 * 同时有两个线程,对资源中的变量进行操作
 * 一个对变量赋值
 * 一个对变量进行输出打印
 *
 * flag = true 表示赋值完成。等待输出
 * flag = false 表示输出值完成,等待赋值
 */
public class Resource {

    public String name;
    public String sex;
    public boolean flag = false;
}
/**
 * 输入线程,对资源对象Resource的成员变量进行赋值
 *
 * 需不需要执行赋值语句,看标记,如果标记为true等待
 * 如果为false不需要等待,进行赋值
 */
public class Input implements Runnable{
    //定义Resource对象
    private Resource r;

    public Input(Resource r){
        this.r = r;
    }

    @Override
    public void run() {
        int i = 0;
        while(true){
            //交叉打印
            synchronized (r) {

                //flag = true ,等待
                if(r.flag){
                    try {
                        r.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                if (i % 2 == 0) {
                    r.name = "张三";
                    r.sex = "男";
                } else {
                    r.name = "lisi";
                    r.sex = "nv";
                }
                i++;
                //将对方线程唤醒,标记改为true,表示赋值完成
                r.flag = true;
                r.notify();//唤醒

            }
        }
    }

}

/**
 * 输出线程,对资源对象Resource的成员变量,输出值
 *
 * 如果 flag = true ,表示input完成了赋值,需要进行输出
 * 输出完成后,把flag 改为 false,表示要进行赋值
 */
public class Output implements Runnable{

    private Resource r;

    public Output(Resource r){
        this.r = r;
    }

    @Override
    public void run() {
        while(true){
            synchronized (r) {

                // 判断标记,是false,等待
                if(!r.flag){
                    try {
                        r.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                System.out.println(r.name + "..." + r.sex);
                //标记改成false,唤醒对方线程
                r.flag = false;
                r.notify();

            }
        }
    }
}
public class Test {
    public static void main(String[] args) {

        Resource r = new Resource();

        //创建Runnable实现类对象
        Input input = new Input(r);
        Output output = new Output(r);

        new Thread(input).start();
        new Thread(output).start();
    }
}

生产者和消费者

并发协作模型 ”生产者/消费者模式” -->管程法
◆生产者:负责生产数据的模块(可能是方法,对象,线程,进程);
◆消费者:负责处理数据的模块(可能是方法,对象,线程,进程);
◆缓冲区:消费者不能直接使用生产者的数据,他们之间有个“缓冲区
生产者将生产好的数据放入缓冲区,消费者从缓冲区拿出数据
在这里插入图片描述

//产品
public class Bread {

    public int id;
    
    public Bread(int id) {
        this.id = id;
    }
}
//缓冲区
public class SynContainer {

    //需要一个容器大小
    private   Bread[] breads = new Bread[10];
    //容器计数器
    int count = 0;

    //生产者放入产品
    public synchronized void push(Bread bread){
        //如果容器满了,就需要等待消费者消费
        if (count >= breads.length){
            //通知消费者消费,生产者等待
            try{
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        //如果没有满,我们就需要丢入产品
        breads[count] = bread;
        System.out.println("生产了第"+bread.id+"片面包");
        count++;

        //通知消费者消费
        this.notify();
    }

    //消费者消费产品
    public synchronized Bread pop(){
        //判断能否消费
        if(count <= 0){
            //等待生产者生产,消费者等待

            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        //如果可以消费
        count --;
        Bread bread = breads[count];
        System.out.println("消费了--->第"+bread.id+"片面包");

        //吃完了,通知生产者生产
        this.notify();

        return bread;
    }
}
//消费者
public class Consumer extends Thread{

    SynContainer synContainer;

    public Consumer( SynContainer synContainer){
        this.synContainer = synContainer;
    }

    //消费

    @Override
    public void run() {
        for (int i = 1; i < 100; i++) {
            synContainer.pop();
        }
    }
}
//生产者
public class Producer extends Thread{

    SynContainer synContainer;

    public Producer( SynContainer synContainer){
        this.synContainer = synContainer;
    }

    //生产
    @Override
    public void run() {
        for (int i = 1; i <100 ; i++) {
            synContainer.push(new Bread(i));
        }
    }
}
public class Test {

    public static void main(String[] args) {
        SynContainer synContainer = new SynContainer();

        new Producer(synContainer).start();
        new Consumer(synContainer).start();
    }
}

线程池

背景:经常创建和销毁、使用量特别大的资源,比如并发情况下的线程,对性能影响很大。
思路:提前创建好多个线程,放入线程池中,使用时直接获取,使用完放回池中。
可以避免频繁创建销毁、实现重复利用。类似生活中的公共交通工具~书

  • 好处:
    1. 提高响应速度(减少了创建新线程的时间)
    2. 降低资源消耗(重复利用线程池中线程,不需要每次都创建)
    3. 便于线程管理(…)
      • corePoolSize: 核心池的大小
      • maximumPoolSize: 最大线程数
      • keepAliveTime:线程没有任务时最多保持多长时间后会终止

线程池使用

JDK 5.0起提供了线程池相关API: ExecutorServiseExecutors

ExecutorService: 真正的线程池接口。常见子类ThreadPoolExecutor

  • void execute(Runnable command) :执行任务/命令,没有返回值,一般用来执
    行Runnable

  • Future submit(Callable task):执行任务,有返回值,一般用来执行
    Callable

  • void shutdown() :关闭连接池

Executors: 工具类、线程池的工厂类,用于创建并返回不同类型的线程池

import java.util.concurrent.Callable;

public class CPool implements Callable<String> {
    @Override
    public String call() throws Exception {
        return Thread.currentThread().getName();
    }
}
public class RPool implements Runnable{

    @Override
    public void run() {
        System.out.println( Thread.currentThread().getName());
    }
}
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

public class Test {

    public static void main(String[] args) throws ExecutionException, InterruptedException {


        //1.创建服务,2.创建线程池
        //参数为线程池大小
        ExecutorService service = Executors.newFixedThreadPool(2);

        //执行 实现Runnable的,直接从线程池中获取,看到两条线程的name
        service.execute(new RPool());
        service.execute(new RPool());
        service.execute(new RPool());
        service.execute(new RPool());

        //实现Callable的
        Future<String> future1 = service.submit(new CPool());
        Future<String> future2 = service.submit(new CPool());
        Future<String> future3 = service.submit(new CPool());
        Future<String> future4 = service.submit(new CPool());

        //获取线程池大小
        System.out.println(future1.get());
        System.out.println(future2.get());
        System.out.println(future3.get());
        System.out.println(future4.get());

        //关闭服务
        service.shutdown();
    }
}

参考视频:视频

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值