java多线程基础案例(笔记)

一、传统线程

并发性(concurrency)和并行性(parallel)

  • 并发: 指在同一时刻只能有一条指令执行,但多个进程指令被快速轮换执行。使得在宏观上具有多个进程同时执行的效果。
  • 并行: 指在同一个时刻,有多条指令在多个处理器上同时执行。

当创建一个进程时,必须要为该进程分配独立的内存空间,并分配大量的相关资源,但创建一个线程则简单得多,多个线程可以共享进程代码段公有数据等资源。线程之间也很容易实现通信:概况三点

  • 进程不能共享内存、线程之间共享内存很容易
  • 创建进程需要分配系统资源,创建线程代价相对小很多,
  • java语言内置多线程功能的支持。简化多线程的编程难度。

1.1 Thread中的部分源码

public class Thread implements Runnable {
……
  private Runnable target;
……

 private void init(ThreadGroup g, Runnable target, String name,
                      long stackSize) {
        init(g, target, name, stackSize, null, true);
  }
……
   @Override
    public void run() {
        if (target != null) {
            target.run();
        }
    }
……

}

分析Thread 类的run方法,其实是调用目标类的run方法,如果Thread 没有传入 target,run方法中什么都没有做。Thread类实现了Runnable。实现多线程,完成target的run方法重写即可


1.2 Thread.currentThread().getName() 与 this.getName()

package myThread;

/**
 *  测试 获取线程名称的
 */
public class ThreadGetName {

    public static void main(String[] args) {
        new Thread(new myRunnable(),"myRunnable Thread").start();
        new Thread(new myThread(),"myThread Thread").start();
    }

}

class  myRunnable implements  Runnable{
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName());//myRunnable Thread
        // System.out.println(this.getName());// 没有对应的方法// Runnable 接口中就只有一个run方法
        System.out.println(this.getClass());//class myThread.myRunnable

    }

}

class  myThread extends Thread {
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName());//myThread Thread
        System.out.println(this.getName());//Thread-0
        System.out.println(this.getClass());//class myThread.myThread
        System.out.println(getName()); //getName() 是Thread 类中方法 //Thread-0
    }
}

获取线程的名称推荐使用Thread.currentThread().getName()


在创建多线程和线程组的时候,给他们取一个好听的名字,当系统出现问题时,比拿到一连串Thread-0、Thread-1等信息要好的多


多线程之间切换需要消耗资源; 多线程下载是增加了带宽,是抢用资源。


1.3 匿名内部类的案例分析

到底运行哪一个 run。

package myThread;


public class ThreadInstance {

    public static void main(String[] args) {

        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("匿名内部类,是一个匿名目标类,重写了run() 方法");
            }
        }){

            //重写Thread()的ruan方法
            @Override
            public void run() {

                System.out.println("匿名内部类,是Thread的一个匿名子类,重写run() 方法");
            }

        }.start();

    }
}

如果子类覆盖了父类的方法,则调用子类的,否则调用父类的; 因为已经重写了run()方法,所以不再去调用目标类的run()。


1.4 传统定时器

1.4.1 API等说明

public abstract class TimerTask implements Runnable {
……

}

1.4.2 案例

创建一个定时器,2s 和 4s 来回切换执行。

  void  schedule(TimerTask task, long delay)   安排在指定延迟后执行指定的任务。

/**
 * 传统计时器,设置定时器,2s 和 4s 来回切换执行。
 */
public class TraditionalTimer {

    private int count = 0;
    public static void main(String[] args) {
        Timer timer = new Timer();
        timer.schedule(new TraditionalTimer().new MyTimer(),200);//启动任务
    }

    //创建自定义TimerTask
    class  MyTimer extends TimerTask  {
        @Override
        public void run() {
            count = (count + 1)%2 ;
            System.out.println("running"+ System.currentTimeMillis() / 1000);
            new Timer().schedule(new MyTimer(),2000 + count * 2000); //递归执行任务了。
        }
    }
}

1.5 传统线程互斥

多个线程操作同一份资源的时候,会出现线程安全问题

关键字synchronized 使用锁,多个线程需要使用同一把锁,如果不是同一把锁,起不到互斥效果。所以在synchronized中需要使用同一个对象

不轻易使用字节码做锁,除非你的程序中所有线程都是需要互斥的。像分组进行同步,则使用同一份字节码就做不到


1.6 传统线程通信

锁常常用类的字节码,但是当业务方法需要同步多了,不可能都用同一份字节码。就像A B两个组都需要同步,就不能用类的自己码了。 这个时候应该如何更好的处理这种问题呢?


这个时候,就需要将这些方法放在类中,然后同步这个类,让这个类进行同步即可。面向对象,抽象同步方法到同一个类中。把相关联的方法整合到同一个类中

描述: 子线程循环10次,主线程循环100次,又回到子线程执行10次,这样反复50次

package myThread;

/**
 * 线程通信
 */
