Java 多线程。

Java 多线程。



概念。

线程就是独立的执行路径。

在程序运行时,即使没有自己创建线程,后台也会有多个线程(eg. 主线程,gc 线程)。

main() 称之为主线程,为系统的入口,用于执行整个程序。

在一个进程中,如果开辟了多个线程,线程的运行由调度器安排调度,调度器是与操作系统紧密相关的,先后顺序是不能人为干预的。

对同一份资源操作时,会存在资源抢夺问题,需要加入并发控制。

线程会带来额外的开销,eg. CPU 调度时间,并发控制开销。

每个线程在自己的工作内存交互,内存控制不当会造成数据不一致。



线程创建 ~ 继承 Thread 类。

  • 自定义线程类继承 Thread 类。

  • 重写 run(); 方法,编写线程执行体。

  • 创建线程对象,调用 start(); 方法启动线程。

package com.geek;

/**
 * 线程创建 ~ 继承 Thread 类。
 * <p>
 * - 自定义线程类继承 Thread 类。
 * <p>
 * - 重写 run(); 方法,编写线程执行体。
 * <p>
 * - 创建线程对象,调用 start(); 方法启动线程。
 */

// 线程开启不一定立即执行,由 CPU 调度执行。

public class ThreadDemo00 extends Thread {

    public static void main(String[] args) {

        // 创建一个线程对象。
        ThreadDemo00 threadDemo00 = new ThreadDemo00();
        // 调用 start(); 方法开启线程。
        threadDemo00.start();

        // main 线程,主线程。
        for (int i = 0; i < 200; i++) {
            System.out.println("主线程 ~ " + i);
        }
    }

    @Override
    public void run() {
//        super.run();
        for (int i = 0; i < 20; i++) {
            System.out.println("~" + i);
        }
    }

}

import org.apache.commons.io.FileUtils;

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

public class TestThreadDownload extends Thread {

    private String url;// 网络图片地址。
    private String name;// 保存的文件名。

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

    public static void main(String[] args) {
        TestThreadDownload t1 = new TestThreadDownload("https://bkimg.cdn.bcebos.com/pic/0b55b319ebc4b74595cd99c0c3fc1e178b82158e?x-bce-process=image/watermark,image_d2F0ZXIvYmFpa2UyNzI=,g_7,xp_5,yp_5", "1.jpg");
        TestThreadDownload t2 = new TestThreadDownload("https://bkimg.cdn.bcebos.com/pic/0b55b319ebc4b74595cd99c0c3fc1e178b82158e?x-bce-process=image/watermark,image_d2F0ZXIvYmFpa2UyNzI=,g_7,xp_5,yp_5", "2.jpg");
        TestThreadDownload t3 = new TestThreadDownload("https://bkimg.cdn.bcebos.com/pic/0b55b319ebc4b74595cd99c0c3fc1e178b82158e?x-bce-process=image/watermark,image_d2F0ZXIvYmFpa2UyNzI=,g_7,xp_5,yp_5", "3.jpg");
        t1.start();
        t2.start();
        t3.start();
    }

    @Override
    public void run() {
        WebDownloader webDownloader = new WebDownloader();
        webDownloader.downloader(url, name);
        System.out.println("下载了文件:" + name);
    }

}


// 下载器。
class WebDownloader {

