一、进程与线程
1. 进程:
进程是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础。
2. 线程
线程是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。
3. 主线程
Java程序至少会有一个线程,这就是主线程,程序启动后是由JVM创建主线程,程序结束时由JVM停止主线程。
负责管理子线程,即子线程的启动、挂起、停止等等操作。
4. 线程优先级
Java虚拟机允许应用程序同时执行多个执行线程。每个线程都有优先权。 具有较高优先级的线程优先于优先级较低的线程执行。
每个线程可能也可能不会被标记为守护程序。
当在某个线程中运行的代码创建一个新的Thread对象时,新线程的优先级最初设置为等于创建线程的优先级,
并且当且仅当创建线程是守护进程时才是守护线程。
二、线程的创建
1. 创建多线程程序的第一种方式:创建Thread类的子类
java.lang.thread,必须实现thread类
实现步骤:
- 创建一个Thread类的子类
- 在Thread类的子类中重写Thread中的run方法,设置线程任务(开启线程要做什么?)
- 调用Thread中的start方法,开启线程,执行run方法。
void start()使该线程(main线程)和另一个线程(创建的新线程,执行其Run方法)。
多次启动一个线程是非法的。特别是当前的线程已经结束执行后,不能重新启动。
public class CreateThreadDemo {
public static void main(String[] args) {
MyThread myThread = new MyThread();
myThread.start();
for(int i = 0; i < 20; i++){
System.out.println("main"+i);
}
}
}
public class MyThread extends Thread {
@Override
public void run() {
for(int i = 0; i < 20; i++){
System.out.println("run:"+i);
}
}
}
2. 获取线程的名称:
1).使用Thread类中的方法getName()
String getName()返回该线程的名称。
2).可以先获取到当前正在执行的线程,使用线程的方法getName()获取线程的名称
static Thread currentThread() 返回对当前正在执行的线程对象的引用。
/*
线程的名称:
主线程:main
新线程:Thread-0,Thread-1,Thread-2
*/
public class DemoGetThreadName {
public static void main(String[] args) {
MyThreads myThreads = new MyThreads();
myThreads.start();
new MyThreads().start();
new MyThreads().start();
//链式编程
System.out.println(Thread.currentThread().getName());
}
}
//定义一个Thread类的子类
public class MyThreads extends Thread {
@Override
public void run() {
//获取线程名称
//String name = getName();
//System.out.println(name);
// Thread t = currentThread();
// System.out.println(t);
// String name = t.getName();
// System.out.println(name);
//链式编程
System.out.println(Thread.currentThread().getName());
}
}
3. 设置线程名称:(了解)
1).使用Thread类中的方法setName(名字)
void setName(String name)
改变线程名称,使之与参数name相同。
2).创建一个带参数的构造方法,参数传递线程名称;调用父类的带参构造方法,把线程名称传递给父类,让父类(Thread)给子线程起一个名字
Thread(String name)分配新的Thread对象。
public class MyThread extends Thread {
public MyThread(){
}
public MyThread(String name){
super(name);
}
@Override
public void run() {
//获取线程的名称
System.out.println(Thread.currentThread().getName());
}
}
4.进程睡眠
public static void sleep(longmills):使当前正在执行的线程以指定毫秒数暂停(暂时停止执行),毫秒数结束后,线程继续执行。
public class Demo01Sleep {
//模拟秒表
public static void main(String[] args) {
for(int i = 0;i <= 60; i++){
System.out.println(i);
//使用Thread类的Sleep方法让程序睡眠一秒钟
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
5. 创建多线程的第二种方法:实现Runnable接口
java.lang.Runnable
Runnable接口应由任何类实现,其实例将由线程执行。 该类必须定义一个无参数的方法,称为run 。java.lang.Thread类的构造方法 Thread(Runnable target)分配新的Thread对象 Thread(Runnable target,String name)分配新的Thread对象 **实现步骤:** 1).创建一个Runnable接口的实现类 2).在实现类中重写Runnable接口的run方法,设置线程任务 3).创建一个Runnable接口的实现类对象 4).创建Thread类对象,构造方法中传递Runnable接口的实现类对象 5).调用Thread类中的start方法,开启新的线程执行方法 **使用Runnable接口创建多线程程序的好处:** 1).避免了单继承局限性 一个类只能继承一个类(一个人只有一个亲爹),类继承了thread类就不能继承其它的类 实现了Runnable接口,还可以继承其它类,实现其它的接口 2).增强了程序的扩展性,降低了程序的耦合性(解耦) 实现Runnable接口的方式,把设置线程任务和开启线程分(解耦) 实现类中,重写了run方法:用来设置线程任务 创建Thread类对象,调用start方法:用来开启新的线程
public class Demo01_Runnable {
public static void main(String[] args) {
RunnableImpl rmp = new RunnableImpl();
//Thread thread = new Thread(rmp);
//thread.start();
Thread t = new Thread(new RunnableImpl2());
t.start();
for(int i = 0;i<60;i++){
System.out.println(Thread.currentThread().getName()+"-->"+i);
}
}
}
public class RunnableImpl implements Runnable {
@Override
public void run() {
for(int i = 0;i<60;i++){
System.out.println(Thread.currentThread().getName()+"-->"+i);
}
}
}
6. 第三种方法创建线程:匿名内部类方式创建线程
匿名:没有名字
内部类:写在其它类内部中的类**匿名内部类作用**:简化代码 把子类继承父类、重写父类的方法和创建子类对象合一步完成 匿名内部类的最终产物:子类/实现类对象,而这个类没有名字 **格式:** new 父类/接口(){ 重复父类或接口中的方法 };
public class Demo01_InnerClassThread {
public static void main(String[] args) {
//线程的父类使Thread
//new Thread(){}.start;
new Thread(){
//重写run方法,设置线程任务
@Override
public void run() {
for(int i=0;i<15;i++){
System.out.println(Thread.currentThread().getName()+"-->"+i);
}
}
}.start();
//线程的接口Runnable
//RunnableImpl run = new RunnableImpl();
Runnable run = new Runnable(){
//重写run方法,设置线程任务
@Override
public void run() {
for(int i=0;i<15;i++){
System.out.println(Thread.currentThread().getName()+"-->"+i+"黑马");
}
}
};
new Thread(run).start();
//简化接口的方式
new Thread(new Runnable() {
//重写run方法,设置线程任务
@Override
public void run() {
for(int i=0;i<15;i++){
System.out.println(Thread.currentThread().getName()+"-->"+i+"程序员");
}
}
}).start();
}
}
三、线程的状态
在线程的生命周期中,线程会有几种状态:
1. 新建状态
新建状态(New)是通过new等方式创建线程对象,它仅仅是一个空的线程对象。
2.就绪状态
当主线程调用新建线程的start()方法后,它就进入就绪状态(Runnable)。此时的线程尚未真正开始执行run()方法,它必须等待CPU的调度。
3.运行状态
CPU调度就绪状态的线程,线程进入运行状态(Running),处于运行状态的线程独占CPU,执行run()方法。
4.阻塞状态
因为某种原因运行状态的线程会进入不可运行状态,即阻塞状态(Blocked),处于阻塞状态的线程JVM系统不能执行该线程,即使CPU空闲,也不能执行该线程。
几个原因会导致线程进入阻塞状态:
当前线程调用sleep()方法,进入休眠状态。 被其他线程调用了join()方法,等待其他线程结束。
发出I/O请求,等待I/O操作完成期间。 当前线程调用wait()方法。
处于阻塞状态可以重新回到就绪状态,如:休眠结束、其他线程加入、I/O操作完成和调用notify或notifyAll唤醒wait线程。
5.死亡状态
线程退出run()方法后,就会进入死亡状态(Dead),线程进入死亡状态有可能是正常实现完成run()方法进入,也可能是由于发生异常而进入的。
四、线程安全问题
1. 解决线程安全的第一种方法:使用同步代码块
格式:
synchronized(锁对象){
可能出现线程安全问题的代码(访问了共享数据的代码)
}
注意:
1).通过代码块中的锁对象,可以使用任意的对象
2).但是必须保证多个线程使用的锁对象是同一个
3).锁对象的作用:
把同步代码块锁住,只让一个线程在同步代码块中执行
2. 解决线程安全的第二种方法:使用同步方法
**使用步骤:**
1).把访问量共享数据的代码放到一个方法中去
2).在方法上添加Synchronized修饰符
**格式:**
修饰符 synchronized 返回值类型 方法名(参数列表){
可能出现线程安全问题的代码(访问了共享数据的代码)
}
3. 解决线程安全的第三种方法:使用lock锁
java.util.concurrent.locks.Lock接口
Lock实现提供比使用synchronized方法和语句可以获得的更广泛的锁定操作。
它们允许更灵活的结构化,可能具有完全不同的属性,并且可以支持多个相关联的对象Condition 。
**Lock中的方法:**
1).void lock() 获得锁。
2).void unlock()释放锁。
java.util.concurrent.locks.ReentrantLock implement Lock接口
**使用步骤:**
1).在成员位置创建一个ReentrantLock对象
2).在可能会出现线程安全问题的代码前调用Lock接口中的方法lock获取锁
3).在可能会出现线程安全问题的代码前调用Lock接口中的方法unlock释放锁
public class RunnableImpl implements Runnable {
//设置线程任务
private static int ticket = 100;
Lock l = new ReentrantLock();
@Override
public void run() {
System.out.println("this:"+this);
while (true) {
l.lock();
//线程进,进入获取锁对象
if (ticket > 0) {
//提高安全问题出现的概率,让程序睡眠
try {
Thread.sleep(10);
//票存在,则可以出售
System.out.println(Thread.currentThread().getName() + "正在出售第-->" + ticket-- + "张票");
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
//线程出,释放获取锁对象
//无论程序是否执行异常都将释放锁对象
l.unlock();
}
}
}
}
/*@Override
public void run() {
System.out.println("this:"+this);
while (true) {
l.lock();
//线程进,进入获取锁对象
if (ticket > 0) {
//提高安全问题出现的概率,让程序睡眠
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
//票存在,则可以出售
System.out.println(Thread.currentThread().getName() + "正在出售第-->" + ticket-- + "张票");
}
//线程出,释放获取锁对象
l.unlock();
}
}
*/
/*
静态的同步方法锁对象是谁?
--不是this,this是对象创建后产生的,静态方法优先于对象
静态方法的锁对象是本类的Class属性-->class文件对象(反射)
*/
public static /*synchronized*/ void payTicketStatic(){
synchronized (RunnableImpl.class) {
//线程进,进入获取锁对象
if (ticket > 0) {
//提高安全问题出现的概率,让程序睡眠
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
//票存在,则可以出售
System.out.println(Thread.currentThread().getName() + "正在出售第-->" + ticket-- + "张票");
}
//线程出,释放获取锁对象
}
}
/*
定义一个同步方法
同步方法也会把方法内部代码锁住
只让一个线程执行
同部方法的对象是谁?
就是new RunnableImpl()
也是就是this
*/
public synchronized void payTicket(){
//线程进,进入获取锁对象
if (ticket > 0) {
//提高安全问题出现的概率,让程序睡眠
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
//票存在,则可以出售
System.out.println(Thread.currentThread().getName() + "正在出售第-->" + ticket--+"张票");
}
//线程出,释放获取锁对象
}
}
五、 线程通信
/*
进入到TimeWaiting(计时等待)有2种方式
1.使用sleep(long m)方法,在毫秒值结束后线程睡醒进入到Runnable/Blocked状态
2.使用wait(long m)方法,wait方法如果在毫秒值结束之后,还没有被notify唤醒,就会自动醒来
唤醒的方法:
void notify():唤醒在此对象监视器上等待的单个线程
void notifyAll():唤醒在此对象监视器上等待的所有线程
*/
public class Demo02WaitAndNotify {
public static void main(String[] args) {
Object obj = new Object();
new Thread(new Runnable() {
@Override
public void run() {
//一直等着买包子
while(true) {
System.out.println("顾客1:老板我要吃包子");
synchronized (obj) {
try {
obj.wait(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("包子做好了,顾客1正在吃包子");
System.out.println("-----------------------------------------------------");
}
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
//一直等着买包子
while(true) {
System.out.println("顾客2:老板我要吃包子");
synchronized (obj) {
try {
obj.wait(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("包子做好了,顾客2正在吃包子");
System.out.println("-----------------------------------------------------");
}
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
//一直在做包子
while(true) {
System.out.println("老板正在做包子,等待5秒钟...");
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (obj) {
//obj.notify();//随机唤醒一个线程
obj.notifyAll();//唤醒所有的线程
}
}
}
}).start();
}
}
六、线程池
/*
JDK1.5之后出现的
java.util.concurrent.Executors:线程池的工厂类
Executors类中的静态方法:
static ExecutorService newFixedThreadPool(int nThreads) 创建一个可重用固定线程的线程池
参数:
int nThreads创建线程池中线程的数量
返回值:
ExcecutorService接口,返回的是ExcecutorService接口的实现类对象,我们可以使用ExcecutorService接口接收(面向接口编程)
java.util.concurrent.ExecutorService:线程池接口
用来从线程池中获取线程,调用start方法;执行线程任务
submit(Runnable task)提交一个Runnable任务用于执行
关闭/销毁线程池:
void shutDown()
线程池的使用步骤:
1.使用线程池的工,厂类Executors里面提供的静态方法newFixedThreadPool(int nThreads);生产指定数目线程的线程池
2.创建一个类,实现Runnable接口,重写Run方法,设置线程任务
3.调用ExecutorService中的方法submit,传递线程任务(实现类),开启线程,执行run方法;
4.调用ExecutorService中的方法shutDown()销毁线程池(不建议执行);
*/
public class Demo01ThreadPool {
public static void main(String[] args) {
// 1.使用线程池的工,厂类Executors里面提供的静态方法newFixedThreadPool(int nThreads);生产指定数目线程的线程池
ExecutorService es = Executors.newFixedThreadPool(2);
//3.调用ExecutorService中的方法submit,传递线程任务(实现类),开启线程,执行run方法;
es.submit(new RunnableImpl());//pool-1-thread-2创建一个新的线程执行
//线程池会一直开启,使用完了线程,会自动把线程归还给线程池,线程可以继续使用
es.submit(new RunnableImpl());//pool-1-thread-2创建一个新的线程执行
es.submit(new RunnableImpl());//pool-1-thread-1创建一个新的线程执行
es.shutdown();
es.submit(new RunnableImpl());//error
}
}
public class RunnableImpl implements Runnable {
@Override
public void run() {
// 2.创建一个类,实现Runnable接口,重写Run方法,设置线程任务
System.out.println(Thread.currentThread().getName()+"创建一个新的线程执行");
}
}