public class TraditionalCommunicate {

    public static void main(String[] args) {
        //同一份资源
        Bussines bussines = new Bussines();
        //启动一个子线程
        new Thread(new Runnable() {
            @Override
            public void run() {
                for(int i = 1; i <= 50; i++) {
                    bussines.sub(i);
                }
            }
        }).start();
        //主线程
        for(int i = 1; i <= 50; i++) {
            bussines.main(i);
        }
    }
}


//将业务方法抽取到一个类中,单独使用一个类去管理; 线程通信
class Bussines {

    private boolean subRun = true; //信号
    /**
     * 子类方法中循环10次
     */
    public synchronized void sub(int i) {
        while(!subRun) {
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        for(int j = 1; j <= 10; j++) {
            System.out.println("loop of " + i + "-- sub loop of " + j);
        }
        subRun = false;
        this.notify();
    }
    /**
     * 父类方法中执行循环100次
     * @param i
     */
    public synchronized void main(int i) {
        while(subRun) {
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        for(int j = 1; j <= 100; j++) {
            System.out.println("loop of " + i + "-- main loop of " + j);
        }
        subRun = true;
        this.notify();
    }
}

这里写图片描述


1.6 线程范围内共享变量的概念与作用

ThreadLocal的作用和目的: 用于实现线程内的数据共享,即对于相同的程序代码,多个模块在同一个线程中运行时要共享一份数据,而在另外线程中运行又共享另外一份数据.

这里写图片描述


1.6.1 模拟ThreadLocal,进行线程内的数据共享

(一) 应用场景

想让不同的线程,数据隔离,而线程内数据共享,下面是一个失败的案例。

import java.util.Random;

public class ThreadScopeDataShare {
    private static int data = 0;
    private static Random dandom = new Random();

    public static void main(String[] args) {
        for (int i = 0; i < 2; i++) {
            new Thread(new Runnable() {
                public void run() {
                    data = dandom.nextInt();
                    System.out.println(Thread.currentThread().getName()
                            + "放入数据:" + data);
                    new A().get();
                    new B().get();
                }

            }).start();
        }
    }

    static class A {
        public void get() {
            System.out.println("A 从" + Thread.currentThread().getName()
                    + "中取的数据:" + data);
        }
    }

    static class B {
        public void get() {
            System.out.println("B 从" + Thread.currentThread().getName()
                    + "中取的数据:" + data);
        }
    }
}

可能出现下面的运行结果。 线程 Thread-0 和 Thread-1 ;A,B取值是造成不一致

Thread-0放入数据:-362983279
Thread-1放入数据:-203558021
AThread-0中取的数据:-203558021
AThread-1中取的数据:-203558021
BThread-1中取的数据:-203558021
BThread-0中取的数据:-203558021

(二)模拟实现

用一个Map来模拟 ThreadLocal

package myThread;

import java.util.HashMap;
import java.util.Map;
import java.util.Random;

public class ThreadScopeDataShare {

    private static int data = 0;
    private static Map<Thread,Object> threadMap = new HashMap<Thread,Object>(); //模拟ThreadLocal

    public static void main(String[] args) {
        for (int i = 0; i < 2; i++) {
            new Thread(new Runnable() {
                public void run() {
                    data = new Random().nextInt();
                    System.out.println(Thread.currentThread().getName()
                            + "放入数据:" + data);
                    threadMap.put(Thread.currentThread(),data);
                    new A().get();
                    new B().get();
                }

            }).start();
        }
    }

    static class A {
        public void get() {
            System.out.println("A 从" + Thread.currentThread().getName()
                    + "中取的数据:" + threadMap.get(Thread.currentThread()));
        }
    }
    static class B {
        public void get() {
            System.out.println("B 从" + Thread.currentThread().getName()
                    + "中取的数据:" + threadMap.get(Thread.currentThread()));
        }
    }
}

运行结果

Thread-0放入数据:2144185164
AThread-0中取的数据:2144185164
Thread-1放入数据:736558228
AThread-1中取的数据:736558228
BThread-1中取的数据:736558228
BThread-0中取的数据:2144185164

通过Map 数据结果实现了数据在线程内共享。


1.6.3 ThreadLocal (Thread Local Variable)类的介绍

它代表一个线程局部变量,通过把数据放在ThreadLocal中,就可以让每个线程创建一个该变量的副本,从而避免并发访问的线程安全问题。

ThreadLocal的作用和目的,用于实现线程内的数据共享,即对于相同的程序代码,多个模块在同一个线程中运行时要共享一份数据,而在另外线程中运行又共享另外一份数据


每个线程调用全局ThreadLocal对象的set方法,对相当于往其内部的map中增加一条记录,key分别是各自的线程,value是各自的set方法传进去的值。在线程结束是可以调用ThreadLocal.clear()方法,这样会更快释放内存,不调用也可以,因为线程结束后也可以自动释放相关的ThreadLocal变量

API 描述
T get() 返回此线程局部变量中当前线程的值
void remove() 删除此线程局部变量中当前线程的值
void set(T value) 设置此线程局部变量中当前线程副本中的值

案例分析

将线程相关的内容封装到统一类中去。暴露给外部的不是直接的ThreadLoacl,而是一个类的方法.

package myThread;

import java.util.Random;

public class ThreadTest{

    public static void main(String[] args) {
        for (int i = 0; i < 2; i++) {
            new Thread(new Runnable() {
                public void run() {
                    int data = new Random().nextInt();
                    System.out.println(Thread.currentThread().getName()
                            + "放入数据:" + data);

                    MyThreadScopeData.getThreadInstance().setName("name" + data);
                    MyThreadScopeData.getThreadInstance().setAge(data);
                    new A().get();
                    new B().get();
                }

            }).start();
        }
    }

    static class A {
        public void get() {
            System.out.println("A 从" + Thread.currentThread().getName()
                    + "中取的数据:" + MyThreadScopeData.getThreadInstance().getName()
                    + MyThreadScopeData.getThreadInstance().getAge());
        }
    }

    static class B {
        public void get() {
            System.out.println("A 从" + Thread.currentThread().getName()
                    + "中取的数据:" + MyThreadScopeData.getThreadInstance().getName()
                    + MyThreadScopeData.getThreadInstance().getAge());
        }
    }
}

class MyThreadScopeData {

    private static ThreadLocal<MyThreadScopeData> map = new ThreadLocal<MyThreadScopeData>();

    private MyThreadScopeData() {
    }

    public static MyThreadScopeData getThreadInstance() {
        MyThreadScopeData instance = map.get();
        if (instance == null) {
            instance = new MyThreadScopeData();
            map.set(instance);
        }
        return instance;
    }


    private String name;
    private int age;

    public String getName() {
        return name;
    }

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

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }
}

ThreadLocal 中的 get() 和 set() 都和 Thread.currentThread() 关系对应绑定


1.6.3.2 ThreadLocal 部分源码

public class ThreadLocal<T> { 

……
    public T get() {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null) {
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null) {
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                return result;
            }
        }
        return setInitialValue();
    }