    // 下载方法。
    public void downloader(String url, String name) {
        try {
            FileUtils.copyURLToFile(new URL(url), new File(name));
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

}


线程创建 ~ 实现 Runnable 接口。

package com.geek;

public class TestThread02 implements Runnable {

    public static void main(String[] args) {

        // 创建 Runnable 接口的实现类对象。
        TestThread02 testThread02 = new TestThread02();
        // 创建线程对象。通过线程对象来开启线程。代理。
        Thread thread = new Thread(testThread02);
        // 调用 start(); 方法开启线程。
        thread.start();

        // main 线程,主线程。
        for (int i = 0; i < 200; i++) {
            System.out.println("主线程 ~ " + i);
        }
    }

    @Override
    public void run() {
        // run 方法线程体。
        for (int i = 0; i < 200; i++) {
            System.out.println(" ~ " + i);
        }
    }

}



小结。

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


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


线程创建 ~ 实现 Callable 接口。

  • 实现 Callable 接口,需要泛型。
  • 重写 call(); 方法,需要抛异常。
  • 创建目标对象。
  • // 创建执行服务。

ExecutorService executorService = Executors.newFixedThreadPool(3);

  • // 提交执行。

Future booleanFuture = executorService.submit(t1);
try {

  • // 获取结果。

Boolean r1 = booleanFuture.get();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}

  • // 关闭服务。

executorService.shutdownNow();

package com.geek.demo02;

import org.apache.commons.io.FileUtils;

import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.util.concurrent.*;

/**
 * 线程实现方式 3 ~ 实现 Callable 接口。
 */
// 实现 Callable 接口,需要泛型。

// 可以定义返回值。
// 需要抛异常。

public class TestCallable implements Callable<Boolean> {

    private String url;// 网络图片地址。
    private String name;// 保存的文件名。

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

    public static void main(String[] args) {
        // 创建目标对象。
        TestCallable t1 = new TestCallable("https://bkimg.cdn.bcebos.com/pic/0b55b319ebc4b74595cd99c0c3fc1e178b82158e?x-bce-process=image/watermark,image_d2F0ZXIvYmFpa2UyNzI=,g_7,xp_5,yp_5", "1.jpg");
        TestCallable t2 = new TestCallable("https://bkimg.cdn.bcebos.com/pic/0b55b319ebc4b74595cd99c0c3fc1e178b82158e?x-bce-process=image/watermark,image_d2F0ZXIvYmFpa2UyNzI=,g_7,xp_5,yp_5", "2.jpg");
        TestCallable t3 = new TestCallable("https://bkimg.cdn.bcebos.com/pic/0b55b319ebc4b74595cd99c0c3fc1e178b82158e?x-bce-process=image/watermark,image_d2F0ZXIvYmFpa2UyNzI=,g_7,xp_5,yp_5", "3.jpg");

        // 创建执行服务。
        ExecutorService executorService = Executors.newFixedThreadPool(3);
        // 提交执行。
        Future<Boolean> booleanFuture1 = executorService.submit(t1);
        Future<Boolean> booleanFuture2 = executorService.submit(t2);
        Future<Boolean> booleanFuture3 = executorService.submit(t3);
        try {
            // 获取结果。
            Boolean r1 = booleanFuture1.get();
            System.out.println("r1 = " + r1);
            Boolean r2 = booleanFuture2.get();
            System.out.println("r2 = " + r2);
            Boolean r3 = booleanFuture3.get();
            System.out.println("r3 = " + r3);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
        // 关闭服务。
        executorService.shutdownNow();
    }

    // 重写 call(); 方法,需要抛异常。
    @Override
    public Boolean call() {
        WebDownloader webDownloader = new WebDownloader();
        webDownloader.downloader(url, name);
        System.out.println("下载了文件:" + name);
        return true;
    }

}


// 下载器。
class WebDownloader {

    // 下载方法。
    public void downloader(String url, String name) {
        try {
            FileUtils.copyURLToFile(new URL(url), new File(name));
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

}



线程安全问题。

package com.geek;

/**
 * 多个线程同时操作同一个对象。
 * 发现问题。多个线程操作同一资源情况下,线程不安全。
 */
public class TestThreadMulti implements Runnable {

    // 票数。
    private int ticketNum = 10;

    public static void main(String[] args) {
        TestThreadMulti testThreadMulti = new TestThreadMulti();
        new Thread(testThreadMulti, "小明").start();
        new Thread(testThreadMulti, "老师").start();
        new Thread(testThreadMulti, "黄牛").start();
    }

    @Override
    public void run() {
        while (true) {
            if (ticketNum <= 0) {
                break;
            }
            try {
                Thread.sleep(200);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread() + " 拿到了第 " + ticketNum-- + " 票。");
        }
    }

}

Thread[老师,5,main] 拿到了第 9 票。
Thread[黄牛,5,main] 拿到了第 8 票。
Thread[小明,5,main] 拿到了第 10 票。
Thread[老师,5,main] 拿到了第 7 票。
Thread[黄牛,5,main] 拿到了第 6 票。
Thread[小明,5,main] 拿到了第 5 票。
Thread[小明,5,main] 拿到了第 4 票。
Thread[老师,5,main] 拿到了第 3 票。
Thread[黄牛,5,main] 拿到了第 3 票。
Thread[黄牛,5,main] 拿到了第 2 票。
Thread[老师,5,main] 拿到了第 0 票。
Thread[小明,5,main] 拿到了第 1 票。

Process finished with exit code 0


龟兔赛跑。

package com.geek;

public class Race implements Runnable {

    // 胜利者。
    private static String winner;

    public static void main(String[] args) {
        Race race = new Race();
        new Thread(race, "兔子").start();
        new Thread(race, "乌龟").start();
    }

    @Override
    public void run() {
        for (int i = 1; i <= 100; i++) {

            // 模拟兔子休息。
            if (Thread.currentThread().getName().equals("兔子") && i % 10 == 0) {
                try {
                    Thread.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }

            // 判断比赛是否结束。
            boolean flag = gameOver(i);
            if (flag) {
                break;
            }
            System.out.println(Thread.currentThread().getName() + " ~ 跑了 " + i + " 步。");
        }
    }

    // 判断比赛是否结束。
    private boolean gameOver(int step) {
        if (null != winner) {// 已经存在胜利者。
            return true;
        }
        {
            if (step >= 100) {
                winner = Thread.currentThread().getName();
                System.out.println("Winner is " + winner);
                return true;
            }
        }
        return false;
    }

}



静态代理模式。

结婚。

你:真实角色。
婚庆公司:代理你,帮你处理琐事。
结婚:实现结婚接口即可。

package com.geek.proxy;

interface Marry {
    // 久旱逢甘雨
    // 他乡遇故知
    // 洞房花烛夜
    // 金榜提名时

    void happyMarry();
}

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

    public static void main(String[] args) {

        // 线程底层原理。(对比静态代理)。
        // Thread ~ 代理角色。
        // Runnable ~ 真实对象。重写要实现的方法。
        new Thread(()-> System.out.println("我爱你")).start();

        You you = new You();// 你要结婚,真实对象。
        new WeddingCompany(you).happyMarry();
//        WeddingCompany WeddingCompany = new WeddingCompany(you);
//        WeddingCompany.happyMarry();
    }

}


// 真实角色。你结婚。
class You implements Marry {

    @Override
    public void happyMarry() {
        System.out.println("geek 要结婚了。。。");
    }

}

// 代理角色。帮助你结婚。
class WeddingCompany implements Marry {

    private Marry target;

    public WeddingCompany(Marry target) {
        this.target = target;
    }

    @Override
    public void happyMarry() {
        before();
        this.target.happyMarry();// 真实对象。
        after();
    }

    private void after() {
        System.out.println("结婚之后。");
    }

    private void before() {
        System.out.println("结婚之前。");
    }

}



Lambda 表达式。

  • 避免匿名内部类定义过多。
  • 让代码看起来更简洁。
  • 去掉一堆没有意义的代码,只留下核心逻辑。
函数式接口 ~ Functional Interface。

任何接口,如果只包含一个抽象方法,ta 就是一个函数式接口。

public interface Runnable {
	public abstract void run();
}

对于函数式接口,我们可以通过 Lambda 表达式来创建该接口的对象。

package com.geek.lambda;

// 定义一个函数式接口。
interface ILike {
    void like();
}

public class TestLambda {

    public static void main(String[] args) {
        ILike like = new Like();
        like.like();
    }

}

// 实现类。
class Like implements ILike {

    @Override
    public void like() {
        System.out.println("I like Lambda.");
    }

}

package com.geek.lambda;

// 定义一个函数式接口。
interface ILike {
    void like();
}

public class TestLambda {

    public static void main(String[] args) {
        // 函数式接口实现。(1)。
        ILike like = new Like();
        like.like();

        // 简化 2 ~ 静态内部类。
        ILike like2 = new Like2();
        like2.like();

        // 简化 3 ~ 局部内部类。
        class Like3 implements ILike {
            @Override
            public void like() {
                System.out.println("I like Lambda 3...");
            }
        }

        ILike like3 = new Like3();
        like3.like();

        // 简化 4 ~ 匿名内部类。
        ILike like4 = new ILike() {
            @Override
            public void like() {
                System.out.println("I like Lambda 4...");
            }
        };
        like4.like();

        // 用 Lambda 简化。
        ILike like1 = () -> System.out.println("I like Lambda 5...");
        like1.like();
    }

    // 简化 2 ~ 静态内部类。
    static class Like2 implements ILike {

        @Override
        public void like() {
            System.out.println("I like Lambda 2...");
        }

    }

}

// 实现类。
class Like implements ILike {

    @Override
    public void like() {
        System.out.println("I like Lambda.");
    }

}



线程状态。

在这里插入图片描述



停止线程。

不推荐使用 jdk 提供的 stop();、destroy(); 方法。(已废弃)。

推荐线程自己停止下来。

建议使用一个标志位进行终止变量,当 flag = false,则终止线程运行。

package com.geek.state;

/**
 * 测试 stop。
 */
public class TestStop implements Runnable {

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

    public static void main(String[] args) {
        TestStop testStop = new TestStop();
        new Thread(testStop).start();

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

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

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

}



线程休眠。

sleep(ms) 指定当前线程阻塞的毫秒数。

sleep(); 存在异常 InterryptedException。

sleep(); 时间达到后线程进入就绪状态。

sleep(); 可以模拟网络延时、倒计时等。

每个对象都有一个锁,sleep(); 不会释放锁。

package com.geek.state;

import com.geek.TestThreadMulti;

/**
 * 模拟网络延时。放大问题的发生性。
 */
public class TestSleep implements Runnable {

    // 票数。
    private int ticketNum = 10;

    public static void main(String[] args) {
        TestThreadMulti testThreadMulti = new TestThreadMulti();
        new Thread(testThreadMulti, "小明").start();
        new Thread(testThreadMulti, "老师").start();
        new Thread(testThreadMulti, "黄牛").start();
    }

    @Override
    public void run() {
        while (true) {
            if (ticketNum <= 0) {
                break;
            }
            // 模拟延时。
            try {
                Thread.sleep(200);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread() + " 拿到了第 " + ticketNum-- + " 票。");
        }
    }

}

package com.geek.state;

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

public class TestSleep2 {

    public static void main(String[] args) throws InterruptedException {
//        tenDown();
        currentTime();
    }

    // 打印当前系统时间。
    public static void currentTime() {
        Date date = new Date(System.currentTimeMillis());
        while (true) {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(new SimpleDateFormat("HH:mm:ss").format(date));
            date = new Date(System.currentTimeMillis());
        }
    }

    // 模拟倒计时。
    public static void tenDown() throws InterruptedException {
        int number = 10;
        while (true) {
            Thread.sleep(1000);
            System.out.println(number--);
            if (number < 0) {
                break;
            }
        }
    }

}



线程礼让。

yield

v. 出产(作物);产生(收益、效益等);提供;屈服;让步;放弃;缴出
n. 产量;产出;利润

礼让线程,让当前正在执行的线程暂停,但不阻塞。

将线程从运行状态转为就绪状态。

让 CPU 重新调度,礼让不一定成功,看 CPU 心情。

package com.geek.state;

/**
 * 测试线程礼让。
 * 礼让不一定成功,看 CPU 心情。
 */
public class TestYield {

    public static void main(String[] args) {
        MyYield myYield = new MyYield();

        new Thread(myYield, "a").start();
        new Thread(myYield, "b").start();
    }

}


class MyYield implements Runnable {

    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName() + " 线程开始执行。");
        Thread.yield();
        System.out.println(Thread.currentThread().getName() + " 线程停止执行。");
    }

}



join。

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

  • 可以想象成插队。

package com.geek.state;

/**
 * 测试 join。
 */
public class TestJoin implements Runnable {

    public static void main(String[] args) throws InterruptedException {
        TestJoin testJoin = new TestJoin();
        Thread thread = new Thread(testJoin);
        thread.start();

        // 主线程。
        for (int i = 0; i < 500; i++) {
            if (i == 200) {
                thread.join();// 插队。
            }
            System.out.println("main" + i);
        }
    }

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

}



线程状态。


    /**
     * A thread state.  A thread can be in one of the following states:
     * <ul>
     * <li>{@link #NEW}<hr>

<br>
     *     A thread that has not yet started is in this state.
     *     </li>
     * <li>{@link #RUNNABLE}<hr>

<br>
     *     A thread executing in the Java virtual machine is in this state.
     *     </li>
     * <li>{@link #BLOCKED}<hr>

<br>
     *     A thread that is blocked waiting for a monitor lock
     *     is in this state.
     *     </li>
     * <li>{@link #WAITING}<hr>

<br>
     *     A thread that is waiting indefinitely for another thread to
     *     perform a particular action is in this state.
     *     </li>
     * <li>{@link #TIMED_WAITING}<hr>

<br>
     *     A thread that is waiting for another thread to perform an action
     *     for up to a specified waiting time is in this state.
     *     </li>
     * <li>{@link #TERMINATED}<hr>

<br>
     *     A thread that has exited is in this state.
     *     </li>
     * </ul>
     *
     * <p>
     * A thread can be in only one state at a given point in time.
     * These states are virtual machine states which do not reflect
     * any operating system thread states.
     *
     * @since   1.5
     * @see #getState
     */
    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;
    }
  • NEW。
    尚未启动的线程。

  • RUNNABLE。
    在 Java 虚拟机中执行的线程状态。

  • BLOCKED。
    被 阻塞等待监视器锁定的线程状态。

  • WAITING。
    正在等待另一个线程执行特定动作的线程状态。

  • TIMED_WAITING。
    正在等待另一个线程执行动作打到指定等待时间的线程状态。

  • TERMINATED。
    已退出的线程状态。

一个线程可以在给定时间点处于一个状态。这些状态是不反映任何操作系统线程状态的虚拟状态。

package com.geek.state;

/**
 * 线程状态。
 */
public class TestState {

    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(() -> {
            for (int i = 0; i < 5; i++) {
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println("~ ~ ~ ~ ~ ~ ~");
        });
        // 观察状态。
        Thread.State state = thread.getState();
        System.out.println("state = " + state);
        // 启动后。
        thread.start();
        state = thread.getState();
        System.out.println("state = " + state);

        System.out.println("~ ~ ~ ~ ~ ~ ~");

        while (state!= Thread.State.TERMINATED) {// 只要线程不终止,就一直输出状态。
            Thread.sleep(100);
            state = thread.getState();
            System.out.println("state = " + state);
        }

        thread.start();// Exception in thread "main" java.lang.IllegalThreadStateException
    }

}



线程优先级。

Java 提供一个线程调度器来监控程序中启动后进入就绪状态的所有线程,线程调度器按照优先级决定应该调度哪个线程来执行。

线程的优先级用数字表示,范围 1 ~ 10。

    /**
     * The minimum priority that a thread can have.
     */
    public final static int MIN_PRIORITY = 1;

   /**
     * The default priority that is assigned to a thread.
     */
    public final static int NORM_PRIORITY = 5;

    /**
     * The maximum priority that a thread can have.
     */
    public final static int MAX_PRIORITY = 10;

  • 获取 / 改变优先级。

getPriority(); setPriority(int a);

package com.geek.state;

/**
 * 测试线程优先级。
 */
public class TestPriority {

    public static void main(String[] args) {
        // 主线程 ~ 默认优先级。
        System.out.println(Thread.currentThread().getName() + " ~ ~ ~ " + Thread.currentThread().getPriority());

        MyPriority myPriority = new MyPriority();

        Thread t1 = new Thread(myPriority);
        Thread t2 = new Thread(myPriority);
        Thread t3 = new Thread(myPriority);
        Thread t4 = new Thread(myPriority);
        Thread t5 = new Thread(myPriority);
        Thread t6 = new Thread(myPriority);

        // 优先级。
        t1.start();

        t2.setPriority(1);
        t2.start();

        t3.setPriority(4);
        t3.start();

        t4.setPriority(Thread.MAX_PRIORITY);// 10
        t4.start();

//        t5.setPriority(-1);
//        t5.start();
//
//        t6.setPriority(11);
//        t6.start();
    }

}


class MyPriority implements Runnable {

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

}



守护进程 ~ daemon。

daemon

程序;守护线程;背景程序;守护进程;守护程序

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

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

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

eg. 后台记录操作日志,监控内存、垃圾回收…

package com.geek.state;

/**
 * 测试守护线程。
 * 上帝守护你。
 */
public class TestDaemon {

    /**
     * You 线程结束,程序就结束。不管 God 线程是否结束。
     *
     * @param args
     */
    public static void main(String[] args) {
        God god = new God();
        Thread thread = new Thread(god);
        thread.setDaemon(true);// 默认是 false,表示是用户线程。
        thread.start();

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

}


// 上帝。
class God implements Runnable {

    @Override
    public void run() {
        while (true) {
            System.out.println("上帝保佑着你。");
        }
    }

}

// 你。
class You implements Runnable {

    @Override
    public void run() {
        for (int i = 0; i < 36500; i++) {
            System.out.println("你一生都开心的活着。");
        }
        System.out.println("~ ~ ~ ~ ~ ~ ~ goodbye, world.");
    }

}



线程同步。

多个线程操作同一个资源。

并发:同一个对象被多个线程同时操作。

上万人同时抢 100 张票。
两个银行同时取钱。

现实生活中,我们会遇到“同一个资源,多个人都想使用”的问题,eg. 食堂排队打饭,每个人都想吃饭,最天然的办法就是,排队,一个个来。

处理多线程问题时,多个线程访问同一个对象,并且某些线程还想修改这个对象,这是我们就需要线程同步。线程同步其实就是一种等待机制,读个需要同时访问此对象的线程进入到这个对象的等待池形成队列,等待前面线程使用完毕,下一个线程再使用。

由于同一进程的多个线程共享同一块存储空间,在带来方便的同时也带来了访问冲突问题,为了保证数据在方法中被访问时的正确性,在访问时加入锁机制 synchronized,当一个线程获得对象的排它锁,独占资源,其他线程必须等待,使用后释放锁即可。存在以下问题。

  • 一个线程持有锁会导致其他所有需要此锁的线程挂起。
  • 在多线程竞争下,加锁,释放锁会导致比较多的上下文切换和调度延时,引起性能问题 。
  • 如果一个优先级高的线程等待一个优先级低的线程锁释放,会导致优先级倒置,引起性能问题。

synchronize

v. (使)同步,在时间上一致,同速进行

package com.geek.syn;

/**
 * 不安全的银行。
 */
public class UnsafeBank {

    public static void main(String[] args) {
        // 账户。
        Account account = new Account(100, "结婚基金");

        Drawing you = new Drawing(account, 50, "你");
        Drawing girlFriend = new Drawing(account, 100, "girlFriend");

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

}

/**
 * 账户。
 */
class Account {

    int money;// 余额。

    String name;// 卡名。

    public Account(int money, String name) {
        this.money = money;
        this.name = name;
    }

}

class Drawing extends Thread {

    Account account;// 账户。
    int drawingMoney;// 取了多少钱。
    int nowMoney;// 手里的钱。

    public Drawing(Account account, int drawingMoney, String name) {
        super(name);
        this.account = account;
        this.drawingMoney = drawingMoney;
    }

    // 取钱。
    @Override
    public void run() {
//        super.run();
        // 判断有没有钱。
        if (account.money - drawingMoney < 0) {
            System.out.println(Thread.currentThread().getName() + " 钱不够,取不了。");
            return;
        }

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

        // 卡内余额 = 余额 - 你取的钱。
        account.money = account.money - drawingMoney;
        // 手里的钱。
        nowMoney += drawingMoney;

        System.out.println(account.name + " 余额为:" + account.money);
        // this.getName() = Thread.currentThread().getName()。
        System.out.println(this.getName() + " 手里的钱:" + nowMoney);
    }

}



线程不安全的集合。

package com.geek.syn;

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

/**
 * 线程不安全的集合。
 */
public class UnsafeList {

    public static void main(String[] args) {
        List<String> list = new ArrayList<>();
        for (int i = 0; i < 10000; i++) {
            new Thread(() -> {
                list.add(Thread.currentThread().getName());
            }).start();
        }
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(list.size());// 9999
    }

}



解决 ~ 同步方法。

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

synchonized 方法和 synchonized 块。

public synchonized void method(String[] args) {}

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

缺陷:若将一个大的方法声明为 synchonized 将会影响效率。



同步块 ~ synchonized(Object object) {}。

synchonized(Object object) {}

obj 称之为同步监视器

obj 可以是任何对象,但是推荐使用共享资源作为同步监视器。
同步方法中无需 指定同步监视器,因为同步方法的同步监视器就是 this,就是这个对象本身,或者是 class(反射中讲解)。

同步监视器的执行过程。

  • 第一个线程访问,锁定同步监视器,执行其中的代码。
  • 第二个线程访问,发现同步监视器被锁定,无法访问。
  • 第一个线程访问完毕,解锁同步监视器。
  • 第二个线程访问,发现同步监视器没有锁,然后锁定并访问。
package com.geek.syn;

/**
 * 不安全买票。
 */
public class UnsafeBuyTicket {

    public static void main(String[] args) {
        BuyTicket buyTicket = new BuyTicket();
        new Thread(buyTicket, "苦逼的我").start();
        new Thread(buyTicket, "牛逼的你们").start();
        new Thread(buyTicket, "可恶的黄牛").start();
    }

}


class BuyTicket implements Runnable {

    // 外部停止方式。
    boolean flag = true;
    // 票。
    private int ticketNumber = 10;

    @Override
    public void run() {
        // 买票。
        while (flag) {
            try {
                buy();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    // synchronized 同步方法
//    private void buy() throws InterruptedException {
    private synchronized void buy() throws InterruptedException {
        // 判断是否有票。
        if (ticketNumber <= 0) {
            flag = false;
            return;
        }
        // 模拟延时。
        Thread.sleep(100);
        // 买票。
        System.out.println(Thread.currentThread().getName() + " 拿到 " + ticketNumber--);
    }

}

package com.geek.syn;

/**
 * 不安全的银行。
 */
public class UnsafeBank {

    public static void main(String[] args) {
        // 账户。
        Account account = new Account(100, "结婚基金");

        Drawing you = new Drawing(account, 50, "你");
        Drawing girlFriend = new Drawing(account, 100, "girlFriend");

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

}

/**
 * 账户。
 */
class Account {

    int money;// 余额。

    String name;// 卡名。

    public Account(int money, String name) {
        this.money = money;
        this.name = name;
    }

}

class Drawing extends Thread {

    Account account;// 账户。
    int drawingMoney;// 取了多少钱。
    int nowMoney;// 手里的钱。

    public Drawing(Account account, int drawingMoney, String name) {
        super(name);
        this.account = account;
        this.drawingMoney = drawingMoney;
    }

    // 这里 用 synchronized 锁 run(); 方法没用。
    // synchronized 默认锁 this 对象。
    // 我们需要锁的是 Account 对象。
    // 取钱。
    @Override
//    public synchronized void run() {
    public void run() {
//        super.run();

        // 锁的对象是变化的量,公共资源。
        synchronized (Account.class) {

            // 判断有没有钱。
            if (account.money - drawingMoney < 0) {
                System.out.println(Thread.currentThread().getName() + " 钱不够,取不了。");
                return;
            }

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

            // 卡内余额 = 余额 - 你取的钱。
            account.money = account.money - drawingMoney;
            // 手里的钱。
            nowMoney += drawingMoney;

            System.out.println(account.name + " 余额为:" + account.money);
            // this.getName() = Thread.currentThread().getName()。
            System.out.println(this.getName() + " 手里的钱:" + nowMoney);
        }
    }

}

package com.geek.syn;

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

/**
 * 线程不安全的集合。
 */
public class UnsafeList {

    public static void main(String[] args) {
        List<String> list = new ArrayList<>();
        for (int i = 0; i < 10000; i++) {
            new Thread(() -> {
                synchronized (List.class) {
                    list.add(Thread.currentThread().getName());
                }
            }).start();
        }
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(list.size());// 9996
    }

}



juc。

package com.geek.juc;

import java.util.concurrent.CopyOnWriteArrayList;

public class TestJUC {

    public static void main(String[] args) {
        CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<>();

        for (int i = 0; i < 10000; i++) {
            new Thread(() -> {
                list.add(Thread.currentThread().getName());
            }).start();
        }
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(list.size());// 10000
    }

}



死锁。

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

package com.geek.deadLock;

/**
 * 多个线程互相抱着对放需要的资源,形成僵持。
 */
public class TestDeadLock {

    public static void main(String[] args) {
        Makeup g1 = new Makeup(0, "灰姑娘");
        Makeup g2 = new Makeup(1, "白雪公主");

        g1.start();
        g2.start();
    }

}


// 口红。
class Lipstick {

}

// 镜子。
class Mirror {

}

class Makeup extends Thread {

    // 需要的资源只有一份,用 static 来保证。
    static Lipstick lipstick = new Lipstick();
    static Mirror mirror = new Mirror();

    int choice;// 选择。
    String girlName;// 使用化妆品的人。

    Makeup(int choice, String girlName) {
        this.choice = choice;
        this.girlName = girlName;
    }

    @Override
    public void run() {
//        super.run();
        // 化妆。
        try {
            makeup();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    /**
     * 化妆。互相持有对方的锁,就是需要拿到对方的资源。
     */
    private void makeup() throws InterruptedException {
        if (choice == 0) {
            synchronized (lipstick) {// 获得口红的锁。
                System.out.println(this.girlName + " 获得口红的锁。");
                Thread.sleep(1000);
                synchronized (mirror) {// 一秒后想获得镜子。
                    // (此时         Makeup g2 = new Makeup(1, "白雪公主");)
                    // mirror 是被锁住的。
                    System.out.println(this.girlName + " 获得镜子的锁。");
                }
            }
        } else {
            synchronized (mirror) {// 获得镜子的锁。
                System.out.println(this.girlName + " 获得镜子的锁。");
                Thread.sleep(2000);
                synchronized (lipstick) {// 两秒后想获得口红。
                    System.out.println(this.girlName + " 获得口红的锁。");
                }
            }
        }
    }

}

结果。卡着不动。

灰姑娘 获得口红的锁。
白雪公主 获得镜子的锁。

↓ ↓ ↓

改进。不抱对方的锁。

package com.geek.deadLock;

/**
 * 多个线程互相抱着对放需要的资源,形成僵持。
 */
public class TestDeadLock {

    public static void main(String[] args) {
        Makeup g1 = new Makeup(0, "灰姑娘");
        Makeup g2 = new Makeup(1, "白雪公主");

        g1.start();
        g2.start();
    }

}


// 口红。
class Lipstick {

}

// 镜子。
class Mirror {

}

class Makeup extends Thread {

    // 需要的资源只有一份,用 static 来保证。
    static Lipstick lipstick = new Lipstick();
    static Mirror mirror = new Mirror();

    int choice;// 选择。
    String girlName;// 使用化妆品的人。

    Makeup(int choice, String girlName) {
        this.choice = choice;
        this.girlName = girlName;
    }

    @Override
    public void run() {
//        super.run();
        // 化妆。
        try {
            makeup();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    /**
     * 化妆。互相持有对方的锁,就是需要拿到对方的资源。
     */
    private void makeup() throws InterruptedException {
        if (choice == 0) {
            synchronized (lipstick) {// 获得口红的锁。
                System.out.println(this.girlName + " 获得口红的锁。");
                Thread.sleep(1000);
            }
            synchronized (mirror) {// 一秒后想获得镜子。
                System.out.println(this.girlName + " 获得镜子的锁。");
            }
        } else {
            synchronized (mirror) {// 获得镜子的锁。
                System.out.println(this.girlName + " 获得镜子的锁。");
                Thread.sleep(2000);
            }
            synchronized (lipstick) {// 两秒后想获得口红。
                System.out.println(this.girlName + " 获得口红的锁。");
            }
        }
    }

}

白雪公主 获得镜子的锁。
灰姑娘 获得口红的锁。
灰姑娘 获得镜子的锁。
白雪公主 获得口红的锁。

Process finished with exit code 0


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

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

我们只要想办法破其中任意一个或多个条件就可以避免死锁。



Lock 锁 ~ 可重入锁。

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

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

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

package com.geek.juc;

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class TestLock {

    public static void main(String[] args) {
        TestLock2 testLock2 = new TestLock2();
        new Thread(testLock2).start();
        new Thread(testLock2).start();
        new Thread(testLock2).start();
    }

}


class TestLock2 implements Runnable {

    // Lock 锁。
    private final Lock lock = new ReentrantLock();

    int ticketNumber = 10;

    @Override
    public void run() {
        while (true) {
            try {
                lock.lock();// 加锁。
                if (ticketNumber > 0) {
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(ticketNumber--);
                } else {
                    break;
                }
            } finally {
                lock.unlock();
            }
        }
    }

}



synchronized 与 Lock 对比。
  • Lock 是显式锁(手动开启和关闭锁,别忘记关闭锁)。
    synchronized 是隐式锁,出了作用域自动释放。

  • Lock 只有代码块锁。
    synchronized 有代码块和方法锁。

  • 使用 Lock 锁,JVM 将花费较少的时间来调度线程,性能更好。并且有更好的扩展性(提供更多的子类)。

  • 优先使用顺序。
    Lock > 同步代码块(已经进入了方法体,分配了相应资源) > 同步方法(在方法体之外)。



线程协作 ~ 生产者消费者问题。

  • 应用场景。

假设仓库中只能存放一件产品,生产者将生产出来的产品放入仓库,消费者将仓库中的产品取走消费。

如果仓库中没有产品,则生产者将产品放入仓库,否则停止生产并等待,直到仓库中的产品被消费者取走。

如果仓库中放有产品,则消费者可以将产品取走消费,否则停止消费并等待,直到仓库中再次放入产品为止。

Producer(生产者) --> 数据缓存区 --> Consumer(消费者)。

  • 分析。

这是一个线程同步问题。生产者和消费者共享同一个资源,并且生产者和消费者之间相互依赖,互为条件。

  • 对于生产者,没有生产产品之前,要通知消费者等待,而生产了产品之后,又需要马上通知消费者消费。
  • 对于消费者,在消费之后,要通知生产者已经结束消费,需要生产新的产品以供消费。
  • 在生产者消费者问题中,仅有 synchronized 是不够的。
    synchronized 可阻止并发更新同一个共享资源,实现了同步。
    synchronized 不能用来实现不同线程之间的消息传递(通信)。
  • Java 提供了 方法去解决线程之间的通信问题。
方法名作用
wait();表示线程一直等待,直到其他线程通知。与 sleep(); 不同,会释放锁。
wait(long timeout);指定等待的毫秒数。
notify();唤醒一个处于等待状态的线程。
notifyAll();唤醒同一个对象上所有调用 wait(); 方法的线程,优先级别搞的线程优先调度。

均是 Object 类的方法,都只能在同步方法或者同步代码块中使用,否则会抛出异常 IllegalMonitorStateException。



解决方式 1。

并发协作模型。“生产者 / 消费者模式” ~ 管程法。

  • 生产者:负责生产数据的模块(可能是方法,对象,线程,进程)。
  • 消费者:负责处理数据的模块(可能是方法,对象,线程,进程)。
  • 缓冲区:消费者不能直接使用生产者的数据,它们之间有个“缓冲区”。
    生产者将生产好的数据放入缓冲区,消费者从缓冲区拿出数据。

Producer(生产者) --> 数据缓存区 --> Consumer(消费者)。



解决方式 2。

并发协作模型。“生产者 / 消费者模式” ~ 信号灯法。

package com.geek.juc;

/**
 * 生产者消费者模型 ~ 标志位解决。 ~ 信号灯法。
 */
public class TestPC2 {

    public static void main(String[] args) {
        TV tv = new TV();
        new Player(tv).start();
        new Watcher(tv).start();
    }

}


/**
 * 生产者 ~ 演员。
 */
class Player extends Thread {

    private TV tv;

    public Player(TV tv) {
        this.tv = tv;
    }

    @Override
    public void run() {
        for (int i = 0; i < 20; i++) {
            if (i % 2 == 0) {
                this.tv.play("快乐大本营。");
            } else {
                this.tv.play("抖音记录美好生活。");
            }
        }
    }
}

/**
 * 消费者 ~ 观众。
 */
class Watcher extends Thread {

    private TV tv;

    public Watcher(TV tv) {
        this.tv = tv;
    }

    @Override
    public void run() {
        for (int i = 0; i < 20; i++) {
            tv.watch();
        }
    }

}

/**
 * 产品 ~ 节目。
 */
class TV {
    // 演员表演,观众等待。T
    // 观众观看,演员等待。F
    String voice;// 表演的节目。
    boolean flag = true;

    /**
     * 表演。
     *
     * @param voice
     */
    public synchronized void play(String voice) {
        if (!flag) {
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println("表演了:" + voice);
        // 通知观众观看。
        this.notifyAll();
        this.voice = voice;
        this.flag = !this.flag;
    }

    public synchronized void watch() {
        if (flag) {
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println("观看了:" + voice);

        // 通知演员表演。
        this.notifyAll();
        this.flag = !this.flag;
    }

}



线程池。

背景。

经常创建和销毁,使用量特别大的资源,比如并发情况下的线程,对性能影响很大。

思路。

提前创建好多个线程,放入线程池中,使用时直接获取,使用完放回池中。可以避免频繁创建销毁、实现重复利用。类似生活中的公共交通工具。

好处。

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


使用。

JDK 5.0 起提供了线程池相关 API:ExecutorService 和 Executors。

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

  • void execute(Runnable command) ~ 执行任务 / 命令,没有返回值,一般用来执行 Runnable。
  • <T> Future<T> submit(Callable<T> task) ~ 执行任务,有返回值,一般用来执行 Callable。
  • void shutdown() ~ 关闭连接池。

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

package com.geek;

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

public class ThreadNew {

    public static void main(String[] args) {
        new MyThread1().start();

        new Thread(new MyThread2()).start();

        FutureTask<Integer> futureTask = new FutureTask<>(new MyThread3());
        new Thread(futureTask).start();
        try {
            Integer integer = futureTask.get();
            System.out.println("integer = " + integer);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
    }

}

/**
 * 继承 Thread 类。
 */
class MyThread1 extends Thread {
    @Override
    public void run() {
//        super.run();
        System.out.println("MyThread ~ 1");
    }
}

/**
 * 实现 Runnable 类。
 */
class MyThread2 implements Runnable {

    @Override
    public void run() {
        System.out.println("MyThread ~ 2");
    }

}

/**
 * 实现 Callable 接口。
 */
class MyThread3 implements Callable<Integer> {

    @Override
    public Integer call() throws Exception {
        System.out.println("MyThread ~ 3");
        return 100;
    }

}

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

lyfGeek

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值