一、传统线程
并发性(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
A 从Thread-0中取的数据:-203558021
A 从Thread-1中取的数据:-203558021
B 从Thread-1中取的数据:-203558021
B 从Thread-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
A 从Thread-0中取的数据:2144185164
Thread-1放入数据:736558228
A 从Thread-1中取的数据:736558228
B 从Thread-1中取的数据:736558228
B 从Thread-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 中的
get
和run
都是跟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多线程与并发库高级应用