一.线程概述:
1).线程,是程序执行流的最小单元,线程也有就绪、阻塞和运行三种基本状态。每一个程序都至少有一个线程,若程序只有一个线程,那么就是程序本身。
2).线程的状态:
新建状态:一个新产生的线程从新状态开始了它的生命周期。它保持这个状态知道程序start这个线程。
就绪状态:当一个新状态的线程被start以后,线程就变成可运行状态,一个线程在此状态下被认为是开始执行其任务。
运行状态:当一个线程等待另外一个线程执行一个任务的时候,该线程就进入就绪状态。当另一个线程给就绪状态的线程发送信号时,该线程才重新切换到运行状态。
阻塞状态:由于一个线程的时间片用完了,该线程从运行状态进入休眠状态。当时间间隔到期或者等待的时间发生了,该状态的线程切换到运行状态。
死亡状态:一个运行状态的线程完成任务或者其他终止条件发生,该线程就切换到终止状态。
3).在单个程序中同时运行多个线程完成不同的工作,称为多线程。多线程中带给我们很多的好处,但是又给我们带来了麻烦,就是同步问题和死锁。
4).对于现在的单核CPU来说,某一个时刻只能有一个线程在执行(微观串行),从宏观角度来看多个线程在同时执行(宏观并行)。但是对于双核或双核以上的CPU来说,可以真正的做到微观并行。
二.线程创建:
1). 一个Java应用总是从main()方法开始运行,mian()方法运行在一个线程内,它被称为主线程。
2).每一个java线程都有一个优先级,这样有助于操作系统确定线程的调度顺序,java优先级MIN_PRIORITY(0)和MAX_PRIORITY(10)之间的范围,之间的范围内。默认情况下,每一个线程都会分配一个优先级NORM_PRIORITY(5),不能依靠线程的优先级来决定线程的执行顺序。
3).在java中实现多线程有两种方式,第一种直接继承Thread,第二种实现Runnable接口。
Thread类的重要方法:
1 | public void start() 使该线程开始执行;Java 虚拟机调用该线程的 run 方法。 |
2 | public void run() 如果该线程是使用独立的 Runnable 运行对象构造的,则调用该 Runnable 对象的 run 方法;否则,该方法不执行任何操作并返回。 |
3 | public final void setName(String name) 改变线程名称,使之与参数 name 相同。 |
4 | public final void setPriority(int priority) 更改线程的优先级。 |
5 | public final void setDaemon(boolean on) 将该线程标记为守护线程或用户线程。 |
6 | public final void join(long millisec) 等待该线程终止的时间最长为 millis 毫秒。 |
7 | public void interrupt() 中断线程。 |
8 | public final boolean isAlive() 测试线程是否处于活动状态。 |
(一).先使用第一种:
public class MyThread extends Thread {
private String name;
public MyThread(String name){
this.name = name;
}
public void run() {
// TODO Auto-generated method stub
for (int i = 0; i<5;i++){
System.out.println("MyThread "+this.name);
}
}
}
继承Thread类,实现其中的run()方法,run()方法中写入要执行的代码。
运行:
MyThread mt = new MyThread("A");
MyThread mt1 = new MyThread("B");
mt.start();
mt1.start();
注意我们要启动线程而不是直接使用run(),而是通过start()方法实现。运行结果:
从这里的结果我们可以看出来,执行的顺序,跟代码的顺序是不一样的,这就说明了线程的调用是不跟代码的中的顺序执行的。
(二).使用第二种Runnable:
public class MyRunnable implements Runnable{
private String name ;
public MyRunnable(String name){
this.name = name ;
}
public void run() {
for(int i = 0 ; i<5;i++){
System.out.println("MyRunnable "+this.name);
}
}
}
运行:
MyRunnable mr = new MyRunnable("A");
MyRunnable mr1 = new MyRunnable("B");
MyRunnable mr2 = new MyRunnable("C");
Thread t1 = new Thread(mr);
Thread t2 = new Thread(mr1);
Thread t3 = new Thread(mr2);
t1.start();
t2.start();
t3.start();
运行结果:
这两种实现的方式区别:当我们使用第一种方式来生成线程对象的时,我们需要重写run方法,因为Tread类中的run方法此时什么事情也没有做。而第二种方式来生成线程时,我们需要实现Runnable接口的run方法,然后使用new Tread(new MyRunnable())(假如MyTread已经实现了Runnable接口)来生成线程对象,这时的线程对象的run方法就会调用MyTread类的run方法,这样我们自己编写的run方法就执行了。
三.线程基本使用:
1).线程的消亡不能通过调用一个stop()命令,而是要让run()方法自然结束。
可以主动结束线程的例子:
public class MyThread implements Runnable{
//设置标志
private boolean flag = true;
@Override
public void run() {
while(flag){
//。。。
}
}
public void stopRunning(){
this.flag = false;
}
}
public class ControlThread{
private MyThread runnable = new MyThread();
private Thread thread = new Thread(runnable);
public void startThread(){
thread.start();
}
public void stopThread(){
runnable.stopRunning();
}
}
2).局部变量和成员变量:
成员变量:
public class ThreadTest {
public static void main(String[] args) {
Runnable r = new NumberThread();
Thread t1 = new Thread(r);
Thread t2 = new Thread(r);
t1.start();
t2.start();
}
}
class NumberThread implements Runnable{
//成员变量
int number ;
@Override
public void run() {
//局部变量
//int number = 0;
while(true){
System.out.println("number:"+(number++));
try {
//休眠1s
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
if(number > 10){
break;
}
}
}
}
打印:
number:0
number:0
number:1
number:1
number:2
number:3
number:4
number:5
number:7
number:6
number:8
number:9
number:10
每次运行结果都会不同,但肯定少于20个数。因为多个线程对一个对象的成员变量进行操作的时,他们对该成员变量是互相影响的(也就是说一个线程对成员变量的改变会影响到另一个线程)。
局部变量:
public class ThreadTest {
public static void main(String[] args) {
Runnable r = new NumberThread();
Thread t1 = new Thread(r);
Thread t2 = new Thread(r);
t1.start();
t2.start();
}
}
class NumberThread implements Runnable{
//成员变量
//int number ;
@Override
public void run() {
//局部变量
int number = 0;
while(true){
System.out.println("number:"+(number++));
try {
//休眠1s
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
if(number > 10){
break;
}
}
}
}
打印:
number:0
number:0
。
。
。
number:10
number:10
每个线程都会有一个该局部变量的拷贝,一个线程对该局部变量的改变不会影响到其他的线程。
四.多线程的同步:
1).在多线程中,可能会有两个甚至多个的线程试图同时访问一个有限的资源,必须对这种潜在资源冲突进行预防。
通过例子来看一下(银行取钱):
public class FetchMoney {
public static void main(String[] args) {
Bank bank = new Bank();
//柜台
MoneyThread mt1 = new MoneyThread(bank);
//ATM
MoneyThread mt2 = new MoneyThread(bank);
mt1.start();
mt2.start();
}
}
class Bank{
//存款1000块
private int money = 1000;
public int getMoney(int number){
if(number < 0){
return -1;
}else if(number > money){
return -2;
}else{
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
money -= number;
return number;
}
}
}
class MoneyThread extends Thread{
private Bank bank;
public MoneyThread(Bank bank){
this.bank = bank;
}
@Override
public void run() {
//取钱
System.out.println(bank.getMoney(800));
}
}
打印:800 800
为什么会这个结果呢,因为在第一个线程进入取款方法时进入睡眠状态未执行取钱操作,而第二个线程也进入了取钱方法,也进入了睡眠,那么第一个方法醒后就继续执行,就会取出800来,第二个醒后也继续执行,也取出800来,这就是线程同步的问题了。
如何解决这个问题: 在线程使用一个资源时为其加锁即可,访问资源的第一个线程为其加上锁后,其他线程则不能再使用那个资源,除非解锁。
实现同步:用synchronized关键字,当synchronized修饰一个方法时,该方法就是同步方法。
继续解决上面的例子:
public class FetchMoney {
public static void main(String[] args) {
Bank bank = new Bank();
//柜台
MoneyThread mt1 = new MoneyThread(bank);
//ATM
MoneyThread mt2 = new MoneyThread(bank);
mt1.start();
mt2.start();
}
}
class Bank{
//存款1000块
private int money = 1000;
public synchronized int getMoney(int number){
if(number < 0){
return -1;
}else if(number > money){
return -2;
}else{
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
money -= number;
return number;
}
}
}
class MoneyThread extends Thread{
private Bank bank;
public MoneyThread(Bank bank){
this.bank = bank;
}
@Override
public void run() {
//取钱
System.out.println(bank.getMoney(800));
}
}
打印:800 -2
2).java中的每个对象都有一个锁(lock)或者叫做监视器(monitor),当访问某个对象的synchronized方法时,表示将该对象上锁,此时其他任何线程都无法再去访问该synchronized方法了,直到之前的那个线程执行方法完毕后(或者抛出了异常),那么该对象的锁释放掉,其他线程才有可能再去访问该synchronized方法。
3).当一个对象有多个synchronized方法,某一时刻某个线程已经进入到了某个synchronized方法,那么在该方法没有执行完毕前,其他线程是无法访问该对象的任何synchronized方法。
看例子:
public class ThreadTest2 {
public static void main(String[] args) {
CountClass cc = new CountClass();
ThreadCount1 tc1 = new ThreadCount1(cc);
ThreadCount2 tc2 = new ThreadCount2(cc);
tc1.start();
tc2.start();
}
}
class CountClass {
public synchronized void count() {
for (int i = 0; i < 5; i++) {
System.out.println("A:" + i);
}
}
public synchronized void count2() {
for (int i = 0; i < 5; i++) {
System.out.println("B:" + i);
}
}
}
class ThreadCount1 extends Thread {
private CountClass cc;
public ThreadCount1(CountClass cc) {
this.cc = cc;
}
@Override
public void run() {
cc.count();
}
}
class ThreadCount2 extends Thread {
private CountClass cc;
public ThreadCount2(CountClass cc) {
this.cc = cc;
}
@Override
public void run() {
cc.count2();
}
}
打印:
A:0
A:1
A:2
A:3
A:4
B:0
B:1
B:2
B:3
B:4
上锁的是对象。
4).如果某个synchronized方法是static的,那么当线程访问该方法时,它锁的并不是synchronized方法所在的对象,而是synchronized方法所在的对象所对应的class对象,因为java中无论一个类有多少个对象,这些对象会对应唯一一个对象,因此当线程分别访问同一个类的两个对象的两个static,synchronized方法时,他们的执行顺序也是顺序的,也就是说一个线程先执行方法,执行完毕后另一个线程在执行。
public class ThreadTest2 {
public static void main(String[] args) {
CountClass cc = new CountClass();
ThreadCount1 tc1 = new ThreadCount1(cc);
cc = new CountClass();
ThreadCount2 tc2 = new ThreadCount2(cc);
tc1.start();
tc2.start();
}
}
class CountClass {
public synchronized static void count() {
for (int i = 0; i < 5; i++) {
System.out.println("A:" + i);
}
}
public synchronized static void count2() {
for (int i = 0; i < 5; i++) {
System.out.println("B:" + i);
}
}
}
class ThreadCount1 extends Thread {
private CountClass cc;
public ThreadCount1(CountClass cc) {
this.cc = cc;
}
@Override
public void run() {
//因为是静态方法所以会有警告
cc.count();
}
}
class ThreadCount2 extends Thread {
private CountClass cc;
public ThreadCount2(CountClass cc) {
this.cc = cc;
}
@Override
public void run() {
cc.count2();
}
}
打印:
A:0
A:1
A:2
A:3
A:4
B:0
B:1
B:2
B:3
B:4
5).synchronized块,写法synchronized(obj){ ... }表示线程在执行的时候对obj上锁。当执行完毕后释放。
例子:
public class ThreadTest3 {
public static void main(String[] args) {
CountClass2 cc = new CountClass2();
ThreadCount3 tc3 = new ThreadCount3(cc);
ThreadCount4 tc4 = new ThreadCount4(cc);
tc3.start();
tc4.start();
}
}
class CountClass2 {
private Object object = new Object();
public void count() {
synchronized (object) {
for (int i = 0; i < 5; i++) {
System.out.println("A:" + i);
}
}
}
public void count2() {
synchronized (object) {
for (int i = 0; i < 5; i++) {
System.out.println("B:" + i);
}
}
}
}
class ThreadCount3 extends Thread {
private CountClass2 cc;
public ThreadCount3(CountClass2 cc) {
this.cc = cc;
}
@Override
public void run() {
cc.count();
}
}
class ThreadCount4 extends Thread {
private CountClass2 cc;
public ThreadCount4(CountClass2 cc) {
this.cc = cc;
}
@Override
public void run() {
cc.count2();
}
}
打印:
A:0
A:1
A:2
A:3
A:4
B:0
B:1
B:2
B:3
B:4
如果想实现跟synchronized方法一样的效果只需要将obj换成this表示当前的对象,synchronized方法是一种粗粒度的并发控制,某一时刻,只能有一个线程执行该synchronized方法;synchronized块则是一种细粒度的并发控制,只会将块中的代码同步,位于方法内,synchronized块外的代码是可以被多个线程访问到的。
五.死锁(deadlock):
所谓死锁: 是指两个或两个以上的进程在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,它们都将无法推进下去。
产生死锁的原因:
1.因为系统资源不足。
2.进程运行推进的顺序不合适。
3.资源分配不当。
产生死锁的条件有四个:
1.互斥条件:所谓互斥就是进程在某一时间内独占资源。
2.请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放。
3.不剥夺条件:进程已获得资源,在末使用完之前,不能强行剥夺。
4.循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。
这时我们就要用到Object类中的方法:wait和notify、notifyall方法。
void | wait() 在其他线程调用此对象的 notify() 方法或 notifyAll() 方法前,导致当前线程等待。 |
void | wait(long timeout) 在其他线程调用此对象的 notify() 方法或 notifyAll() 方法,或者超过指定的时间量前,导致当前线程等待。 |
void | wait(long timeout, int nanos) 在其他线程调用此对象的 notify() 方法或 notifyAll() 方法,或者其他某个线程中断当前线程,或者已超过某个实际时间量前,导致当前线程等待。 |
void | notify() 唤醒在此对象监视器上等待的单个线程。 |
void | notifyAll() 唤醒在此对象监视器上等待的所有线程。 |
例子(交替显示1和0):
public class Sample
{
private int number;
public synchronized void increase()
{
while (0 != number)
{
try
{
wait();
}
catch (InterruptedException e)
{
e.printStackTrace();
}
}
number++;
System.out.println("increase:"+number);
notify();
}
public synchronized void decrease()
{
while (0 == number)
{
try
{
wait();
}
catch (InterruptedException e)
{
e.printStackTrace();
}
}
number--;
System.out.println("decrease:"+number);
notify();
}
}
public class IncreaseThread extends Thread
{
private Sample sample;
public IncreaseThread(Sample sample)
{
this.sample = sample;
}
@Override
public void run()
{
for(int i = 0; i < 5; i++)
{
try
{
Thread.sleep((long)(Math.random() * 1000));
}
catch (InterruptedException e)
{
e.printStackTrace();
}
sample.increase();
}
}
}
public class DecreaseThread extends Thread
{
private Sample sample;
public DecreaseThread(Sample sample)
{
this.sample = sample;
}
@Override
public void run()
{
for(int i = 0; i < 5; i++)
{
try
{
Thread.sleep((long)(Math.random() * 1000));
}
catch (InterruptedException e)
{
e.printStackTrace();
}
sample.decrease();
}
}
}
public class MainTest
{
public static void main(String[] args)
{
Sample sample = new Sample();
Thread t1 = new IncreaseThread(sample);
Thread t2 = new DecreaseThread(sample);
Thread t3 = new IncreaseThread(sample);
Thread t4 = new DecreaseThread(sample);
t1.start();
t2.start();
t3.start();
t4.start();
}
}
打印:
increase:1
decrease:0
increase:1
decrease:0
increase:1
decrease:0
increase:1
decrease:0
increase:1
decrease:0
increase:1
decrease:0
increase:1
decrease:0
increase:1
decrease:0
increase:1
decrease:0
increase:1
decrease:0
注意:wait和notify这两个方法要求在调用时线程已经获得了对象的锁,因此对这两个方法的调用需要放在synchronized方法中或块里,当线程执行了wait方式时,它会释放掉对象的锁。