Java多线程并发入门(基础知识)

1.每个进程都拥有自己的一整套变量,线程共享数据。

2.共享变量使得线程之间通信更有效、容易。

3.关于Runnable函数式接口的问题参考我的博客:https://www.cnblogs.com/cckong/p/14264821.html

4.直接调用run方法只会在一个线程执行,是同步的,start方法是异步的。

我们知道Thread是实现Runnable接口的,但是调用start方法,是让线程进入了可执行态。如果调用run会当成普通方法调用。

5.线程状态(6个:新建、可运行、阻塞、等待、计时等待、终止)

这里是根据java Thread.state 官方表示的状态

操作系统我们也有线程3种或者5种状态的说法

三种状态:就绪、运行、等待
五种状态:新建、就绪、运行、等待、退出

 6.想要获取当前状态 使用getState方法

7.当你new Thread时进入新建状态了

8.可运行状态取决于CPU处理器是否运行你,有可能在运行也有可能不在。

9.阻塞或等待状态原因:需要的资源的锁没有释放

等待另一个线程通知出现了一个条件

方法有超时参数,线程会进入计时等待阶段。

10.终止状态:run方法自然退出,线程自然终止

没有捕获的异常使线程意外终止

11.中断线程:interrupt方法请求终止一个线程,(阻塞的线程无法请求终止),最好捕获InterruptedException

12.守护线程:setDaemon(true)转换为守护线程。

13.异常

检查型异常:其他所有

非检查型异常:派生于Error类 、RuntimeException类的异常。

14.run方法不能抛出任何检查型异常,但非检查型可能导致线程终止,或死亡。

15.针对上条,可以用处理未捕获异常的处理器,实现UncaughtExceptionHandler接口的类。

16.让线程调用setUncaughtExceptionHandler静态方法安装处理器。

17.线程优先级 可以用setPriority方法1-10(不推荐声明优先级)

18.竞态条件:两个或以上线程对于共享数据的修改会让对象破坏,需要要同步存取。

例子:修改数字三步:读出数据,修改数据,写回数据。

线程A读出、修改了,这时候线程B杀了进来,完成了修改三部曲,A继续第三步,这时候数字就已经不对了

19.Reentrant类:重入锁

从JDK5引入。下面来看一个实例

 private ReentrantLock mylock=new ReentrantLock();

    public void transfer(int a)
    {
        
        mylock.lock();
        try {
            a++;
        }
        finally {
            mylock.unlock();
        }
    }

unlock操作一定要放在finally里面执行,不然其他线程永久阻塞。

20.用条件对象(if/while)来管理获得一个锁但不能做有用工作的线程。

例子:线程A进入修改数字程序,发现数字低于多少值不能修改了。

21.await方法 线程暂停 并放弃锁

signalAll方法重新激活所有等待此条件的线程(通知现在有可能满足条件,线程需自己去检查满足条件与否)

22。只要一个对象的状态发生变化,就signalAll所有等待线程来检查条件。

23.signal()唤醒指定线程。

24.总结锁和条件对象:

锁保护代码片段,一次只有一个线程执行代码

一个锁可以有一个或多个条件对象

锁管理进入保护代码的线程

条件对象管理进入保护代码但还不能运行的线程

24.synchronized关键字:

可以作为synchronized方法 或者 synchronized代码块

从Java1.0开始,每个对象都有一个内部锁。这个锁有一个内部条件

如果一个方法声明时有synchronized关键字 对象的锁将保护整个方法代码

public synchronized void method()
{
    //body
}

等价于(intrinsic固有的)

public void method()
{
    this.intrinsicLock.lock();
    try{
        //body
    }
    finally{
        this.intrinsicLock.unlock();
    }
}

wait和notify的应用:

 wait将线程添加到等待集里面

notify/notifyAll解除等待线程的阻塞

25.并发关键字使用推荐顺序:
(1)concurrent包里的机制,如阻塞队列

(2)synchronized关键字,减少代码量

(3)Lock/Condition

26.监视器monitor特型:

只包含私有字段的类

类中每个对象都有一个关联的锁

所有方法由这个锁锁定。

锁可以有任意多的相关条件。

27.监视器本质上就是os上的管程,https://www.cnblogs.com/noteless/p/10394054.html,这篇博客讲的非常清楚。

28.volatile关键字(英文翻译:不稳定的)提供免锁机制,编译器和jvm知道该字段有可能被另一个线程更新

29.

private boolean done;
public synchronized boolean isDone(){ return done;}
public synchronized void setDone(){done=false;}

同时调用两个方法,肯定会有一个加锁,另一个阻塞。

使用volatile关键字修改对其他所有线程可见。

private volatile boolean done;
public  boolean isDone(){ return done;}
public  void setDone(){done=false;}

30.假设对共享变量只有赋值的功能,可以使用volatile关键字。

31.stop(终止)不安全,suspend(阻塞)易导致死锁,resume(恢复suspend的线程):都试图控制一个线程的操作,而不是线程互操作。

stop:结束所有未结束的方法,包括run方法,立即释放对象的锁,导致对象处于不一致状态。(转账,钱取出来了,还没放进别人账户,就是释放了,不安全)

因为调用stop的线程不清楚 调用线程的状态!

suspend如果线程在拥有一个锁,然后被阻塞了,锁是不能释放的,会导致别的线程无法访问,导致死锁。

安全挂起线程:suspendRequested变量

32.并发的修改一个数据结构,很容易破环这个数据结构(链表的指针无效、混乱等)

33.可以并发的时候加锁来保证数据结构的安全,但不如选择线程安全的实现。

34.阻塞队列:

35.concurrentHashMap计数方法*如果映射太大 会超出int)

 36..concurrentHashMap默认支持16个书写器,通过构造器可以更多。

37.

 38.对于并发散列映射

search搜索:为每个键或者值应用一个函数,直到生成一个非null的结果

reduce规约:组合所有键或值

foreach:所有键或值应用一个函数。

39.线程池::包含很多准备运行的线程。

40.Callble和Runnable类似,封装一个call方法(函数式接口),但是有返回值,返回调用的类型。Callable<Integer> 就会返回Integer

public interface Callable<V>
{
    V call() throws Exception;
}

41.Future保存异步计算的结果,接口下的方法:
V get()调用后阻塞,直到计算完成

V get(long tiomeout,TimeUnit unit)调用后阻塞,在时间限制内计算完成返回,否则返回异常

void cancel(boolean mayInterrupt)取消计算,已经开始计算让它中断,没开始则取消

boolean isCancelled()是否取消

boolean isDone()是否完成

42.执行器(Executors)类中有很多静态工厂类方法来构造线程池

43.实现线程的三个方法

继承Thread类 实现Runnable接口 实现Callable接口

44.使用Thread类写一个线程

我们先观察一下 线程调用run方法和start方法的区别

package com.Thread;

/**
 * @Description: 使用继承thread类 实现线程。重写run方法,在主函数用start调用
 * @Author: cckong
 * @Date: 2021/2/2
 */
public class Test01 extends Thread{
    @Override
    public void run() {
        for (int i = 0; i < 20; i++) {
            System.out.println("run"+i);
        }
    }

    public static void main(String[] args) {
        Test01 test01=new Test01();
        test01.run();
        for (int i = 0; i < 20; i++) {
            System.out.println("main"+i);
        }
    }
}

 我们可以看到run方法是执行完在执行其他的线程。

我们再看一下start方法

package com.Thread;

/**
 * @Description: 使用继承thread类 实现线程。重写run方法,在主函数用start调用
 * @Author: cckong
 * @Date: 2021/2/2
 */
public class Test01 extends Thread{
    @Override
    public void run() {
        for (int i = 0; i < 17; i++) {
            System.out.print("   start"+i);
        }
    }

    public static void main(String[] args) {
        Test01 test01=new Test01();
        test01.start();
        System.out.println("
");
        for (int i = 0; i < 17; i++) {
            System.out.print("   main"+i);
        }
    }
}

 可以看出是交替执行的。(由CPU调度安排)

 45.实现Runnable接口生成一个线程

package com.Thread;

/**
 * @Description: 实现Runnable接口 实现线程.先重写run方法,然后使用一个thread代理调用start方法
 * @Author: cckong
 * @Date: 2021/2/2
 */
public class Test02 implements Runnable{
    @Override
    public void run() {
        for (int i = 0; i < 17; i++) {
            System.out.print("   start"+i);
        }
    }

    public static void main(String[] args) {
        Test02 test02=new Test02();
        new Thread(test02).start();
        
        
        for (int i = 0; i < 17; i++) {
            System.out.print("   main"+i);
        }
    }
}

 我们可以看见也需要重写run方法。唯一的不同是 需要new一个Thread来代理调用

46.二者区别

47.经典抢火车票问题

/**
 * @Description: 经典抢火车票例子
 * @Author: cckong
 * @Date: 2021/2/2
 */
public class Test03 implements Runnable{
    private int ticnum=10;

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

    public static void main(String[] args) {
        Test03 test03=new Test03();

        new Thread(test03,"黄牛").start();
        new Thread(test03,"小明").start();
        new Thread(test03,"小红").start();
        new Thread(test03,"老师").start();


    }
}

 出现了很多并发的问题需要解决。

我们现在ticnum上加了 volatile关键字 其作用是 变量的更改对外可见

但还是无法保证安全性

48.经典龟兔赛跑问题

/**
 * @Description: 龟兔赛跑案例
 * @Author: cckong
 * @Date: 2021/2/2
 */
public class Test04 implements Runnable{
    private static String winner;//赢家的名字
    @Override
    public void run() {
        String user=Thread.currentThread().getName();
        int step=0;//记录当前步数
        for (int i = 0; i < 101; i++) {

            //这里是对于兔子树下睡觉的形象化 当线程用户是兔子时 十步歇一会
            if (user.equals("兔子") && step % 10 == 0) {
                try {
                    Thread.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            step++;//步数+1
            System.out.println(user+"已经跑了"+step+"步");

            if(win(step)) break;//当已经有了赢家了或者自己赢了 就停止循环
        }

    }
    //判断是否结束游戏了(已经存在赢家 自己赢了)
    public boolean win(int step)
    {
        if(winner!=null) return true;
        if(step>=100){
            winner=Thread.currentThread().getName();

            System.out.println(winner+"赢了");
            return true;

        }
        return false;
    }

    public static void main(String[] args) {
        Test04 test04=new Test04();

        new Thread(test04,"兔子").start();
        new Thread(test04,"乌龟").start();
//
//        System.out.println(winner+"赢了");
    }
}

49.实现callbale接口 建立线程

/**
 * @Description: callable接口
 * @Author: cckong
 * @Date: 2021/2/3
 */
public class Test05 implements Callable<Boolean> {
    @Override
    public Boolean call() throws Exception {
        for (int i = 0; i < 17; i++) {
            System.out.print("   start"+i);
        }
        return true;//可以有返回值
    }

    public static void main(String[] args) {
        Test05 test05=new Test05();//创建一个线程
        ExecutorService executorService= Executors.newFixedThreadPool(1);//创建执行环境 并指定线程数
        Future<Boolean> r1=executorService.submit(test05);//提交线程

System.out.println(" "); for (int i = 0; i < 17; i++) { System.out.print(" main"+i); }
boolean rs1=false;//默认false try { rs1 = r1.get();//获得返回值 } catch (InterruptedException e) { e.printStackTrace(); } catch (ExecutionException e) { e.printStackTrace(); } System.out.println(rs1); executorService.shutdownNow();//关闭服务 } }

50.模拟倒计时(Thread.sleep())

51.线程礼让yield

礼让是讲资源让出来 然后由cpu重新调度。不一定礼让就会换线程

正在运行的线程由运行状态变为就绪状态。

52.线程强制执行join(插队)

主线程和thread刚开始是并发执行的。

在i=200时 thread进行插队 只执行thread。

thread执行之后在执行主线程。

53.Thread.getState()获取当前状态

54.setPriority()设置优先级

当你设置最高优先级的话 也不一定调度 要看cpu的调度情况

要求在1-10之间

55.守护线程 daemon

需要用到线程函数 setDaemon设置为true 默认是false是用户线程。

main是用户线程 gc垃圾回收算法是守护线程

 

在这个程序里 上帝作为守护线程先输出 当用户线程 你启动了之后 开始运行你

当你运行结束。此时jvm里面只有一个守护线程上帝。

上帝还要运行一段时间 因为jvm关闭需要时间。

56.list是线程不安全的集合

 可以使用CopyOnWriteArrayList是JUC包下的安全集合

57.死锁的条件

58.线程池

原文地址:https://www.cnblogs.com/cckong/p/14260544.html