进程:执行中的程序;一个进程至少包含一个线程
线程:进程中负责程序执行的执行单元
线程本身依靠程序进行运行
线程是程序中的顺序控制流,只能使用分配给程序的资源和环境
单线程:程序中只存在一个线程,实际上主方法就是一个主线程
多线程:指的是这个程序(一个进程)运行时产生了不止一个线程,目的是为了更好地使用CPU资源,多个线程并发执行可以提高程序的效率,可以同时完成多项工作,多线程节约的是执行程序的等待时间,如果程序排列紧密,没有等待时间,多线程不能真正的提高效率。
相比于多进程,多线程的优势有:
- 进程之间不能共享数据,线程可以
- 系统创建进程需要为该进程重新分配系统资源,故创建线程代价比较小
- Java语言内置了多线程功能支持,简化了Java多线程编程
线程的实现
继承Tread类的实现
在java.lang包中定义,继承Thread类必须重写run()方法
public class ThreadLearn extends Thread {
@Override
public void run() {
System.out.println("主动创建线程");
}
}
public class Example {
public static void main(String[] args) {
ThreadLearn thread = new ThreadLearn();
thread1.start();
}
}
打印:主动创建线程
public class ThreadLearn extends Thread {
private String name;
public ThreadLearn(String name) {
this.name = name;
}
@Override
public void run() {
System.out.println("name"+this.name+"子线程ID"+Thread.currentThread().getId());
}
}
public static void main(String[] args) {
System.out.println("主线程ID:"+Thread.currentThread().getId());
ThreadLearn thread1 = new ThreadLearn("thread1");
thread1.start();
ThreadLearn thread2 = new ThreadLearn("thread2");
thread2.run();
}
//打印结果
主线程ID:1
namethread2子线程ID1
namethread1子线程ID11
从输出结果可以得出以下结论:
- thread1和thread2的线程ID不同,thread2和主线程ID相同,说明通过run方法调用并不会创建新的线程,而是在主线程中直接运行run方法,跟普通的方法调用没有任何区别;
- 虽然thread1的start方法调用在thread2的run方法前面调用,但是先输出的是thread2的run方法调用的相关信息,说明新线程创建的过程不会阻塞主线程的后续执行。
定义类实现Runnable接口
在Java中创建线程除了继承Thread类之外,还可以通过实现Runnable接口来实现类似的功能。实现Runnable接口必须重写其run方法。
public class RunnableLearn implements Runnable {
@Override
public void run() {
System.out.println("子线程ID:"+Thread.currentThread().getId());
}
}
public class Example {
public static void main(String[] args) {
System.out.println("主线程ID:"+Thread.currentThread().getId());
RunnableLearn runnable = new RunnableLearn();
Thread thread = new Thread(runnable);
thread.start();
}
//打印
主线程ID:1
子线程ID:11
Thread类中定义了一个Runnable类型的成员变量target用来接收Runnable的子类对象;当调用Thread对象的start()方法的时候,方法内首先会判断target是否为null。如果不为null就调用Runnable子类对象的run方法;多个Thread对象可以共享一个Runnable子类对象。
在Java中,这2种方式都可以用来创建线程去执行子任务,具体选择哪一种方式要看自己的需求。
继承Thread的多线程实现可以直接使用Thread类中的方法,代码简单;弊端是如果已经有了父类,就不能用这种方法(Java只允许单继承),所以如果自定义类需要继承其它类,则只能选择实现Runnable接口。而实现Runnable接口的类不能直接使用Thread中的方法,需要先获取到线程对象后,才能得到Thread的方法,代码复杂。
匿名内部类实现多线程的两种方式
继承Thread类
// 匿名内部类的多继承的实现
new Thread() {
public void run() {
while(true)
System.out.println("继承Thread的线程");
}
}.start();
Thread thread = new Thread() {
public void run() {
while(true)
System.out.println("继承Thread的线程");
}
};
thread.start();
//创建Thread对象,提供Runnable对象
new Thread(new Runnable() {
public void run() {
while(true) {
System.out.println("实现Runnable接口的线程");
}
}
}).start();
Thread thread1 = new Thread(new Runnable() {
public void run() {
while(true)
System.out.println("实现Runnable接口的线程");
}
});
thread1.start();
多线程的方法
- 通过getName()方法获取线程对象的名字
- 通过构造方法传入String类型的名字或通过setName(String name)方法设置线程对象的名字
- 获取当前线程的对象
Thread.currentThrad()
- 休眠线程
Thread.sleep(millis)//参数为毫秒
- 守护线程
setDaemon()
: 围绕着其它非守护线程运行,该线程不会单独运行,当其它非守护线程都执行结束后,自动退出 - 加入线程
join()/join(int)
- 礼让线程
yield
- 设置线程的优先级
setPriority()
public class Example {
public static void main(String[] args) {
// 获取当前主线程ID
System.out.println("主线程ID:"+Thread.currentThread().getId());
Thread thread = new Thread("线程一"); // 传入字符串设置线程名字
String threadName = thread.getName(); // 获取线程名字
System.out.println(threadName); //线程一
thread.setName("线程二"); // 将线程一改名为线程二
threadName = thread.getName();
System.out.println(threadName); // 线程二
try {
Thread.sleep(1000);//休眠线程(设置线程等待1秒)
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
//守护线程
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
System.out.print("线程t1运行中");
}
});
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("守护线程t2运行中");
}
});
Thread t3 = new Thread(new Runnable() {
@Override
public void run() {
System.out.print("线程t3运行中");
}
});
t2.setDaemon(true); // 设置守护线程
t1.start();
t3.start();
t2.start(); // t1和t3运行之后,自动退出
// 加入线程
// 当前线程暂停,等待指定的线程执行结束后,当前线程在继续
Thread tj1 = new Thread(new Runnable() {
@Override
public void run() {
for(int i = 0; i < 50; i++) {
System.out.println("线程一运行中...");
try {
Thread.sleep(10);
}catch(InterruptedException e) {
e.printStackTrace();
}
}
}
});
Thread tj2 = new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 50; i++) {
System.out.println("线程二运行中..........");
try {
// tj1.join(); //让线程一先执行
tj1.join(100);//让线程一先执行100毫秒,100毫秒之后,当前加入线程之后就不起作用了
} catch (InterruptedException e) {
e.printStackTrace();
}
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
});
//只有调用了加入线程的线程才会停止运行,其他线程不受影响
Thread tj3 = new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 50; i++) {
System.out.println("线程三运行中..........");
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
});
tj1.start();
tj2.start();
tj3.start();
// 礼让线程
//让出当前线程的执行权
Thread ty1 = new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 50; i++) {
System.out.println("y线程一运行中");
//让出当前线程的执行权
Thread.yield();
}
}
});
Thread ty2 = new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 50; i++) {
System.out.println("y线程二运行中..........");
}
}
});
ty1.start();
ty2.start();
//设置线程的优先级
//每个线程都有优先级 默认是5,范围是1-10,1表示优先级最低
//优先级高的线程在争夺cpu的执行权上有一定的优势,但不是绝对的
Thread tp1 = new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 50; i++) {
System.out.println("p线程一运行中");
}
}
});
Thread tp2 = new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 50; i++) {
System.out.println("p线程二运行中..........");
}
}
});
tp1.setPriority(1);
tp2.setPriority(10); //线程二的优先级高
tp1.start();
tp2.start();
}
}
共享资源及安全问题
所谓的共享资源,指的是多条线程在运行时操作的(共享的)同一内容
- 实现Runnable接口:
操作共享资源,只需要在Runnable类中创建成员变量,通过run方法来操作该成员变量,该成员变量就是共享资源
public class Demo2_apple {
public static void main(String[] args) {
// 思路:
// 1.创建两条线程,接收实现Runnable接口的对象
SubRunnable myRunnable = new SubRunnable();
Thread t1 = new Thread(myRunnable);
Thread t2 = new Thread(myRunnable);
t1.setName("小明");
t2.setName("小清");
t1.start();
t2.start();
}
}
class SubRunnable implements Runnable {
int appleCount = 5;
@Override
public void run() {
while(appleCount>0) {
appleCount--;
System.out.println(Thread.currentThread().getName()+"抢到了一个苹果"
+ ",还剩"+appleCount+"个苹果");
}
}
}
- 继承Thread类的方式:
通过将共享资源作为对象,通过构造器传递给Thread
public class Demo1_apple {
// 两个小朋友,一个叫刘文浩,一个叫龚陈超,两个小朋友去抢苹果,
// 一共有五个苹果,一次只能拿一个,看谁抢的多
public static void main(String[] args) {
// 思路:
// 1.创建两条线程,将Apple类对象通过构造器传入
Apple apple = new Apple();
Thread t1 = new SubThread(apple);
Thread t2 = new SubThread(apple);
t1.setName("小明");
t2.setName("小清");
t1.start();
t2.start();
}
}
// 共享的资源
class Apple {
int appleCount = 5;
}
class SubThread extends Thread {
Apple apple; // 共享资源
public SubThread(Apple apple) {
this.apple = apple;
}
@Override
public void run() {
while(apple.appleCount>0) {
apple.appleCount--;
System.out.println(Thread.currentThread().getName()+"抢到了一个苹果"
+ ",还剩"+apple.appleCount+"个苹果");
}
}
}
发现了线程安全的问题
在多条操作同一资源时,一条线成夹在另一条线程内执行,对共享资源进行多次修改,上面的代码执行的结果为
通过实现Runnable接口实现的共享资源,打印的内容
小明抢到了一个苹果,还剩4个苹果
小清抢到了一个苹果,还剩4个苹果
小明抢到了一个苹果,还剩3个苹果
小清抢到了一个苹果,还剩2个苹果
小明抢到了一个苹果,还剩1个苹果
小清抢到了一个苹果,还剩0个苹果
通过继承Thread类的方式实现的共享资源,打印的内容
小明抢到了一个苹果,还剩3个苹果
小明抢到了一个苹果,还剩2个苹果
小明抢到了一个苹果,还剩1个苹果
小清抢到了一个苹果,还剩3个苹果
小明抢到了一个苹果,还剩0个苹果
上面的输出结果,两条线程有输出为一样的结果,这就是两个线程都对资源进行了修改,这样就出现了线程安全问题,我们要怎么解决这个问题呢,答案是使用锁.
多线程加锁
Java通过①synchronized关键字;②Java.util.concurrent包中的lock接口和ReentrantLock实现类这两种方式来实现加锁.
synchronized
- 同步代码块:谁充当着锁,synchronized(对象锁)
同步代码块使用的是对象锁,"对象锁"对对象没有要求,任何对象都可以充当锁,对象锁的意义是形成互斥
注意:当一个对象充当锁时,该对象锁要同时被多条线程共享才有意义,否则不能形成互斥; 对象锁的互斥是面向所有线程的. - 同步方法:方法所在的对象充当着锁.
public synchronized void method(){}
- 静态同步方法:所在类的类模板(字节码)充当着锁
public static synchronized void method() {} // 是这个类的类对象充当着锁 Class.class
当我们给线程加上所之后
while(apple.appleCount>0) {
synchronized(apple) {
if(apple.appleCount>0) {
apple.appleCount--;
System.out.println(Thread.currentThread().getName()+"抢到了一个苹果"
+ ",还剩"+apple.appleCount+"个苹果");
}
}
}
执行的结果,就不会出现了重复的苹果
ReentrantLock
使用ReentrantLock对象实现上锁
class MyRunnable1 implements Runnable {
int num = 0;
Lock lock = new ReentrantLock();
@Override
public void run() {
while(num<100) {
lock.lock();
num++;
System.out.println(Thread.currentThread().getName()+":"+num);
lock.unlock();
}
}
}
线程的死锁
死锁的定义:使用同步的多个线程同时持有对方运行时所需的资源
多线程同步时,当线程需要需要的锁被另一条线程占用,另一条线程没有办法释放锁,因此当前线程也无法获得所,于是程序就卡住了.
应该尽量避免使用嵌套的synchronized,因为这样会很容易出现死锁问题.
public class Demo4_deadLock {
public static void main(String[] args) {
final StringBuffer sb1 = new StringBuffer();
final StringBuffer sb2 = new StringBuffer();
new Thread() {
@Override
public void run() {
synchronized(sb1) {
sb1.append("a");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
synchronized(sb2) {
sb2.append("b");
}
}
}
}.start();
new Thread() {
@Override
public void run() {
synchronized(sb2) {
sb2.append("c");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
synchronized(sb1) {
sb1.append("d");
}
}
}
}.start();
System.out.println(sb1);
System.out.println(sb2);
}
}
![](https://img.haomeiwen.com/i1933980/fa9e4ccb8b809a6b.jpg)
线程的通信
1. 使用synchronized实现线程的通信
多条线程之间实现通信,需要使用以下几个方法
-
wait()
让当前线程等待 -
notify()
唤醒正在等待的某一条线程,选择是随机的 -
notifyAll()
唤醒正在等待的所有线程
我们现在用一个例子来说明线程之间的通信
// 线程的通信 synchronized
public class Demo5_communication {
// 让两个线程交替打印1~100
public static void main(String[] args) {
MRCommRunnable runnable = new MRCommRunnable();
Thread t1 = new Thread(runnable);
Thread t2 = new Thread(runnable);
t1.setName("线程1");
t2.setName("线程2");
t1.start();
t2.start();
}
}
class MRCommRunnable implements Runnable {
int num = 1;
@Override
public void run() {
while(num < 100) {
synchronized(this) {
notifyAll();
num++;
System.out.println(Thread.currentThread().getName()+":"+num);
try {
wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
}
这样使用就可以实现线程之间的交替打印
我们在main方法中在创建一个线程
public static void main(String[] args) {
Thread t3 = new Thread( new Runnable() {
@Override
public void run() {
while(num1 < 100) {
synchronized(this) {
this.notifyAll();
num1++;
System.out.println(Thread.currentThread().getName()+":"+num1);
try {
wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
});
t3.setName("线程3");
t3.start();
}
当在执行后,这个方法进入线程等待状态 wait(),而在Runnable子类中的唤醒功能却唤醒不了t3线程,这是因为它们的对象锁不是一样的.
注意:这几个方法,只能在synchronized中使用;对象锁拥有两个队列,用于存放被等待的线程,和被唤醒的线程,因此,wait, notify和notifyAll方法是属于Object的;对象锁是谁,就由谁来调用这三个方法
synchronized(a) {
a.wait(); //wait()==this.wait() 不行
}
2.使用Lock实现线程的通信
因为wait, notify,notifyAll只能使用在synchronized关键字中,因此,Lock使用Condition对象的await,singal, singalAll来代替.通过Lock对象的newCondition()来获得Condition对象.
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
// 线程通信 Lock
public class Demo6_comm_lock {
// 让两个线程交替打印1~100
public static void main(String[] args) {
MRCommRunnableLock runnable = new MRCommRunnableLock();
Thread t1 = new Thread(runnable);
Thread t2 = new Thread(runnable);
t1.setName("线程1");
t2.setName("线程2");
t1.start();
t2.start();
}
}
class MRCommRunnableLock implements Runnable {
int num = 1;
Lock lock = new ReentrantLock();
Condition condition = lock.newCondition(); // 通过Condition对象实现线程的通信
@Override
public void run() {
while(num < 100) {
lock.lock(); // 上锁
condition.signalAll(); // 唤醒线程
num++;
System.out.println(Thread.currentThread().getName()+":"+num);
try {
condition.await(); // 让线程等待
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} finally {
lock.unlock(); // 解锁
}
}
}
}
线程的的局部变量-------ThreadLocal
堆空间中的资源往往对于多线程来说都是共享的.在设计一个类时,如果希望类的某些属性不被多线程共享,就可以把这些属性设计交给ThreadLocak来维护.于是,每条线程在使用该类时,都会自动创建ThreadLocal的副本,在实际操作中,操作的每个线程独有的那个ThreadLocak,而不会实现共享.
public class Demo7_threadLocal {
public static void main(String[] args) {
MRThreadLocalRunnable runnable = new MRThreadLocalRunnable("aaa", 123);
Thread t1 = new Thread(runnable);
Thread t2 = new Thread(runnable);
t1.setName("肖邦");
t2.setName("爱迪生");
t1.start();
t2.start();
}
}
class MRThreadLocalRunnable implements Runnable {
private String threadName = new String();
int num;
public MRThreadLocalRunnable(String threadName, int num) {
this.threadName = threadName;
this.num = num;
}
@Override
public void run() {
for(int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName()+"name:"+threadName);
// 设置当前线程的名字
if(i==5)
threadName = Thread.currentThread().getName();
}
}
}
这输出打印的是
肖邦name:aaa
肖邦name:aaa
肖邦name:aaa
肖邦name:aaa
肖邦name:aaa
肖邦name:aaa
肖邦name:肖邦
肖邦name:肖邦
肖邦name:肖邦
肖邦name:肖邦
爱迪生name:aaa
爱迪生name:肖邦
爱迪生name:肖邦
爱迪生name:肖邦
爱迪生name:肖邦
爱迪生name:肖邦
爱迪生name:爱迪生
爱迪生name:爱迪生
爱迪生name:爱迪生
爱迪生name:爱迪生
public class Demo7_threadLocal {
public static void main(String[] args) {
MRThreadLocalRunnable runnable = new MRThreadLocalRunnable("aaa", 123);
Thread t1 = new Thread(runnable);
Thread t2 = new Thread(runnable);
t1.setName("肖邦");
t2.setName("爱迪生");
t1.start();
t2.start();
}
}
class MRThreadLocalRunnable implements Runnable {
// 线程的局部变量
private ThreadLocal<String> threadName = new ThreadLocal<>();
int num;
public MRThreadLocalRunnable(String threadName, int num) {
this.threadName.set(threadName); // 设置线程的局部变量
this.num = num;
}
@Override
public void run() {
for(int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName()+"name:"+threadName.get());
// 设置当前线程的名字
if(i==5)
threadName.set(Thread.currentThread().getName());
}
}
}
输出打印的是
爱迪生name:null
爱迪生name:null
爱迪生name:null
肖邦name:null
肖邦name:null
肖邦name:null
爱迪生name:null
肖邦name:null
爱迪生name:null
肖邦name:null
肖邦name:null
爱迪生name:null
肖邦name:肖邦
爱迪生name:爱迪生
爱迪生name:爱迪生
爱迪生name:爱迪生
爱迪生name:爱迪生
肖邦name:肖邦
肖邦name:肖邦
肖邦name:肖邦
这是因为使用线程的局部变量 ThreadLocal,你在设置名字的时候,它设置的都是属于每个线程的变量,而不是共享的变量
线程池
当频繁地创建和启动多线程,来完成一些比较小的任务,那么这样的代价是非常大的。因此,可以通过线程池来实现,将线程的创建和销毁都交给线程池来管理,而我们需要使用线程时,将任务提交给线程池即可,由线程池来为我们安排一条线程,来执行我们的任务。
jdk1.5之后就提供了相应的类,实现线程池。
1. 创建线程池的方式
- 创建一个有多条线程的线程池,如果线程池中的线程没有可用,就新建一条线程
Executors.newCachedThreadPool()
- 创建一个有多条线程的线程池,如果线程池中的线程没有可用,就进入到等待队列,直到有可用的线程为止
Executors.newFixedThreadPool(int nThreads)
2. 线程池的使用
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class Demo8_threadPool {
public static void main(String[] args) {
// 创建一个指定线程数量的线程池
ExecutorService pool = Executors.newFixedThreadPool(5);
// 创建任务
MYRunnable my = new MYRunnable();
// 将任务交给任务池,有任务池安排线程来执行任务
pool.submit(my);
pool.submit(my);
pool.submit(my);
pool.submit(my);
pool.submit(my);
pool.submit(my);
pool.submit(my);
pool.submit(my);
// 关闭线程池
pool.shutdown();
}
}
class MYRunnable implements Runnable {
@Override
public void run() {
System.out.println("aaaaaaaaaabbbbbccdddeeefffggghhiizzkk");
}
}
注意:之前创建的线程都只能执行一次,但是使用线程池的方式,可以将任务执行多次.
生产者和消费者模式
1.是否涉及到多线程? 是
2.是否涉及到共享资源? 是
- 是否涉及到线程安全问题? 是
- 是否涉及到线程的通信? 是
生产者和消费者都是多线程,它们共享的是商店这个资源,对它进行操作,具体的操作放在共享资源中进行操作
什么时候会失去CPU
- 时间片到了
- 被强占了
- 使用了 yield() 礼让线程
- 使用了 join() 加入线程
- 使用了sleep() 休眠线程
- 使用了 wait() 使线程处于等待状态
是么时候会释放锁
- wait()
- run结束
- 程序出现了异常
网友评论