……    
    public void set(T value) {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
    }

}

……

ThreadLocal 中的getrun都是跟 Thread.currentThread() 关联


如果多个线程之间需要共享资源,以达到线程之间的通信功能,就使用同步机制,如果仅仅需要隔离多个线程之间的共享冲突,则可以使用 ThreadLocal


1.7.多个线程之间共享数据的方式探讨

多个线程访问共享对象和数据的方式

1.7.1 方式

序号 方式描述
如果每个线程执行的代码相同
1 如果每个线程执行的代码相同,可以使用同一个Runnable对象,这个Runnable对象中有一个共享数据,例如售票系统就可以这么做这么做
每个线程执行的代码不同
2 将数据封装在另外一个对象中,然后将这个对象逐一传递给各个Runnable对象。每个线程对共享数据的操作方法也分配到那个对象身上去完成,这样容易实现针对该数据进行的各个操作的互斥和通信
3 将这些Runnable对象作为某一类中的内部类,共享数据作为外部类中的成员变量,每个线程对共享数据的操作方法也分配给外部类,以便实现对共享数据进行的各个操作的互斥和通信。作为内部类的各个Runnable对象调用外部类的这些方法
4 上面两种方式的组合,将共享数据封装在另一个对象中,每个线程对共享数据的操作方法也分配到那个对象身上去完成,对象作为这个外部对象中的成员内部类或局部内部类

总之,要同步互斥的几段代码最好是分别放在几个独立的方法中,这些方法再放在同一个类中,这样比较容易实现他们之间的同步互斥通信。


1.7.2 案例

package myThread;

/**
 * 设计4个线程,其中两个线程每次对j增加1,另外两个线程对j每次减少1
 */
public class MultiThreadDataShare {

    public static void main(String[] args) {

        ThreadData data = new ThreadData(0); //共享数据

        for(int i = 0; i < 2; i++) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    while(true) {
                        data.add();
                        try {
                            Thread.sleep(500);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }).start();
        }

        for(int i = 0; i < 2; i++) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    while (true) {
                        data.sub();
                        try {
                            Thread.sleep(500);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }).start();
        }
    }

}


class ThreadData {

    private int  j  = 0; //共享数据
    /**
     * 让数据增加 1
     */
    public ThreadData(int j) {
        this.j = j;
    }
    public synchronized  void add() {
        j ++ ;
        System.out.println(j);
    }

    public synchronized void  sub() {
        j--;
        System.out.println(j);
    }

    public int getJ() {
        return j;
    }

    public void setJ(int j) {
        this.j = j;
    }
}

参考:

张孝祥-Java多线程与并发库高级应用

猜你喜欢

转载自blog.csdn.net/qq_31156277/article/details/80477705