Java多线程学习
?一、进程和线程进程:每个进程都有独立的代码和数据空间(进程上下文),进程间的切换会有较大的开销,一个进程包含1--n个线程。 线程:同一类线程共享代码和数据空间,每个线程有独立的运行栈和程序计数器(PC),线程切换开销小。 线程和进程一样分为五个阶段:创建、就绪、运行、阻塞、终止。 多进程是指操作系统能同时运行多个任务(程序)。 多线程是指在同一程序中有多个顺序流在执行。 二、实现多线程的方法在Java中要想实现多线程,有两种手段,一种是继续Thread类,另外一种是实现Runable接口。 1、继承java.lang.Thread类class Thread1 extends Thread{
private String name;
public Thread1(String name) {
this.name=name;
}
public void run() {
for (int i = 0; i < 5; i++) {
System.out.println(name + "运行 : " + i);
try {
sleep((int) Math.random() * 10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
?输出: A运行 : 0
B运行 : 0
A运行 : 1
A运行 : 2
A运行 : 3
A运行 : 4
B运行 : 1
B运行 : 2
B运行 : 3
B运行 : 4
?再运行一下: A运行 : 0
B运行 : 4
A运行 : 4
说明: start()方法的调用后并不是立即执行多线程代码,而是使得该线程变为可运行态(Runnable),什么时候运行是由操作系统决定的。从程序运行的结果可以发现,多线程程序是乱序执行。因此,只有乱序执行的代码才有必要设计为多线程。 Thread.sleep()方法调用目的是不让当前线程独自霸占该进程所获取的cpu资源,以留出一定时间给其他线程执行的机会。实际上所有的多线程代码执行顺序都是不确定的,每次执行的结果都是随机的。但是start方法重复调用的话,会出现java.lang.IllegalThreadStateException异常。 Thread1 mTh1=new Thread1("A");
Thread1 mTh2=mTh1;
mTh1.start();
mTh2.start();
?输出: Exception in thread "main" java.lang.IllegalThreadStateException
at java.lang.Thread.start(Unknown Source)
at com.multithread.learning.Main.main(Main.java:31)
A运行 : 0
A运行 : 1
A运行 : 2
A运行 : 3
A运行 : 4
? 2、实现java.lang.Runnable接口 class Thread2 implements Runnable{
String name;
Thread2(String name) {
name;
}
@Override
run() {
) {
System.out.println(name + "运行 : " + i);
{
Thread.sleep(();
} (InterruptedException e) {
e.printStackTrace();
}
}
}
}
? class Main {
static main(String[] args) {
new Thread(new Thread2("C")).start();
new Thread2("D")).start();
}
}
?输出: C运行 : 0
D运行 : 0
D运行 : 1
C运行 : 1
D运行 : 2
C运行 : 2
D运行 : 3
C运行 : 3
D运行 : 4
C运行 : 4
?说明: Thread2类通过实现Runnable接口,使得该类有了多线程类的特征。run()方法是多线程程序的一个约定。所有的多线程代码都在run方法里面。Thread类实际上也是实现了Runnable接口的类。在启动的多线程的时候,需要先通过Thread类的构造方法Thread(Runnable target) 构造出对象,然后调用Thread对象的start()方法来运行多线程代码。实际上所有的多线程代码都是通过运行Thread的start()方法来运行的。因此,不管是扩展Thread类还是实现Runnable接口来实现多线程,最终还是通过Thread的对象的API来控制线程的,熟悉Thread类的API是进行多线程编程的基础。 三、Thread和Runable的区别如果一个类继承Thread,则不适合资源共享。但是如果实现了Runable接口的话,则很容易的实现资源共享。 private int count=5;
) {
System.out.println(name + "运行 count= " + count--);
(InterruptedException e) {
e.printStackTrace();
}
}
}
}
main(String[] args) {
Thread1 mTh1=);
Thread1 mTh2=new Thread1("B");
mTh1.start();
mTh2.start();
}
}
?输出: B运行 count= 5
A运行 count= 5
B运行 count= 4
B运行 count= 3
B运行 count= 2
B运行 count= 1
A运行 count= 4
A运行 count= 3
A运行 count= 2
A运行 count= 1
? 从上面可以看出,不同的线程之间count是不同的,这对于卖票系统来说就会有很大的问题,当然,这里可以用同步来作。这里我们用Runnable来做下看看 int count=15;
@Override
) {
System.out.println(Thread.currentThread().getName() + "运行 count= " + count--);
(InterruptedException e) {
e.printStackTrace();
}
}
}
}
main(String[] args) {
Thread2 my = new Thread2();
new Thread(my,"C").start();//同一个mt,但是在Thread中就不可以,如果用同一个实例化对象mt,就会出现异常
).start();
).start();
}
}
?输出: C运行 count= 15
D运行 count= 14
E运行 count= 13
D运行 count= 12
D运行 count= 10
D运行 count= 9
D运行 count= 8
C运行 count= 11
E运行 count= 12
C运行 count= 7
E运行 count= 6
C运行 count= 5
E运行 count= 4
C运行 count= 3
E运行 count= 2
? 这里要注意每个线程都是用同一个实例化对象,如果不是同一个,效果就和上面的一样了! 总结: 四、线程状态转换 1、新建状态(New):新创建了一个线程对象。 五、线程调度1、调整线程优先级:Java线程有优先级,优先级高的线程会获得较多的运行机会。Java线程的优先级用整数表示,取值范围是1~10,Thread类有以下三个静态常量: int MAX_PRIORITY 线程可以具有的最高优先级,取值为10
int MIN_PRIORITY 线程可以具有的最低优先级,取值为1
int NORM_PRIORITY 分配给线程的默认优先级,取值为5
? Thread类的setPriority()和getPriority()方法分别用来设置和获取线程的优先级。每个线程都有默认的优先级。主线程的默认优先级为Thread.NORM_PRIORITY。线程的优先级有继承关系,比如A线程中创建了B线程,那么B将和A具有相同的优先级。 JVM提供了10个线程优先级,但与常见的操作系统都不能很好的映射。如果希望程序能移植到各个操作系统中,应该仅仅使用Thread类有以下三个静态常量作为优先级,这样能保证同样的优先级采用了同样的调度方式。 六、常用函数说明 1、sleep(long millis): 在指定的毫秒数内让当前正在执行的线程休眠(暂停执行) Thread t = AThread();
t.start();
t.join();
?为什么要用join()方法: 在很多情况下,主线程生成并起动了子线程,如果子线程里要进行大量的耗时的运算,主线程往往将于子线程之前结束,但是如果主线程处理完其他的事务后,需要用到子线程的处理结果,也就是主线程需要等待子线程执行完成之后再结束,这个时候就要用到join()方法了。 Thread1(String name) {
super(name);
run() {
System.out.println(Thread.currentThread().getName() + " 线程运行开始!");
) {
System.out.println("子线程"+name + "运行 : " + (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName() + " 线程运行结束!");
}
}
main(String[] args) {
System.out.println(Thread.currentThread().getName()+"主线程运行开始!");
Thread1 mTh1=);
mTh1.start();
mTh2.start();
System.out.println(Thread.currentThread().getName()+ "主线程运行结束!");
}
}
?输出结果: main主线程运行开始!
main主线程运行结束!
B 线程运行开始!
子线程B运行 : 0
A 线程运行开始!
子线程A运行 : 0
子线程B运行 : 1
子线程A运行 : 1
子线程A运行 : 2
子线程A运行 : 3
子线程A运行 : 4
A 线程运行结束!
子线程B运行 : 2
子线程B运行 : 3
子线程B运行 : 4
B 线程运行结束!
发现主线程比子线程早结束
?加join的情况: );
mTh1.start();
mTh2.start();
{
mTh1.join();
} (InterruptedException e) {
e.printStackTrace();
}
{
mTh2.join();
} (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+ "主线程运行结束!");
}
}
?运行结果: main主线程运行开始!
A 线程运行结束!
主线程一定会等子线程都结束了才结束。 3、yield():暂停当前正在执行的线程对象,并执行其他线程。 Thread.yield()方法作用是:暂停当前正在执行的线程对象,并执行其他线程。yield()应该做的是让当前运行线程回到可运行状态,以允许具有相同优先级的其他线程获得运行机会。因此,使用yield()的目的是让相同优先级的线程之间能适当的轮转执行。但是,实际中无法保证yield()达到让步目的,因为让步的线程还有可能被线程调度程序再次选中。 class ThreadYield ThreadYield(String name) {
(name);
}
@Override
int i = 1; i <= 50; i++) {
System.out.println("" + this.getName() + "-----" + 当i为30时,该线程就会把cpu时间让掉,让其他或者自己的线程执行(也就是谁先抢到谁执行)
if (i ==30) {
this.yield();
}
}
}
}
main(String[] args) {
ThreadYield yt1 = new ThreadYield("张三");
ThreadYield yt2 = new ThreadYield("李四");
yt1.start();
yt2.start();
}
}
?运行结果: 第一种情况:李四(线程)当执行到30时会cpu时间让掉,这时张三(线程)抢到cpu时间并执行。 Thread4 t1 = new Thread4("t1");
Thread4 t2 = new Thread4("t2");
t1.setPriority(Thread.MAX_PRIORITY);
t2.setPriority(Thread.MIN_PRIORITY);
? 5、interrupt():中断某个线程,这种结束方式比较粗暴,如果t线程打开了某个资源还没来得及关闭也就是run方法还没有执行完就强制结束线程,会导致资源无法关闭。 要想结束进程最好的办法就是用sleep()函数的例子程序里那样,在线程类里面用以个boolean型变量来控制run()方法什么时候结束,run()方法一结束,该线程也就结束了。 class MyThreadPrinter2 Runnable {
String name;
Object prev;
Object self;
MyThreadPrinter2(String name,Object prev,Object self) {
this.name = name;
this.prev = prev;
this.self = self;
}
@Override
run() {
int count = 10;
while (count > 0) {
synchronized (prev) {
(self) {
System.out.print(name);
count--;
self.notify();
}
{
prev.wait();
} (InterruptedException e) {
e.printStackTrace();
}
}
}
}
void main(String[] args) throws Exception {
Object a = Object();
Object b = Object();
Object c = Object();
MyThreadPrinter2 pa = new MyThreadPrinter2("A",c,a);
MyThreadPrinter2 pb = new MyThreadPrinter2("B"new MyThreadPrinter2("C" Thread(pa).start();
Thread.sleep(100); 确保按顺序A、B、C执行
Thread(pb).start();
Thread.sleep(100);
Thread(pc).start();
Thread.sleep(100);
}
}
?输出结果: ABCABCABCABCABCABCABCABCABCABC
? 先来解释一下其整体思路,从大的方向上来讲,该问题为三线程间的同步唤醒操作,主要的目的就是ThreadA->ThreadB->ThreadC->ThreadA循环执行三个线程。为了控制线程执行的顺序,那么就必须要确定唤醒、等待的顺序,所以每一个线程必须同时持有两个对象锁,才能继续执行。一个对象锁是prev,就是前一个线程所持有的对象锁。还有一个就是自身对象锁。主要的思想就是,为了控制执行的顺序,必须要先持有prev锁,也就前一个线程要释放自身对象锁,再去申请自身对象锁,两者兼备时打印,之后首先调用self.notify()释放自身对象锁,唤醒下一个等待线程,再调用prev.wait()释放prev对象锁,终止当前线程,等待循环结束后再次被唤醒。运行上述代码,可以发现三个线程循环打印ABC,共10次。程序运行的主要过程就是A线程最先运行,持有C,A对象锁,后释放A,C锁,唤醒B。线程B等待A锁,再申请B锁,后打印B,再释放B,A锁,唤醒C,线程C等待B锁,再申请C锁,后打印C,再释放C,B锁,唤醒A。看起来似乎没什么问题,但如果你仔细想一下,就会发现有问题,就是初始条件,三个线程按照A,B,C的顺序来启动,按照前面的思考,A唤醒B,B唤醒C,C再唤醒A。但是这种假设依赖于JVM中线程调度、执行的顺序。 wait和sleep区别: 七、常见线程名词解释 主线程:JVM调用程序main()所产生的线程。 sleep(): 强迫一个线程睡眠N毫秒。 八、线程同步Java多线程的难点是在:处理多个线程同步与并发运行时线程间的通信问题。java在处理线程同步时,常用方法有: 1、synchronized关键字。 2、Lock显示加锁。 3、信号量Semaphore。 线程同步问题引入: ????? 创建一个银行账户Account类,在创建并启动100个线程往同一个Account类实例里面添加一块钱。在没有使用上面三种方法的情况下: 代码: import java.util.concurrent.ExecutorService;
java.util.concurrent.Executors;
AccountWithoutSync {
static Account account = new Account(); 实例化一个账户
main(String[] args)
{
long start = System.currentTimeMillis();
使用ExecutorService创建线程池
ExecutorService executor = Executors.newCachedThreadPool();
for(int i=0;i<100;i++)
{
executor.execute( AddPennyTask());
}
关闭线程池 即使线程池中还有未完成的线程 返回未完成的清单
executor.shutdown();
关闭之后还是要保证未完成的线程继续完成 如果线程池中所有任务都完成了,isTerminated返回true
while(!executor.isTerminated())
{}
long end = System.currentTimeMillis();
balance有余额的意思
System.out.println("现在账户里面的余额是:" + account.getBalance());
System.out.println("花费的时间以微秒为单位:"+(end-start)+"微秒");
}
这个线程只调用了一个方法
class AddPennyTask Runnable
{
@Override
run() {
account.deposit(1);
}
}
一个内部类 用于 账户的相关处理
Account
{
int balance =0;
int getBalance()
{
return balance;
}
public void deposit( amount)
{
int newBalance = balance + amount;
为了让错误体现的更明显
{
Thread.sleep(4); 5毫秒
} (InterruptedException e) {
TODO Auto-generated catch block
e.printStackTrace();
}
balance= newBalance;
其实就是balance +=amount;不过换成这一段代码结果就在100和99左右
}
}
}
?运行结果: ?????? 错的不是一般的明显,明明存入了100块,显示只有2块,原因也很简单,就是100个线程同时对acount进行修改,当第100该线程把钱改成100时,第2个线程最后修改,把它改成了2,这群不听话的线程,那就好好管管他们吧,让他们乖乖听话,在这之前,还是要记住以下一些名词: 竞争状态:当多个线程访问同一公共资源并引发冲突造成线程不同步,我们称这种状态为竞争状态。? 线程安全:是针对类来说的,如果一个类的对象在多线程程序中不会导致竞争状态,我们就称这类为线程安全的,上面的Account是线性不安全的,而又如StringBuffer是线程安全的,而StringBuilder则是线程不安全的。? 临界区:造成竞争状态的原因是多个线程同时进入了程序中某一特定部分,如上面程序中的deposit方法,我们称这部分为程序中的临界区,我们要解决多线程不同步问题,就是要解决如何让多个线程有序的访问临界区。? 1、使用synchronized关键字 1、同步方法:在deposit方法前加synchronized关键字,使得该方法变成同步方法:public synchronized void deposit(double amount){}? 2、同步语句:对某一代码块加synchronized关键字,常用格式: (exper)
{
statement
}
? 其中exper是对象的引用,如上面的程序,在要在调用depsoit方法时,改成这样: (account)
{
account.deposit(1);
}
? 使用synchronized,其实是隐式地给方法或者代码块加了锁,任何同步的实例方法都可以转换为同步语句:? synchronized void method(){}
? 转换为同步语句: method{
sysnchronized(){
}
}
?2、利用lock加锁同步 ?????? java也可以用Lock显示的对临界区代码加锁以及解锁,这比用synchronized关键字更加直观灵活。 ?????? 一个锁是一个Lock接口的实例,该接口定义了加锁解锁的方法,且一个锁可以多次调用其newCondition()方法创建名为Condition对象的实例,以此进行线程间的通信(在后面用到)。 ???? 有了Lock接口,我们还要实现它,java提供了RenentrantLock类,该类是为创建相互排斥锁而实现了Lock接口,由此就好办了,下面看一下书上的图: 代码如下: Account2
{
static Lock lock = ReentrantLock();
getBalance()
{
balance;
}
amount)
{
lock.lock();
{
amount;
Thread.sleep(4);
balance= newBalance;
}finally{
lock.unlock();
}
}
}
?给个运行截图: ???????? 100块存入,且时间明显比之前久了,100个线程都乖乖的排队访问临界区。另外注意在对lock()的调用后,紧跟随try catch finnaly语句,这是个好习惯。? 3、利用信号量同步信号量是个好东西,信号量机制在操作系统方面有着广泛的应用,如linux进程同步信号量,而在java里,Semaphore包包含了一些访问信号量的方法。 信号量可以用来限制访问共享资源的线程数,在访问临界区资源前,线程必须获取一个信号量,在访问完之后返回一个信号量。下图是关于类Semaphore,该类包含访问信号量的方法: 利用信号量Semaphorre,可以将上面的Account类改成这样: Account
{
static Semaphore semaphore = new Semaphore(1); 创建一个信号量
amount)
{
{
semaphore.acquire();
newBalance;
} (InterruptedException e) {
e.printStackTrace();
}{
semaphore.release(); 返回一个信号量
}
}
}
? 网上有很多关于java多线程的文章,有不少写成系列的,但个人觉得像多线程这么有有意思的部分,还是得好好静下心来品位,上面的三种方法,可以解决多线程同步问题。 总结:? 1、线程同步的目的是为了保护多个线程反问一个资源时对资源的破坏。 2、线程同步方法是通过锁来实现,每个对象都有切仅有一个锁,这个锁与一个特定的对象关联,线程一旦获取了对象锁,其他访问该对象的线程就无法再访问该对象的其他非同步方法。 3、对于静态同步方法,锁是针对这个类的,锁对象是该类的Class对象。静态和非静态方法的锁互不干预。一个线程获得锁,当在一个同步方法中访问另外对象上的同步方法时,会获取这两个对象锁。 4、对于同步,要时刻清醒在哪个对象上同步,这是关键。 5、编写线程安全的类,需要时刻注意对多个线程竞争访问资源的逻辑和安全做出正确的判断,对“原子”操作做出分析,并保证原子操作期间别的线程无法访问竞争资源。 6、当多个线程等待一个对象锁时,没有获取到锁的线程将发生阻塞。 7、死锁是线程间相互等待锁锁造成的,在实际中发生的概率非常的小。真让你写个死锁程序,不一定好使,呵呵。但是,一旦程序发生死锁,程序将死掉。 九、线程数据传递在传统的同步开发模式下,当我们调用一个函数时,通过这个函数的参数将数据传入,并通过这个函数的返回值来返回最终的计算结果。但在多线程的异步开发模式下,数据的传递和返回和同步开发模式有很大的区别。由于线程的运行和结束是不可预料的,因此,在传递和返回数据时就无法象函数一样通过函数参数和return语句来返回数据。 9.1 通过构造方法传递数据 package mythread;
class MyThread1 Thread
{
String name;
MyThread1(String name)
{
name;
}
run()
{
System.out.println("hello " + name);
}
main(String[] args)
{
Thread thread = new MyThread1("world");
thread.start();
}
}
? 由于这种方法是在创建线程对象的同时传递数据的,因此,在线程运行之前这些数据就就已经到位了,这样就不会造成数据在线程运行后才传入的现象。如果要传递更复杂的数据,可以使用集合、类等数据结构。使用构造方法来传递数据虽然比较安全,但如果要传递的数据比较多时,就会造成很多不便。由于Java没有默认参数,要想实现类似默认参数的效果,就得使用重载,这样不但使构造方法本身过于复杂,又会使构造方法在数量上大增。因此,要想避免这种情况,就得通过类方法或类变量来传递数据。? 9.2 通过变量和方法传递数据 class MyThread2 Runnable
{
setName(String name)
{
main(String[] args)
{
MyThread2 myThread = MyThread2();
myThread.setName("world");
Thread thread = Thread(myThread);
thread.start();
}
}
? 9.3 通过回调函数传递数据 上面讨论的两种向线程中传递数据的方法是最常用的。但这两种方法都是main方法中主动将数据传入线程类的。这对于线程来说,是被动接收这些数据的。然而,在有些应用中需要在线程运行的过程中动态地获取数据,如在下面代码的run方法中产生了3个随机数,然后通过Work类的process方法求这三个随机数的和,并通过Data类的value将结果返回。从这个例子可以看出,在返回value之前,必须要得到三个随机数。也就是说,这个 value是无法事先就传入线程类的。 Data
{
int value = 0;
}
Work
{
process(Data data,Integer numbers)
{
n : numbers)
{
data.value += n;
}
}
}
class MyThread3 Work work;
MyThread3(Work work)
{
this.work = work;
}
run()
{
java.util.Random random = java.util.Random();
Data data = Data();
int n1 = random.nextInt(1000);
int n2 = random.nextInt(2000int n3 = random.nextInt(3000);
work.process(data,n1,n2,n3); 使用回调函数
System.out.println(String.valueOf(n1) + "+" + String.valueOf(n2) + "+"
+ String.valueOf(n3) + "=" + data.value);
}
new MyThread3( Work());
thread.start();
}
}
?? 参考: https://www.cnblogs.com/LZYY/p/3959526.html https://www.cnblogs.com/1020182600HENG/p/5939933.html ? (编辑:北几岛) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |